mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-22 20:16:22 +00:00
Compare commits
1583 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f929faa16 | ||
|
|
7a40c34cf0 | ||
|
|
462d987de9 | ||
|
|
f64e8d8973 | ||
|
|
21222eb697 | ||
|
|
e47d6fb3ac | ||
|
|
c7fc10ac89 | ||
|
|
e8b528f520 | ||
|
|
b22de7bf70 | ||
|
|
ec119c8f6e | ||
|
|
a20b38598e | ||
|
|
aa0eb47205 | ||
|
|
723db9d71e | ||
|
|
1d8dc3d65d | ||
|
|
fe2716876a | ||
|
|
fac0e97e5d | ||
|
|
449d009c28 | ||
|
|
55b2e6fe25 | ||
|
|
9989b3b9da | ||
|
|
7ab1cbfc1f | ||
|
|
62d19c3902 | ||
|
|
19700e7ee3 | ||
|
|
de3e8edd6d | ||
|
|
deda48af4a | ||
|
|
7688d7c619 | ||
|
|
76328b5c45 | ||
|
|
1de17bf06f | ||
|
|
6724daf995 | ||
|
|
7caca053a1 | ||
|
|
ae1bf8c017 | ||
|
|
d20b0da438 | ||
|
|
a0218d7df1 | ||
|
|
5ae5d67b91 | ||
|
|
8493ed7603 | ||
|
|
804b681d40 | ||
|
|
e8303bd059 | ||
|
|
ac6c5d4e32 | ||
|
|
90b0d0d52c | ||
|
|
4093bdbd3e | ||
|
|
a2097cf981 | ||
|
|
6a33d0c9dc | ||
|
|
525d5fb427 | ||
|
|
e4946b8cd5 | ||
|
|
76b32df622 | ||
|
|
bc1079364d | ||
|
|
8602febe9d | ||
|
|
d55dfe27dc | ||
|
|
90c16e2a07 | ||
|
|
149c1cd9b1 | ||
|
|
20f1a43369 | ||
|
|
e8fb8f993d | ||
|
|
f0c782dc01 | ||
|
|
50c13e6d20 | ||
|
|
69bedd035f | ||
|
|
85337f0a31 | ||
|
|
f8a7e2f98e | ||
|
|
ec90a49d43 | ||
|
|
5812b150c6 | ||
|
|
c7ebc7273f | ||
|
|
5226c87304 | ||
|
|
25dd1c5d35 | ||
|
|
c5a9e5e56d | ||
|
|
47ed70d671 | ||
|
|
cb5526f469 | ||
|
|
a4f128077f | ||
|
|
7140ba76d5 | ||
|
|
872e8f2de6 | ||
|
|
6c14e9d083 | ||
|
|
e4b1812b46 | ||
|
|
1c2c6bb1d0 | ||
|
|
baefd4f93b | ||
|
|
4270fe07ab | ||
|
|
e4ae925d2b | ||
|
|
dc599361a4 | ||
|
|
738a311f49 | ||
|
|
71f6ba3418 | ||
|
|
d1d573c408 | ||
|
|
50e39a4a75 | ||
|
|
8635fe7ebb | ||
|
|
6b57d4397a | ||
|
|
8f2b898b2b | ||
|
|
0d1d360d18 | ||
|
|
def3b3a155 | ||
|
|
d344512743 | ||
|
|
19eef71133 | ||
|
|
61d58a354e | ||
|
|
be868d37f2 | ||
|
|
20bb151cf3 | ||
|
|
77f889aba6 | ||
|
|
1e69a54972 | ||
|
|
6b7a47ca28 | ||
|
|
c3fdd3b5f7 | ||
|
|
e9f2121667 | ||
|
|
161e9e1e11 | ||
|
|
e336a45f79 | ||
|
|
9c09f93908 | ||
|
|
582398e7f6 | ||
|
|
b118635abd | ||
|
|
ac0d4a75b5 | ||
|
|
c212d5c5ea | ||
|
|
08ac27cccf | ||
|
|
0b5cab99cf | ||
|
|
cc0057cc56 | ||
|
|
1ce49b814b | ||
|
|
5bbaaece38 | ||
|
|
30bc4ccfa7 | ||
|
|
4f64f1d754 | ||
|
|
c0e578dd47 | ||
|
|
2b82fca2cf | ||
|
|
f0028b33e9 | ||
|
|
7ddea23375 | ||
|
|
83edccacc6 | ||
|
|
75e95d6452 | ||
|
|
423bb4bbcd | ||
|
|
43585c563c | ||
|
|
2564a41d05 | ||
|
|
a0bb1e3625 | ||
|
|
9b4fd57f51 | ||
|
|
e67709e339 | ||
|
|
0c4e913f30 | ||
|
|
c6de0e51c7 | ||
|
|
69e85adadf | ||
|
|
b34068207f | ||
|
|
68b7b4b3a4 | ||
|
|
5e3ee30e66 | ||
|
|
aaf7d12b46 | ||
|
|
729a348657 | ||
|
|
0fca6eb810 | ||
|
|
5a0ae8530c | ||
|
|
7949c9ad74 | ||
|
|
6fb9362f7e | ||
|
|
3481d364cc | ||
|
|
373b9cdd9f | ||
|
|
75af63e6ac | ||
|
|
5aa62a1be4 | ||
|
|
aede8bf0e0 | ||
|
|
9ab7abcb95 | ||
|
|
f87b28afd9 | ||
|
|
8661f6d1ac | ||
|
|
4536b4b2b4 | ||
|
|
655f03940b | ||
|
|
4122de7823 | ||
|
|
0f4c67d24e | ||
|
|
20e8c45819 | ||
|
|
2b8b844fb2 | ||
|
|
3284b8764f | ||
|
|
d19946336e | ||
|
|
770a220808 | ||
|
|
78b71e72f1 | ||
|
|
19990f49b0 | ||
|
|
8208d44466 | ||
|
|
002b2b6dee | ||
|
|
c207167b14 | ||
|
|
cfc066e911 | ||
|
|
3a1d011841 | ||
|
|
7d05c0da9c | ||
|
|
1d7f2ca9e4 | ||
|
|
ea2e0d7546 | ||
|
|
64b79ee64c | ||
|
|
8a00101470 | ||
|
|
01aba73f5b | ||
|
|
71e31346e8 | ||
|
|
483cce9880 | ||
|
|
c8db39a91e | ||
|
|
6d398a2edf | ||
|
|
bd3c8119ba | ||
|
|
16aa78d13c | ||
|
|
3be5cca60a | ||
|
|
bc3dfb96fd | ||
|
|
e78e98a6cf | ||
|
|
9db0e48f63 | ||
|
|
3de52b6bc1 | ||
|
|
be52abbe3b | ||
|
|
ac55b0fafb | ||
|
|
887b6789fc | ||
|
|
ff50fec112 | ||
|
|
4538ef3cf9 | ||
|
|
a872cf7061 | ||
|
|
2d8ca363db | ||
|
|
8e8b011587 | ||
|
|
4241ae035e | ||
|
|
3ef569d280 | ||
|
|
6fe28b15df | ||
|
|
a609a47138 | ||
|
|
b575b87f77 | ||
|
|
7c5ee8a67d | ||
|
|
452c14bece | ||
|
|
57f63ba752 | ||
|
|
5f153b8a01 | ||
|
|
1be49876df | ||
|
|
a79b2a7773 | ||
|
|
cdf6e5a487 | ||
|
|
7c82f45604 | ||
|
|
4d49701203 | ||
|
|
d48cc69898 | ||
|
|
af466a1d75 | ||
|
|
b9599d3aa1 | ||
|
|
dbebfe7c07 | ||
|
|
ddf54fdb83 | ||
|
|
619138d294 | ||
|
|
126b19bf2d | ||
|
|
cc76adf7b6 | ||
|
|
83bcb56a6a | ||
|
|
6e88a70661 | ||
|
|
6755a9878b | ||
|
|
b8ef7593ee | ||
|
|
602cc26f0f | ||
|
|
62271fe064 | ||
|
|
83f5f776a6 | ||
|
|
2a5566a820 | ||
|
|
398e547d06 | ||
|
|
ba957196da | ||
|
|
b5c4a24133 | ||
|
|
cc688dc112 | ||
|
|
91b5eaff80 | ||
|
|
4a52503cb3 | ||
|
|
bcd7e7ea94 | ||
|
|
ba9ae54fbb | ||
|
|
39e05c9991 | ||
|
|
8962f90bcc | ||
|
|
daf3a95db0 | ||
|
|
1c9ebafe2b | ||
|
|
00b3df4455 | ||
|
|
600c3e75bb | ||
|
|
6349fccd0f | ||
|
|
6ececdad26 | ||
|
|
c67f1a7b93 | ||
|
|
8617ea760a | ||
|
|
41a2406f07 | ||
|
|
adae8e45a9 | ||
|
|
e346ae533d | ||
|
|
31789255c9 | ||
|
|
dbe6edd133 | ||
|
|
7cfbcec56e | ||
|
|
9f9a055f64 | ||
|
|
d3614d3505 | ||
|
|
800f67908e | ||
|
|
e2c613c422 | ||
|
|
457037ed99 | ||
|
|
f9f21efd36 | ||
|
|
2d59b6718d | ||
|
|
0c6d213296 | ||
|
|
c34fb7f037 | ||
|
|
796be319b7 | ||
|
|
d28fcdc6a5 | ||
|
|
d0afcb6cfa | ||
|
|
7bd4de937a | ||
|
|
3025693178 | ||
|
|
c9cc3bf3ff | ||
|
|
1f670f7a05 | ||
|
|
48d735b53b | ||
|
|
b91f6c7ce6 | ||
|
|
ad116d1959 | ||
|
|
a0de10870d | ||
|
|
eb0c00896f | ||
|
|
deccd4e9fe | ||
|
|
8be4ec08ad | ||
|
|
59ad0624f2 | ||
|
|
f0c69ca84f | ||
|
|
3ba1c07f68 | ||
|
|
14cd4aaac8 | ||
|
|
8a1fae5d9d | ||
|
|
e323f5a2d5 | ||
|
|
c5c1cbd66f | ||
|
|
4cc9dbbe6a | ||
|
|
3649991ad6 | ||
|
|
1d25691aa2 | ||
|
|
235076b465 | ||
|
|
c2670fa379 | ||
|
|
a769a5391d | ||
|
|
1f58c46f67 | ||
|
|
f4c9f2d0e7 | ||
|
|
851b9136fe | ||
|
|
0fe10e470d | ||
|
|
8c8ea17fac | ||
|
|
7c546b8d3a | ||
|
|
63334a61ad | ||
|
|
f61e65cf54 | ||
|
|
05bf752629 | ||
|
|
5096a90e34 | ||
|
|
03792b3905 | ||
|
|
995b049a5f | ||
|
|
bde37ec2c7 | ||
|
|
d6b3fe7e1b | ||
|
|
954b394987 | ||
|
|
97dae6dde5 | ||
|
|
fe039500d6 | ||
|
|
6952957794 | ||
|
|
01cc97ad55 | ||
|
|
b5c8e005e2 | ||
|
|
1c2602438f | ||
|
|
33da756a2f | ||
|
|
488d4a38b8 | ||
|
|
e60f60b0f8 | ||
|
|
8aa2e3d2f5 | ||
|
|
d5f65e5d07 | ||
|
|
c8511a6e2a | ||
|
|
379b15be1d | ||
|
|
2ee1fea293 | ||
|
|
4385d71c6f | ||
|
|
cf6ea64aba | ||
|
|
101317cb16 | ||
|
|
5990a21c46 | ||
|
|
a9bc007327 | ||
|
|
0c71770b1d | ||
|
|
5bae7e9bdb | ||
|
|
1818a596fe | ||
|
|
8f7541b841 | ||
|
|
090546cda3 | ||
|
|
dcd89d38e7 | ||
|
|
800478d437 | ||
|
|
f797344106 | ||
|
|
9352ee3e25 | ||
|
|
811026dc4a | ||
|
|
479a4dac7b | ||
|
|
499fbbeb17 | ||
|
|
a35bcf6415 | ||
|
|
818ffdfc85 | ||
|
|
d5e19c7ac0 | ||
|
|
37639b0ff4 | ||
|
|
740d89dce6 | ||
|
|
4a7b08fc4e | ||
|
|
48a5f83f00 | ||
|
|
48819c928d | ||
|
|
45a6866dd0 | ||
|
|
6690586406 | ||
|
|
909e54845c | ||
|
|
a7204eb9fa | ||
|
|
6463166c00 | ||
|
|
f8268a864b | ||
|
|
721fff29b3 | ||
|
|
4cf312d3d4 | ||
|
|
36f1b6a834 | ||
|
|
050d7e8f00 | ||
|
|
7c5bed2bb5 | ||
|
|
87316cf7c1 | ||
|
|
f7d61e5a9b | ||
|
|
b2030a72a0 | ||
|
|
533797fc9e | ||
|
|
5688234b9d | ||
|
|
9335789362 | ||
|
|
9e6a2a3fa4 | ||
|
|
122fc77357 | ||
|
|
c978e7965f | ||
|
|
b0e4e24603 | ||
|
|
56de307a3e | ||
|
|
e1dd9ed41b | ||
|
|
17a64764d3 | ||
|
|
3cd0540474 | ||
|
|
27cd9fac8a | ||
|
|
1d2012cc23 | ||
|
|
1b00835dd1 | ||
|
|
413dcf8164 | ||
|
|
7f17e8fb2f | ||
|
|
254d8994d0 | ||
|
|
4f72519ad9 | ||
|
|
900b246183 | ||
|
|
abddb29f37 | ||
|
|
8d429ef753 | ||
|
|
b7679b5c60 | ||
|
|
49982d6eb1 | ||
|
|
3191a6c12b | ||
|
|
32f8747f2e | ||
|
|
38e45a62cf | ||
|
|
c0e2e78780 | ||
|
|
3fe3ddbc49 | ||
|
|
5ca532a54a | ||
|
|
a120df090a | ||
|
|
22d359503a | ||
|
|
e8d84abe43 | ||
|
|
98937cedaa | ||
|
|
d592d6cd7a | ||
|
|
0341a04ee3 | ||
|
|
540fc4f924 | ||
|
|
04290bf9b6 | ||
|
|
ecbc0c1778 | ||
|
|
44b8e48c3a | ||
|
|
a5036c86dc | ||
|
|
ac86e75233 | ||
|
|
9ec3febbfa | ||
|
|
1c5dc6ab6d | ||
|
|
abb8eafec2 | ||
|
|
eb8f5512c5 | ||
|
|
d146476c91 | ||
|
|
7a57670925 | ||
|
|
8a49e98246 | ||
|
|
cf0845d190 | ||
|
|
02bbdcc251 | ||
|
|
13f6bd759b | ||
|
|
497400587d | ||
|
|
a58cd83ea7 | ||
|
|
3f802fe27a | ||
|
|
6a13dd317d | ||
|
|
a442d3d952 | ||
|
|
0d4febff85 | ||
|
|
ba222eaf77 | ||
|
|
b14719464c | ||
|
|
c756b80962 | ||
|
|
a54a886bf0 | ||
|
|
dbe9628cc5 | ||
|
|
7a3b39886e | ||
|
|
fab511cc53 | ||
|
|
4979d9d0bf | ||
|
|
45914b2e9e | ||
|
|
1e9aaf2d2a | ||
|
|
de56c18c6e | ||
|
|
eaefb7136a | ||
|
|
fe9344cd0a | ||
|
|
f010c17ae6 | ||
|
|
f63cd74965 | ||
|
|
9e8f8f76a4 | ||
|
|
d88c6a82d0 | ||
|
|
a8fdf7ffad | ||
|
|
245389d74f | ||
|
|
26933637dd | ||
|
|
98312ac554 | ||
|
|
1ba03088c9 | ||
|
|
c0dfc554b3 | ||
|
|
5c691491e8 | ||
|
|
9731b59174 | ||
|
|
52bf358978 | ||
|
|
c92a56c980 | ||
|
|
3142151fc3 | ||
|
|
fb555f5b96 | ||
|
|
8f1c693d3d | ||
|
|
b8a8becd0c | ||
|
|
b71abd3f6a | ||
|
|
9ae74b4278 | ||
|
|
bdbf434006 | ||
|
|
1a5e93c739 | ||
|
|
8e42ba74c6 | ||
|
|
42bb083e99 | ||
|
|
ae4eecc7f2 | ||
|
|
c4f25b6191 | ||
|
|
29b200040f | ||
|
|
7cb1598fb1 | ||
|
|
65b6f162d8 | ||
|
|
c56d2e08f4 | ||
|
|
ca0a0886b1 | ||
|
|
e9822ae1a3 | ||
|
|
04b284f030 | ||
|
|
9ef24c0a43 | ||
|
|
7ee650ba7a | ||
|
|
96cafed154 | ||
|
|
f65c2ff4fb | ||
|
|
121deec62f | ||
|
|
838d0808c0 | ||
|
|
974fbe9e5b | ||
|
|
f26f94ad3b | ||
|
|
7410f1944c | ||
|
|
c34947f657 | ||
|
|
54092118e1 | ||
|
|
866a7d7401 | ||
|
|
32ab916707 | ||
|
|
1a245f1303 | ||
|
|
a23c61ee3c | ||
|
|
f44336f7aa | ||
|
|
98d4bc48b6 | ||
|
|
a3f1b72bac | ||
|
|
a37f70947b | ||
|
|
71195aa789 | ||
|
|
f6511bed32 | ||
|
|
619500ca64 | ||
|
|
986d290434 | ||
|
|
878f8c58bb | ||
|
|
e067da1fe9 | ||
|
|
f340c636fe | ||
|
|
ce260a1a1e | ||
|
|
a21c9f15e3 | ||
|
|
e64b778d13 | ||
|
|
a1f139f62a | ||
|
|
8ae1d1c963 | ||
|
|
8f8016179b | ||
|
|
2e32e994c3 | ||
|
|
1575e3b045 | ||
|
|
9ab5f68601 | ||
|
|
7fcb806dfe | ||
|
|
5ae736c7cc | ||
|
|
d77ba9970b | ||
|
|
49f97a2c7b | ||
|
|
659ff89062 | ||
|
|
5529641bea | ||
|
|
b38f1d7b2a | ||
|
|
53ba202b14 | ||
|
|
11cc333de7 | ||
|
|
70e47ab4d0 | ||
|
|
1582b35ae2 | ||
|
|
62c27cee6c | ||
|
|
81b8bc9e93 | ||
|
|
49758c4e72 | ||
|
|
001ef4fe1c | ||
|
|
94d0401f4e | ||
|
|
2dbd9bd0b1 | ||
|
|
9168c97eb6 | ||
|
|
758953b6e3 | ||
|
|
8ccdf9ea83 | ||
|
|
9c6a3e4ad5 | ||
|
|
6151d4a0ec | ||
|
|
61014d45f4 | ||
|
|
05a93a2426 | ||
|
|
a4c7412220 | ||
|
|
94e51952f4 | ||
|
|
ebdd64f46f | ||
|
|
2dc70ece44 | ||
|
|
c23ea5ea76 | ||
|
|
6521a7c604 | ||
|
|
02e792148c | ||
|
|
69c350dcca | ||
|
|
1aee3d8e2c | ||
|
|
02695d852c | ||
|
|
7405138489 | ||
|
|
4804257fd1 | ||
|
|
67f2e3a32a | ||
|
|
8d709f9cf4 | ||
|
|
4d3132f1c9 | ||
|
|
36b44f1814 | ||
|
|
32761aeda0 | ||
|
|
851b05c110 | ||
|
|
997e951aca | ||
|
|
448dc6b7c6 | ||
|
|
84458fa46f | ||
|
|
50bb8a0d91 | ||
|
|
997b3c3061 | ||
|
|
4f240c004c | ||
|
|
597a8d36af | ||
|
|
cf52a4c5c2 | ||
|
|
c29180a094 | ||
|
|
10f4304559 | ||
|
|
30447bcf70 | ||
|
|
9ff9385c47 | ||
|
|
6c5499e848 | ||
|
|
3bacbe8536 | ||
|
|
09c7a69050 | ||
|
|
5dc727580f | ||
|
|
248a4ed527 | ||
|
|
db95185eee | ||
|
|
85dae15a0d | ||
|
|
3e61a1e12b | ||
|
|
6cd4186ac9 | ||
|
|
cbbadc3d6d | ||
|
|
fc0024faa2 | ||
|
|
0f3d4062d7 | ||
|
|
7ba8a88989 | ||
|
|
349d254193 | ||
|
|
be201e811d | ||
|
|
84a032fbb4 | ||
|
|
4815602558 | ||
|
|
e4b83392be | ||
|
|
0658c17adb | ||
|
|
bdc72aee42 | ||
|
|
689d91e30f | ||
|
|
6b785e4318 | ||
|
|
f46cf55912 | ||
|
|
d520849ce1 | ||
|
|
50661bbb3b | ||
|
|
d2d5b1ac76 | ||
|
|
244972e0f8 | ||
|
|
f80e6c2efa | ||
|
|
e9e32eda3c | ||
|
|
73844e223f | ||
|
|
6583a6d9c6 | ||
|
|
ca4824adcd | ||
|
|
80b5cc08bb | ||
|
|
afbcc79a06 | ||
|
|
3371bd2e04 | ||
|
|
5efdf53c06 | ||
|
|
c9112de8ba | ||
|
|
fd4b589a13 | ||
|
|
df813dbac9 | ||
|
|
004fb362ec | ||
|
|
3cd749753a | ||
|
|
c7964f7693 | ||
|
|
57bba2fd3f | ||
|
|
04c9b2a7a8 | ||
|
|
b9d142c2b7 | ||
|
|
6ab52e282f | ||
|
|
b14adf8c3f | ||
|
|
4e0b162f5f | ||
|
|
62d47ff7f0 | ||
|
|
7f025380f0 | ||
|
|
7d1e981bca | ||
|
|
a08103f996 | ||
|
|
dd4991a4f8 | ||
|
|
5442292d23 | ||
|
|
3f050d3d03 | ||
|
|
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 | ||
|
|
0a6f299ae6 | ||
|
|
73f87e30c2 | ||
|
|
838ece2c89 | ||
|
|
d8b88ea2c0 | ||
|
|
5908951b75 | ||
|
|
0b41f4c4d2 | ||
|
|
35439d4fbc | ||
|
|
fdce40310f | ||
|
|
6b4785ae32 | ||
|
|
f74e8e9cb7 | ||
|
|
5a4eb7e09e | ||
|
|
3bc4df03cc | ||
|
|
5d585132fb | ||
|
|
eff4905883 | ||
|
|
8923ac4fe3 | ||
|
|
073535e5ed | ||
|
|
d304b90ca6 | ||
|
|
816c26e14e | ||
|
|
b1244ffa01 | ||
|
|
fc1342bff9 | ||
|
|
d7b95194b5 | ||
|
|
b58bdeccd2 | ||
|
|
f260b9bdee | ||
|
|
fb1bdc9ec5 | ||
|
|
697eff48fc | ||
|
|
c05019339a | ||
|
|
8438efaf41 | ||
|
|
81c019cc99 | ||
|
|
c773fdc435 | ||
|
|
c1406f51f1 | ||
|
|
92affd3440 | ||
|
|
d3da0652ef | ||
|
|
e3fbbd6cf1 | ||
|
|
fcff13470c | ||
|
|
e3061ee7e7 | ||
|
|
0ee305fc4a | ||
|
|
58b93fd0c4 | ||
|
|
b30217fa2d | ||
|
|
ae48eec3a2 | ||
|
|
948233ba27 | ||
|
|
c2db9b183a | ||
|
|
6d2b88fa0b | ||
|
|
1d5da825c5 | ||
|
|
330c9b53d6 | ||
|
|
751fe7d4fb | ||
|
|
9df1fc6e5d | ||
|
|
8d660f1701 | ||
|
|
4d61d3c4aa | ||
|
|
0457088c99 | ||
|
|
8e575da74e | ||
|
|
48ed28888e | ||
|
|
4084b1124e | ||
|
|
60ba607027 | ||
|
|
3df2c11b4a | ||
|
|
c93221923a | ||
|
|
375317e932 | ||
|
|
7ce527957a | ||
|
|
6946521199 | ||
|
|
18ee20e680 | ||
|
|
c53da15219 | ||
|
|
d4995e342f | ||
|
|
c9f14da294 | ||
|
|
e9c2446cba | ||
|
|
35f179625c | ||
|
|
39749aa113 | ||
|
|
ba65e982fd | ||
|
|
b50e5d7e59 | ||
|
|
a3148dc172 | ||
|
|
73f1491d2d | ||
|
|
28eb54dc96 | ||
|
|
21fb426524 | ||
|
|
d5710ca809 | ||
|
|
0ba6cdda17 | ||
|
|
afdcfa8525 | ||
|
|
5db4f8512b | ||
|
|
dc0c1b73bc | ||
|
|
f999257095 | ||
|
|
7182909e28 | ||
|
|
fe3f015171 | ||
|
|
5bb668be63 | ||
|
|
01de147900 | ||
|
|
a7e5fcc806 | ||
|
|
e2d187d74b | ||
|
|
48b0620629 | ||
|
|
19e9f382e4 | ||
|
|
446eaf6588 | ||
|
|
78deb1420d | ||
|
|
e092515dff | ||
|
|
81f6fef978 | ||
|
|
6a2f8fa9ee | ||
|
|
a79a8c8874 | ||
|
|
c39659b064 | ||
|
|
9a30fbd05a | ||
|
|
83f48418f6 | ||
|
|
bcd7b41c91 | ||
|
|
cefb7d12bc | ||
|
|
3c0c15103e | ||
|
|
a8a8afc2be | ||
|
|
49e32abd3f | ||
|
|
7977eefaca | ||
|
|
f1fa6c3108 |
11
.env.example
11
.env.example
@@ -35,13 +35,20 @@ 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
|
||||||
|
|
||||||
|
GOOGLE_MAPS_API_KEY=
|
||||||
ANALYTICS_ID=
|
ANALYTICS_ID=
|
||||||
SITE_OWNER=mail@example.com
|
SITE_OWNER=mail@example.com
|
||||||
|
USE_ENCRYPTION=true
|
||||||
|
|
||||||
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=
|
||||||
7
.github/CONTRIBUTING.md
vendored
Normal file
7
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Hi there!
|
||||||
|
|
||||||
|
Thank you for taking the time to report an issue or requesting a new feature.
|
||||||
|
|
||||||
|
Please take note that there are NO rules or regulations when you submit an issue.
|
||||||
|
|
||||||
|
If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/).
|
||||||
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
|
||||||
|
|||||||
@@ -2,7 +2,50 @@
|
|||||||
tools:
|
tools:
|
||||||
external_code_coverage: false
|
external_code_coverage: false
|
||||||
filter:
|
filter:
|
||||||
excluded_paths:
|
paths:
|
||||||
- app/Support/Migration/*
|
- app/*
|
||||||
- app/database/migrations/*
|
- public/js/ff/*
|
||||||
- database/migrations/*
|
excluded_paths:
|
||||||
|
- "database/migrations/*"
|
||||||
|
- "bootstrap/*"
|
||||||
|
- "config/*"
|
||||||
|
- "docker/*"
|
||||||
|
- "public/js/lib/*"
|
||||||
|
- "public/lib/adminlte/js/*"
|
||||||
|
- "public/lib/bootstrap/js/*"
|
||||||
|
- "resources/*"
|
||||||
|
- "routes/*"
|
||||||
|
- "storage/*"
|
||||||
|
checks:
|
||||||
|
php:
|
||||||
|
use_self_instead_of_fqcn: true
|
||||||
|
uppercase_constants: true
|
||||||
|
return_doc_comments: true
|
||||||
|
return_doc_comment_if_not_inferrable: true
|
||||||
|
remove_extra_empty_lines: true
|
||||||
|
parameter_doc_comments: true
|
||||||
|
optional_parameters_at_the_end: true
|
||||||
|
no_short_variable_names:
|
||||||
|
minimum: '3'
|
||||||
|
no_short_method_names:
|
||||||
|
minimum: '3'
|
||||||
|
no_long_variable_names:
|
||||||
|
maximum: '20'
|
||||||
|
no_goto: true
|
||||||
|
newline_at_end_of_file: true
|
||||||
|
encourage_single_quotes: true
|
||||||
|
avoid_todo_comments: true
|
||||||
|
avoid_perl_style_comments: true
|
||||||
|
avoid_fixme_comments: true
|
||||||
|
avoid_multiple_statements_on_same_line: true
|
||||||
|
align_assignments: true
|
||||||
|
duplication: false
|
||||||
|
javascript: true
|
||||||
|
|
||||||
|
coding_style:
|
||||||
|
php:
|
||||||
|
spaces:
|
||||||
|
around_operators:
|
||||||
|
concatenation: true
|
||||||
|
other:
|
||||||
|
after_type_cast: false
|
||||||
23
.travis.yml
Normal file
23
.travis.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
language: php
|
||||||
|
php:
|
||||||
|
- 7.0
|
||||||
|
- 7.1
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- vendor
|
||||||
|
- $HOME/.composer/cache
|
||||||
|
|
||||||
|
install:
|
||||||
|
- if [[ "$(php -v | grep 'PHP 7')" ]]; then phpenv config-rm xdebug.ini; fi
|
||||||
|
- rm composer.lock
|
||||||
|
- composer update --no-scripts
|
||||||
|
- cp .env.testing .env
|
||||||
|
- 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
|
||||||
226
CHANGELOG.md
226
CHANGELOG.md
@@ -2,7 +2,225 @@
|
|||||||
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.3] - 2017-01-30
|
||||||
|
|
||||||
|
_The 100th release of Firefly!_
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Add locales to Docker (#534) by @elohmeier.
|
||||||
|
- Optional database encryption. On by default.
|
||||||
|
- Datepicker for Firefox and other browsers.
|
||||||
|
- New instruction block for updating and installing.
|
||||||
|
- Ability to clone transactions.
|
||||||
|
- Use multi-select Bootstrap thing instead of massive lists of checkboxes.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Lots of old Javascript
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Missing sort broke various charts
|
||||||
|
- Bug in reports that made amounts behave weird
|
||||||
|
- Various bug fixes
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Tested FF against the naughty string list.
|
||||||
|
|
||||||
|
## [4.3.2] - 2017-01-09
|
||||||
|
|
||||||
|
An intermediate release because something in the Twig and Twigbridge libraries is broken and I have to make sure it doesn't affect you guys. But some cool features were on their way so there's that oo.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Some code for issue #475, consistent overviews.
|
||||||
|
- Better currency display. Make sure you have locale packages installed.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Uses a new version of Laravel.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- The password reset routine was broken.
|
||||||
|
- Issue #522, thanks to @xpfgsyb
|
||||||
|
- Issue #524, thanks to @worldworm
|
||||||
|
- Issue #526, thanks to @worldworm
|
||||||
|
- Issue #528, thanks to @skibbipl
|
||||||
|
- Various other fixes.
|
||||||
|
|
||||||
|
## [4.3.1] - 2017-01-04
|
||||||
|
### Added
|
||||||
|
- Support for Russian and Polish.
|
||||||
|
- Support for a proper demo website.
|
||||||
|
- Support for custom decimal places in currencies (#506, suggested by @xpfgsyb).
|
||||||
|
- Most amounts are now right-aligned (#511, suggested by @xpfgsyb).
|
||||||
|
- German is now a "complete" language, more than 75% translated!
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **[New Github repository!](github.com/firefly-iii/firefly-iii)**
|
||||||
|
- Better category overview.
|
||||||
|
- #502, thanks to @zjean
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed a lot of administration functions.
|
||||||
|
- Removed ability to activate users.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- #501, thanks to @zjean
|
||||||
|
- #513, thanks to @skibbipl
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- #519, thanks to @xpfgsyb
|
||||||
|
|
||||||
|
## [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
|
||||||
|
### Added
|
||||||
|
- New Dockerfile thanks to @schoentoon
|
||||||
|
- Added changing the destination account as rule action.
|
||||||
|
- Added changing the source account as rule action.
|
||||||
|
- Can convert transactions into different types.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changed the export routine to be more future-proof.
|
||||||
|
- Improved help routine.
|
||||||
|
- Integrated CrowdIn translations.
|
||||||
|
- Simplified reports
|
||||||
|
- Change error message to refer to solution.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- #367 thanks to @HungryFeline
|
||||||
|
- #366 thanks to @3mz3t
|
||||||
|
- #362 and #341 thanks to @bnw
|
||||||
|
- #355 thanks to @roberthorlings
|
||||||
|
|
||||||
|
## [4.1.3] - 2016-10-22
|
||||||
|
### Fixed
|
||||||
|
- Some event handlers called the wrong method.
|
||||||
|
|
||||||
|
## [4.1.2] - 2016-10-22
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- A bug is fixed in the journal event handler that prevented Firefly III from actually storing journals.
|
||||||
|
|
||||||
## [4.1.1] - 2016-10-22
|
## [4.1.1] - 2016-10-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Option to show deposit accounts on the front page.
|
- Option to show deposit accounts on the front page.
|
||||||
- Script to upgrade split transactions
|
- Script to upgrade split transactions
|
||||||
@@ -126,7 +344,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Bug in the mass edit routines.
|
- Bug in the mass edit routines.
|
||||||
- Firefly III over a proxy will now work (see [issue #290](https://github.com/JC5/firefly-iii/issues/290)), thanks @dfiel for reporting.
|
- Firefly III over a proxy will now work (see [issue #290](https://github.com/firefly-iii/firefly-iii/issues/290)), thanks @dfiel for reporting.
|
||||||
- Sneaky bug in the import routine, fixed by @Bonno
|
- Sneaky bug in the import routine, fixed by @Bonno
|
||||||
|
|
||||||
## [3.10.1] - 2016-08-25
|
## [3.10.1] - 2016-08-25
|
||||||
@@ -147,7 +365,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Fixed a bug where a migration would check an empty table name.
|
- Fixed a bug where a migration would check an empty table name.
|
||||||
- Fixed various bugs in the import routine.
|
- Fixed various bugs in the import routine.
|
||||||
- Fixed various bugs in the piggy banks pages.
|
- Fixed various bugs in the piggy banks pages.
|
||||||
- Fixed a bug in the ``firefly:verify`` routine
|
- Fixed a bug in the `firefly:verify` routine
|
||||||
|
|
||||||
## [3.10] - 2015-05-25
|
## [3.10] - 2015-05-25
|
||||||
### Added
|
### Added
|
||||||
@@ -176,11 +394,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Bulk update problems, #280, thanks @stickgrinder
|
- Bulk update problems, #280, thanks @stickgrinder
|
||||||
- Fixed various problems with amount reporting of split transactions.
|
- Fixed various problems with amount reporting of split transactions.
|
||||||
|
|
||||||
[3.9.1]
|
## [3.9.1]
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed a bug where removing money from a piggy bank would not work. See issue #265 and #269
|
- Fixed a bug where removing money from a piggy bank would not work. See issue #265 and #269
|
||||||
|
|
||||||
[3.9.0]
|
## [3.9.0]
|
||||||
### Added
|
### Added
|
||||||
- @zjean has added code that allows you to force "https://"-URL's.
|
- @zjean has added code that allows you to force "https://"-URL's.
|
||||||
- @tonicospinelli has added Portuguese (Brazil) translations.
|
- @tonicospinelli has added Portuguese (Brazil) translations.
|
||||||
|
|||||||
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
FROM php:7-apache
|
||||||
|
|
||||||
|
RUN apt-get update -y && \
|
||||||
|
apt-get install -y --no-install-recommends libcurl4-openssl-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
libjpeg62-turbo-dev \
|
||||||
|
libpng12-dev \
|
||||||
|
libicu-dev \
|
||||||
|
libmcrypt-dev \
|
||||||
|
libedit-dev \
|
||||||
|
libtidy-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
libbz2-dev \
|
||||||
|
locales && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2
|
||||||
|
|
||||||
|
# Generate locales supported by firefly
|
||||||
|
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
||||||
|
|
||||||
|
# Enable apache mod rewrite..
|
||||||
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
|
# Setup the Composer installer
|
||||||
|
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer && \
|
||||||
|
curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig && \
|
||||||
|
php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" && \
|
||||||
|
chmod +x /tmp/composer-setup.php && \
|
||||||
|
php /tmp/composer-setup.php && \
|
||||||
|
mv composer.phar /usr/local/bin/composer && \
|
||||||
|
rm -f /tmp/composer-setup.{php,sig}
|
||||||
|
|
||||||
|
ADD . /var/www/firefly-iii
|
||||||
|
RUN chown -R www-data:www-data /var/www/
|
||||||
|
ADD docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
|
||||||
|
|
||||||
|
USER www-data
|
||||||
|
|
||||||
|
WORKDIR /var/www/firefly-iii
|
||||||
|
|
||||||
|
RUN composer install --no-scripts --no-dev
|
||||||
|
|
||||||
|
USER root
|
||||||
18
README.md
18
README.md
@@ -1,16 +1,20 @@
|
|||||||
# 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/firefly-iii/firefly-iii/?branch=master) [](https://travis-ci.org/firefly-iii/firefly-iii) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||||
|
|
||||||
[](https://i.nder.be/hhfv03hp) [](https://i.nder.be/hhmwmqw9)
|
[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
|
||||||
|
|
||||||
[](https://i.nder.be/g63q05m0) [](https://i.nder.be/c2g30ngg)
|
[](https://i.nder.be/ccn0u2mp) [](https://i.nder.be/gm8hbh7z)
|
||||||
|
|
||||||
"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 +29,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');
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class EncryptFile extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'firefly:encrypt {file} {key}';
|
protected $signature = 'firefly:encrypt-file {file} {key}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new command instance.
|
* Create a new command instance.
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
$md5 = md5_file($tmpfname);
|
||||||
// get md5 and mime
|
$mime = mime_content_type($tmpfname);
|
||||||
$md5 = md5_file($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:
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,14 @@ namespace FireflyIII\Console\Commands;
|
|||||||
|
|
||||||
|
|
||||||
use DB;
|
use DB;
|
||||||
|
use FireflyIII\Models\BudgetLimit;
|
||||||
|
use FireflyIII\Models\LimitRepetition;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
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
|
||||||
@@ -56,8 +59,29 @@ class UpgradeDatabase extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->setTransactionIdentifier();
|
$this->setTransactionIdentifier();
|
||||||
|
$this->migrateRepetitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function migrateRepetitions()
|
||||||
|
{
|
||||||
|
if (!Schema::hasTable('budget_limits')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// get all budget limits with end_date NULL
|
||||||
|
$set = BudgetLimit::whereNull('end_date')->get();
|
||||||
|
$this->line(sprintf('Found %d budget limit(s) to update', $set->count()));
|
||||||
|
/** @var BudgetLimit $budgetLimit */
|
||||||
|
foreach ($set as $budgetLimit) {
|
||||||
|
// get limit repetition (should be just one):
|
||||||
|
/** @var LimitRepetition $repetition */
|
||||||
|
$repetition = $budgetLimit->limitrepetitions()->first();
|
||||||
|
if (!is_null($repetition)) {
|
||||||
|
$budgetLimit->end_date = $repetition->enddate;
|
||||||
|
$budgetLimit->save();
|
||||||
|
$this->line(sprintf('Updated budget limit #%d', $budgetLimit->id));
|
||||||
|
$repetition->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,12 +89,17 @@ 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')) {
|
||||||
->whereNull('transaction_journals.deleted_at')
|
return;
|
||||||
->whereNull('transactions.deleted_at')
|
}
|
||||||
->groupBy(['transaction_journals.id'])
|
|
||||||
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
|
|
||||||
|
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
|
->whereNull('transaction_journals.deleted_at')
|
||||||
|
->whereNull('transactions.deleted_at')
|
||||||
|
->groupBy(['transaction_journals.id'])
|
||||||
|
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
|
||||||
|
|
||||||
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
|
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
|
||||||
->mergeBindings($subQuery->getQuery())
|
->mergeBindings($subQuery->getQuery())
|
||||||
@@ -79,43 +108,52 @@ class UpgradeDatabase extends Command
|
|||||||
$journalIds = array_unique($result->pluck('id')->toArray());
|
$journalIds = array_unique($result->pluck('id')->toArray());
|
||||||
|
|
||||||
foreach ($journalIds as $journalId) {
|
foreach ($journalIds as $journalId) {
|
||||||
// grab all positive transactiosn from this journal that are not deleted.
|
$this->updateJournal(intval($journalId));
|
||||||
// for each one, grab the negative opposing one which has 0 as an identifier and give it the same identifier.
|
}
|
||||||
$identifier = 0;
|
}
|
||||||
$processed = [];
|
|
||||||
$transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get();
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($transactions as $transaction) {
|
|
||||||
// find opposing:
|
|
||||||
$amount = bcmul(strval($transaction->amount), '-1');
|
|
||||||
|
|
||||||
try {
|
/**
|
||||||
/** @var Transaction $opposing */
|
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
|
||||||
$opposing = Transaction
|
* which has 0 as an identifier and give it the same identifier.
|
||||||
::where('transaction_journal_id', $journalId)
|
*
|
||||||
->where('amount', $amount)->where('identifier', '=', 0)
|
* @param int $journalId
|
||||||
->whereNotIn('id', $processed)
|
*/
|
||||||
->first();
|
private function updateJournal(int $journalId)
|
||||||
} catch (QueryException $e) {
|
{
|
||||||
Log::error($e->getMessage());
|
$identifier = 0;
|
||||||
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
|
$processed = [];
|
||||||
$this->error('This field is required for Firefly III version ' . config('firefly.version') . ' to run.');
|
$transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get();
|
||||||
$this->error('Please run "php artisan migrate" to add this field to the table.');
|
/** @var Transaction $transaction */
|
||||||
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
|
foreach ($transactions as $transaction) {
|
||||||
break 2;
|
// find opposing:
|
||||||
}
|
$amount = bcmul(strval($transaction->amount), '-1');
|
||||||
if (!is_null($opposing)) {
|
|
||||||
// give both a new identifier:
|
try {
|
||||||
$transaction->identifier = $identifier;
|
/** @var Transaction $opposing */
|
||||||
$transaction->save();
|
$opposing = Transaction::where('transaction_journal_id', $journalId)
|
||||||
$opposing->identifier = $identifier;
|
->where('amount', $amount)->where('identifier', '=', 0)
|
||||||
$opposing->save();
|
->whereNotIn('id', $processed)
|
||||||
$processed[] = $transaction->id;
|
->first();
|
||||||
$processed[] = $opposing->id;
|
} catch (QueryException $e) {
|
||||||
$this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id));
|
Log::error($e->getMessage());
|
||||||
}
|
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
|
||||||
$identifier++;
|
$this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
|
||||||
|
$this->error('Please run "php artisan migrate" to add this field to the table.');
|
||||||
|
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (!is_null($opposing)) {
|
||||||
|
// give both a new identifier:
|
||||||
|
$transaction->identifier = $identifier;
|
||||||
|
$transaction->save();
|
||||||
|
$opposing->identifier = $identifier;
|
||||||
|
$opposing->save();
|
||||||
|
$processed[] = $transaction->id;
|
||||||
|
$processed[] = $opposing->id;
|
||||||
|
$this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id));
|
||||||
|
}
|
||||||
|
$identifier++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ class UpgradeFireflyInstructions extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'firefly:upgrade-instructions';
|
protected $signature = 'firefly:instructions {task}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new command instance.
|
* Create a new command instance.
|
||||||
@@ -49,11 +49,60 @@ class UpgradeFireflyInstructions extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
//
|
|
||||||
|
if ($this->argument('task') == 'update') {
|
||||||
|
$this->updateInstructions();
|
||||||
|
}
|
||||||
|
if ($this->argument('task') == 'install') {
|
||||||
|
$this->installInstructions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a nice box
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
*/
|
||||||
|
private function boxed(string $text)
|
||||||
|
{
|
||||||
|
$parts = explode("\n", wordwrap($text));
|
||||||
|
foreach ($parts as $string) {
|
||||||
|
$this->line('| ' . sprintf('%-77s', $string) . '|');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a nice info box
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
*/
|
||||||
|
private function boxedInfo(string $text)
|
||||||
|
{
|
||||||
|
$parts = explode("\n", wordwrap($text));
|
||||||
|
foreach ($parts as $string) {
|
||||||
|
$this->info('| ' . sprintf('%-77s', $string) . '|');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a line
|
||||||
|
*/
|
||||||
|
private function showLine()
|
||||||
|
{
|
||||||
|
$line = '+';
|
||||||
|
for ($i = 0; $i < 78; $i++) {
|
||||||
|
$line .= '-';
|
||||||
|
}
|
||||||
|
$line .= '+';
|
||||||
|
$this->line($line);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installInstructions() {
|
||||||
/** @var string $version */
|
/** @var string $version */
|
||||||
$version = config('firefly.version');
|
$version = config('firefly.version');
|
||||||
$config = config('upgrade.text');
|
$config = config('upgrade.text.install');
|
||||||
$text = null;
|
$text = '';
|
||||||
foreach (array_keys($config) as $compare) {
|
foreach (array_keys($config) as $compare) {
|
||||||
// if string starts with:
|
// if string starts with:
|
||||||
$len = strlen($compare);
|
$len = strlen($compare);
|
||||||
@@ -62,22 +111,53 @@ class UpgradeFireflyInstructions extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
$this->showLine();
|
||||||
$this->line('+------------------------------------------------------------------------------+');
|
$this->boxed('');
|
||||||
$this->line('');
|
|
||||||
|
|
||||||
if (is_null($text)) {
|
if (is_null($text)) {
|
||||||
$this->line('Thank you for installing Firefly III, v' . $version);
|
|
||||||
$this->info('There are no extra upgrade instructions.');
|
$this->boxed(sprintf('Thank you for installin Firefly III, v%s!', $version));
|
||||||
$this->line('Firefly III should be ready for use.');
|
$this->boxedInfo('There are no extra installation instructions.');
|
||||||
} else {
|
$this->boxed('Firefly III should be ready for use.');
|
||||||
$this->line('Thank you for installing Firefly III, v' . $version);
|
$this->boxed('');
|
||||||
$this->line('If you are upgrading from a previous version,');
|
$this->showLine();
|
||||||
$this->line('please follow these upgrade instructions carefully:');
|
return;
|
||||||
$this->info(wordwrap($text));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->line('');
|
$this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version));
|
||||||
$this->line('+------------------------------------------------------------------------------+');
|
$this->boxedInfo($text);
|
||||||
|
$this->boxed('');
|
||||||
|
$this->showLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateInstructions()
|
||||||
|
{
|
||||||
|
/** @var string $version */
|
||||||
|
$version = config('firefly.version');
|
||||||
|
$config = config('upgrade.text.upgrade');
|
||||||
|
$text = '';
|
||||||
|
foreach (array_keys($config) as $compare) {
|
||||||
|
// if string starts with:
|
||||||
|
$len = strlen($compare);
|
||||||
|
if (substr($version, 0, $len) === $compare) {
|
||||||
|
$text = $config[$compare];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
$this->showLine();
|
||||||
|
$this->boxed('');
|
||||||
|
if (is_null($text)) {
|
||||||
|
|
||||||
|
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version));
|
||||||
|
$this->boxedInfo('There are no extra upgrade instructions.');
|
||||||
|
$this->boxed('Firefly III should be ready for use.');
|
||||||
|
$this->boxed('');
|
||||||
|
$this->showLine();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s!', $version));
|
||||||
|
$this->boxedInfo($text);
|
||||||
|
$this->boxed('');
|
||||||
|
$this->showLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
app/Console/Commands/UseEncryption.php
Normal file
66
app/Console/Commands/UseEncryption.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FireflyIII\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class UseEncryption extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'This command will make sure that entries in the database will be encrypted (or not) according to the settings in .env';
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'firefly:use-encryption';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
$this->handleObjects('Account', 'name', 'encrypted');
|
||||||
|
$this->handleObjects('Bill', 'name', 'name_encrypted');
|
||||||
|
$this->handleObjects('Bill', 'match', 'match_encrypted');
|
||||||
|
$this->handleObjects('Budget', 'name', 'encrypted');
|
||||||
|
$this->handleObjects('Category', 'name', 'encrypted');
|
||||||
|
$this->handleObjects('PiggyBank', 'name', 'encrypted');
|
||||||
|
$this->handleObjects('TransactionJournal', 'description', 'encrypted');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $class
|
||||||
|
* @param string $field
|
||||||
|
* @param string $indicator
|
||||||
|
*/
|
||||||
|
public function handleObjects(string $class, string $field, string $indicator)
|
||||||
|
{
|
||||||
|
$fqn = sprintf('FireflyIII\Models\%s', $class);
|
||||||
|
$encrypt = config('firefly.encryption') ? 0 : 1;
|
||||||
|
$set = $fqn::where($indicator, $encrypt)->get();
|
||||||
|
|
||||||
|
foreach ($set as $entry) {
|
||||||
|
$newName = $entry->$field;
|
||||||
|
$entry->$field = $newName;
|
||||||
|
$entry->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line(sprintf('Updated %d %s.', $set->count(), strtolower(Str::plural($class))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,16 +15,17 @@ namespace FireflyIII\Console\Commands;
|
|||||||
|
|
||||||
use Crypt;
|
use Crypt;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,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:
|
||||||
@@ -84,6 +90,9 @@ class VerifyDatabase extends Command
|
|||||||
|
|
||||||
// transfers with budgets.
|
// transfers with budgets.
|
||||||
$this->reportTransfersBudgets();
|
$this->reportTransfersBudgets();
|
||||||
|
|
||||||
|
// report on journals with the wrong types of accounts.
|
||||||
|
$this->reportIncorrectJournals();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,14 +100,13 @@ 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')
|
->get(
|
||||||
->get(
|
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
|
||||||
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
|
);
|
||||||
);
|
|
||||||
|
|
||||||
/** @var stdClass $entry */
|
/** @var stdClass $entry */
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
@@ -114,59 +122,18 @@ 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.encrypted', 'budgets.user_id', 'users.email'])
|
||||||
->groupBy(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email'])
|
->whereNull('budget_limits.id')
|
||||||
->whereNull('budget_limits.id')
|
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']);
|
||||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', '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)
|
$line = sprintf(
|
||||||
. '") which has no budget limits.';
|
'Notice: User #%d (%s) has budget #%d ("%s") which has no budget limits.',
|
||||||
$this->line($line);
|
$entry->user_id, $entry->email, $entry->id, $entry->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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,22 +143,21 @@ 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')
|
->where(
|
||||||
->where(
|
function (Builder $q) {
|
||||||
function (Builder $q) {
|
$q->whereNull('transactions.deleted_at');
|
||||||
$q->whereNull('transactions.deleted_at');
|
$q->orWhereNull('transaction_journals.deleted_at');
|
||||||
$q->orWhereNull('transaction_journals.deleted_at');
|
}
|
||||||
}
|
)
|
||||||
)
|
->get(
|
||||||
->get(
|
['accounts.id as account_id', 'accounts.deleted_at as account_deleted_at', 'transactions.id as transaction_id',
|
||||||
['accounts.id as account_id', 'accounts.deleted_at as account_deleted_at', 'transactions.id as transaction_id',
|
'transactions.deleted_at as transaction_deleted_at', 'transaction_journals.id as journal_id',
|
||||||
'transactions.deleted_at as transaction_deleted_at', 'transaction_journals.id as journal_id',
|
'transaction_journals.deleted_at as journal_deleted_at']
|
||||||
'transaction_journals.deleted_at as journal_deleted_at']
|
);
|
||||||
);
|
|
||||||
/** @var stdClass $entry */
|
/** @var stdClass $entry */
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
$date = is_null($entry->transaction_deleted_at) ? $entry->journal_deleted_at : $entry->transaction_deleted_at;
|
$date = is_null($entry->transaction_deleted_at) ? $entry->journal_deleted_at : $entry->transaction_deleted_at;
|
||||||
@@ -202,24 +168,62 @@ class VerifyDatabase extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function reportIncorrectJournals()
|
||||||
|
{
|
||||||
|
$configuration = [
|
||||||
|
// a withdrawal can not have revenue account:
|
||||||
|
TransactionType::WITHDRAWAL => [AccountType::REVENUE],
|
||||||
|
// deposit cannot have an expense account:
|
||||||
|
TransactionType::DEPOSIT => [AccountType::EXPENSE],
|
||||||
|
// transfer cannot have either:
|
||||||
|
TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE],
|
||||||
|
];
|
||||||
|
foreach ($configuration as $transactionType => $accountTypes) {
|
||||||
|
$set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||||
|
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
|
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||||
|
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
|
||||||
|
->leftJoin('users', 'users.id', '=', 'transaction_journals.user_id')
|
||||||
|
->where('transaction_types.type', $transactionType)
|
||||||
|
->whereIn('account_types.type', $accountTypes)
|
||||||
|
->whereNull('transaction_journals.deleted_at')
|
||||||
|
->get(
|
||||||
|
['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type',
|
||||||
|
'transaction_types.type']
|
||||||
|
);
|
||||||
|
foreach ($set as $entry) {
|
||||||
|
$this->error(
|
||||||
|
sprintf(
|
||||||
|
'Transaction journal #%d (user #%d, %s) is of type "%s" but ' .
|
||||||
|
'is linked to a "%s". The transaction journal should be recreated.',
|
||||||
|
$entry->id,
|
||||||
|
$entry->user_id,
|
||||||
|
$entry->email,
|
||||||
|
$entry->type,
|
||||||
|
$entry->a_type
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any deleted transaction journals that have transactions that are NOT deleted:
|
* Any deleted transaction journals that have transactions that are NOT deleted:
|
||||||
*/
|
*/
|
||||||
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')
|
->get(
|
||||||
->get(
|
[
|
||||||
[
|
'transaction_journals.id as journal_id',
|
||||||
'transaction_journals.id as journal_id',
|
'transaction_journals.description',
|
||||||
'transaction_journals.description',
|
'transaction_journals.deleted_at as journal_deleted',
|
||||||
'transaction_journals.deleted_at as journal_deleted',
|
'transactions.id as transaction_id',
|
||||||
'transactions.id as transaction_id',
|
'transactions.deleted_at as transaction_deleted_at']
|
||||||
'transactions.deleted_at as transaction_deleted_at']
|
);
|
||||||
);
|
|
||||||
/** @var stdClass $entry */
|
/** @var stdClass $entry */
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
$this->error(
|
$this->error(
|
||||||
@@ -234,11 +238,10 @@ 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']);
|
|
||||||
|
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
$this->error(
|
$this->error(
|
||||||
@@ -248,6 +251,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.
|
||||||
*/
|
*/
|
||||||
@@ -265,40 +301,18 @@ 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(
|
['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id',
|
||||||
['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id',
|
'transaction_journals.deleted_at']
|
||||||
'transaction_journals.deleted_at']
|
);
|
||||||
);
|
|
||||||
/** @var stdClass $entry */
|
/** @var stdClass $entry */
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
$this->error(
|
$this->error(
|
||||||
@@ -313,12 +327,11 @@ 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)
|
->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.id']);
|
||||||
->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.id']);
|
|
||||||
|
|
||||||
/** @var TransactionJournal $entry */
|
/** @var TransactionJournal $entry */
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
|
|||||||
15
app/Console/Kernel.php
Executable file → Normal file
15
app/Console/Kernel.php
Executable file → Normal file
@@ -19,8 +19,8 @@ use FireflyIII\Console\Commands\Import;
|
|||||||
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;
|
||||||
|
use FireflyIII\Console\Commands\UseEncryption;
|
||||||
use FireflyIII\Console\Commands\VerifyDatabase;
|
use FireflyIII\Console\Commands\VerifyDatabase;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +63,7 @@ class Kernel extends ConsoleKernel
|
|||||||
EncryptFile::class,
|
EncryptFile::class,
|
||||||
ScanAttachments::class,
|
ScanAttachments::class,
|
||||||
UpgradeDatabase::class,
|
UpgradeDatabase::class,
|
||||||
|
UseEncryption::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,15 +75,4 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
require base_path('routes/console.php');
|
require base_path('routes/console.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the application's command schedule.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function schedule(Schedule $schedule)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class RegisteredUser extends Event
|
|||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new event instance.
|
* Create a new event instance. This event is triggered when a new user registers.
|
||||||
*
|
*
|
||||||
* @param User $user
|
* @param User $user
|
||||||
* @param string $ipAddress
|
* @param string $ipAddress
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* ConfirmedUser.php
|
* RequestedNewPassword.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
|
||||||
@@ -17,26 +17,30 @@ use FireflyIII\User;
|
|||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ConfirmedUser
|
* Class RequestedNewPassword
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Events
|
* @package FireflyIII\Events
|
||||||
*/
|
*/
|
||||||
class ConfirmedUser extends Event
|
class RequestedNewPassword extends Event
|
||||||
{
|
{
|
||||||
use SerializesModels;
|
use SerializesModels;
|
||||||
|
|
||||||
public $ipAddress;
|
public $ipAddress;
|
||||||
|
public $token;
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new event instance.
|
* Create a new event instance. This event is triggered when a users tries to reset his or her password.
|
||||||
*
|
*
|
||||||
* @param User $user
|
* @param User $user
|
||||||
|
* @param string $token
|
||||||
* @param string $ipAddress
|
* @param string $ipAddress
|
||||||
*/
|
*/
|
||||||
public function __construct(User $user, string $ipAddress)
|
public function __construct(User $user, string $token, string $ipAddress)
|
||||||
{
|
{
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
|
$this->token = $token;
|
||||||
$this->ipAddress = $ipAddress;
|
$this->ipAddress = $ipAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ResentConfirmation.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 ResentConfirmation
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Events
|
|
||||||
*/
|
|
||||||
class ResentConfirmation extends Event
|
|
||||||
{
|
|
||||||
use SerializesModels;
|
|
||||||
|
|
||||||
public $ipAddress;
|
|
||||||
public $user;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new event instance.
|
|
||||||
*
|
|
||||||
* @param User $user
|
|
||||||
* @param string $ipAddress
|
|
||||||
*/
|
|
||||||
public function __construct(User $user, string $ipAddress)
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
$this->ipAddress = $ipAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* StoredBudgetLimit.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 Carbon\Carbon;
|
|
||||||
use FireflyIII\Models\BudgetLimit;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class StoredBudgetLimit
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Events
|
|
||||||
*/
|
|
||||||
class StoredBudgetLimit extends Event
|
|
||||||
{
|
|
||||||
|
|
||||||
use SerializesModels;
|
|
||||||
|
|
||||||
/** @var BudgetLimit */
|
|
||||||
public $budgetLimit;
|
|
||||||
|
|
||||||
/** @var Carbon */
|
|
||||||
public $end; // the only variable we can't get from the budget limit (if necessary).
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BudgetLimitEvents constructor.
|
|
||||||
*
|
|
||||||
* @param BudgetLimit $budgetLimit
|
|
||||||
* @param Carbon $end
|
|
||||||
*/
|
|
||||||
public function __construct(BudgetLimit $budgetLimit, Carbon $end)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
$this->budgetLimit = $budgetLimit;
|
|
||||||
$this->end = $end;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* UpdatedBudgetLimit.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 Carbon\Carbon;
|
|
||||||
use FireflyIII\Models\BudgetLimit;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class UpdatedBudgetLimit
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Events
|
|
||||||
*/
|
|
||||||
class UpdatedBudgetLimit extends Event
|
|
||||||
{
|
|
||||||
|
|
||||||
use SerializesModels;
|
|
||||||
|
|
||||||
/** @var BudgetLimit */
|
|
||||||
public $budgetLimit;
|
|
||||||
|
|
||||||
/** @var Carbon */
|
|
||||||
public $end; // the only variable we can't get from the budget limit (if necessary).
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BudgetLimitEvents constructor.
|
|
||||||
*
|
|
||||||
* @param BudgetLimit $budgetLimit
|
|
||||||
* @param Carbon $end
|
|
||||||
*/
|
|
||||||
public function __construct(BudgetLimit $budgetLimit, Carbon $end)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
$this->budgetLimit = $budgetLimit;
|
|
||||||
$this->end = $end;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
13
app/Exceptions/Handler.php
Executable file → Normal file
13
app/Exceptions/Handler.php
Executable file → Normal file
@@ -21,6 +21,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Illuminate\Session\TokenMismatchException;
|
use Illuminate\Session\TokenMismatchException;
|
||||||
use Illuminate\Validation\ValidationException as ValException;
|
use Illuminate\Validation\ValidationException as ValException;
|
||||||
|
use Request;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,12 +73,14 @@ class Handler extends ExceptionHandler
|
|||||||
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
|
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
|
||||||
*
|
*
|
||||||
* @param Exception $exception
|
* @param Exception $exception
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
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',
|
||||||
@@ -97,8 +100,8 @@ class Handler extends ExceptionHandler
|
|||||||
];
|
];
|
||||||
|
|
||||||
// create job that will mail.
|
// create job that will mail.
|
||||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
$ipAddress = Request::ip() ?? '0.0.0.0';
|
||||||
$job = new MailError($userData, env('SITE_OWNER', ''), $ip, $data);
|
$job = new MailError($userData, env('SITE_OWNER', ''), $ipAddress, $data);
|
||||||
dispatch($job);
|
dispatch($job);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,9 +111,9 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Convert an authentication exception into an unauthenticated response.
|
* Convert an authentication exception into an unauthenticated response.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param $request
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
protected function unauthenticated($request)
|
protected function unauthenticated($request)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,11 +13,10 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Export\Collector;
|
namespace FireflyIII\Export\Collector;
|
||||||
|
|
||||||
use Amount;
|
use Carbon\Carbon;
|
||||||
use Crypt;
|
use Crypt;
|
||||||
use FireflyIII\Models\Attachment;
|
use FireflyIII\Models\Attachment;
|
||||||
use FireflyIII\Models\ExportJob;
|
use FireflyIII\Models\ExportJob;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
|
||||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||||
use Illuminate\Contracts\Encryption\DecryptException;
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -31,12 +30,14 @@ use Storage;
|
|||||||
*/
|
*/
|
||||||
class AttachmentCollector extends BasicCollector implements CollectorInterface
|
class AttachmentCollector extends BasicCollector implements CollectorInterface
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var Carbon */
|
||||||
private $explanationString = '';
|
private $end;
|
||||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||||
private $exportDisk;
|
private $exportDisk;
|
||||||
/** @var AttachmentRepositoryInterface */
|
/** @var AttachmentRepositoryInterface */
|
||||||
private $repository;
|
private $repository;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||||
private $uploadDisk;
|
private $uploadDisk;
|
||||||
|
|
||||||
@@ -69,34 +70,17 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
|
|||||||
$this->exportAttachment($attachment);
|
$this->exportAttachment($attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the explanation string in a file and attach it as well.
|
|
||||||
$file = $this->job->key . '-Source of all your attachments explained.txt';
|
|
||||||
$this->exportDisk->put($file, $this->explanationString);
|
|
||||||
$this->getFiles()->push($file);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Attachment $attachment
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
*/
|
*/
|
||||||
private function explain(Attachment $attachment)
|
public function setDates(Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
/** @var TransactionJournal $journal */
|
$this->start = $start;
|
||||||
$journal = $attachment->attachable;
|
$this->end = $end;
|
||||||
$args = [
|
|
||||||
'attachment_name' => e($attachment->filename),
|
|
||||||
'attachment_id' => $attachment->id,
|
|
||||||
'type' => strtolower($journal->transactionType->type),
|
|
||||||
'description' => e($journal->description),
|
|
||||||
'journal_id' => $journal->id,
|
|
||||||
'date' => $journal->date->formatLocalized(strval(trans('config.month_and_day'))),
|
|
||||||
'amount' => Amount::formatJournal($journal, false),
|
|
||||||
];
|
|
||||||
$string = trans('firefly.attachment_explanation', $args) . "\n";
|
|
||||||
Log::debug('Appended explanation string', ['string' => $string]);
|
|
||||||
$this->explanationString .= $string;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,10 +96,8 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
|
|||||||
$decrypted = Crypt::decrypt($this->uploadDisk->get($file));
|
$decrypted = Crypt::decrypt($this->uploadDisk->get($file));
|
||||||
$exportFile = $this->exportFileName($attachment);
|
$exportFile = $this->exportFileName($attachment);
|
||||||
$this->exportDisk->put($exportFile, $decrypted);
|
$this->exportDisk->put($exportFile, $decrypted);
|
||||||
$this->getFiles()->push($exportFile);
|
$this->getEntries()->push($exportFile);
|
||||||
|
|
||||||
// explain:
|
|
||||||
$this->explain($attachment);
|
|
||||||
} catch (DecryptException $e) {
|
} catch (DecryptException $e) {
|
||||||
Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage());
|
Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
@@ -143,7 +125,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
|
|||||||
*/
|
*/
|
||||||
private function getAttachments(): Collection
|
private function getAttachments(): Collection
|
||||||
{
|
{
|
||||||
$attachments = $this->repository->get();
|
$attachments = $this->repository->getBetween($this->start, $this->end);
|
||||||
|
|
||||||
return $attachments;
|
return $attachments;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class BasicCollector
|
|||||||
/** @var ExportJob */
|
/** @var ExportJob */
|
||||||
protected $job;
|
protected $job;
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
private $files;
|
private $entries;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BasicCollector constructor.
|
* BasicCollector constructor.
|
||||||
@@ -36,24 +36,24 @@ class BasicCollector
|
|||||||
*/
|
*/
|
||||||
public function __construct(ExportJob $job)
|
public function __construct(ExportJob $job)
|
||||||
{
|
{
|
||||||
$this->files = new Collection;
|
$this->entries = new Collection;
|
||||||
$this->job = $job;
|
$this->job = $job;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
public function getFiles(): Collection
|
public function getEntries(): Collection
|
||||||
{
|
{
|
||||||
return $this->files;
|
return $this->entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $files
|
* @param Collection $entries
|
||||||
*/
|
*/
|
||||||
public function setFiles(Collection $files)
|
public function setEntries(Collection $entries)
|
||||||
{
|
{
|
||||||
$this->files = $files;
|
$this->entries = $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface CollectorInterface
|
|||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
public function getFiles(): Collection;
|
public function getEntries(): Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
@@ -33,9 +33,11 @@ interface CollectorInterface
|
|||||||
public function run(): bool;
|
public function run(): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $files
|
* @param Collection $entries
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function setFiles(Collection $files);
|
public function setEntries(Collection $entries);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
347
app/Export/Collector/JournalExportCollector.php
Normal file
347
app/Export/Collector/JournalExportCollector.php
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* JournalExportCollector.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Export\Collector;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Crypt;
|
||||||
|
use DB;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Database\Query\JoinClause;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class JournalExportCollector
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Export\Collector
|
||||||
|
*/
|
||||||
|
class JournalExportCollector extends BasicCollector implements CollectorInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
|
||||||
|
/** @var Collection */
|
||||||
|
private $workSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function run(): bool
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Instead of collecting journals we collect transactions for the given accounts.
|
||||||
|
* We left join the OPPOSING transaction AND some journal data.
|
||||||
|
* After that we complement this info with budgets, categories, etc.
|
||||||
|
*
|
||||||
|
* This is way more efficient and will also work on split journals.
|
||||||
|
*/
|
||||||
|
$this->getWorkSet();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract:
|
||||||
|
* possible budget ids for journals
|
||||||
|
* possible category ids journals
|
||||||
|
* possible budget ids for transactions
|
||||||
|
* possible category ids for transactions
|
||||||
|
*
|
||||||
|
* possible IBAN and account numbers?
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$journals = $this->extractJournalIds();
|
||||||
|
$transactions = $this->extractTransactionIds();
|
||||||
|
|
||||||
|
|
||||||
|
// extend work set with category data from journals:
|
||||||
|
$this->categoryDataForJournals($journals);
|
||||||
|
|
||||||
|
// extend work set with category cate from transactions (overrules journals):
|
||||||
|
$this->categoryDataForTransactions($transactions);
|
||||||
|
|
||||||
|
// same for budgets:
|
||||||
|
$this->budgetDataForJournals($journals);
|
||||||
|
$this->budgetDataForTransactions($transactions);
|
||||||
|
|
||||||
|
$this->setEntries($this->workSet);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts)
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*/
|
||||||
|
public function setDates(Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$this->start = $start;
|
||||||
|
$this->end = $end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $journals
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function budgetDataForJournals(array $journals): bool
|
||||||
|
{
|
||||||
|
$set = DB::table('budget_transaction_journal')
|
||||||
|
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
|
||||||
|
->whereIn('budget_transaction_journal.transaction_journal_id', $journals)
|
||||||
|
->get(
|
||||||
|
[
|
||||||
|
'budget_transaction_journal.budget_id',
|
||||||
|
'budget_transaction_journal.transaction_journal_id',
|
||||||
|
'budgets.name',
|
||||||
|
'budgets.encrypted',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$set->each(
|
||||||
|
function ($obj) {
|
||||||
|
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$array = [];
|
||||||
|
foreach ($set as $obj) {
|
||||||
|
$array[$obj->transaction_journal_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->workSet->each(
|
||||||
|
function ($obj) use ($array) {
|
||||||
|
if (isset($array[$obj->transaction_journal_id])) {
|
||||||
|
$obj->budget_id = $array[$obj->transaction_journal_id]['id'];
|
||||||
|
$obj->budget_name = $array[$obj->transaction_journal_id]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $transactions
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function budgetDataForTransactions(array $transactions): bool
|
||||||
|
{
|
||||||
|
$set = DB::table('budget_transaction')
|
||||||
|
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction.budget_id')
|
||||||
|
->whereIn('budget_transaction.transaction_id', $transactions)
|
||||||
|
->get(
|
||||||
|
[
|
||||||
|
'budget_transaction.budget_id',
|
||||||
|
'budget_transaction.transaction_id',
|
||||||
|
'budgets.name',
|
||||||
|
'budgets.encrypted',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$set->each(
|
||||||
|
function ($obj) {
|
||||||
|
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$array = [];
|
||||||
|
foreach ($set as $obj) {
|
||||||
|
$array[$obj->transaction_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->workSet->each(
|
||||||
|
function ($obj) use ($array) {
|
||||||
|
|
||||||
|
// first transaction
|
||||||
|
if (isset($array[$obj->id])) {
|
||||||
|
$obj->budget_id = $array[$obj->id]['id'];
|
||||||
|
$obj->budget_name = $array[$obj->id]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $journals
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function categoryDataForJournals(array $journals): bool
|
||||||
|
{
|
||||||
|
$set = DB::table('category_transaction_journal')
|
||||||
|
->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id')
|
||||||
|
->whereIn('category_transaction_journal.transaction_journal_id', $journals)
|
||||||
|
->get(
|
||||||
|
[
|
||||||
|
'category_transaction_journal.category_id',
|
||||||
|
'category_transaction_journal.transaction_journal_id',
|
||||||
|
'categories.name',
|
||||||
|
'categories.encrypted',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$set->each(
|
||||||
|
function ($obj) {
|
||||||
|
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$array = [];
|
||||||
|
foreach ($set as $obj) {
|
||||||
|
$array[$obj->transaction_journal_id] = ['id' => $obj->category_id, 'name' => $obj->name];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->workSet->each(
|
||||||
|
function ($obj) use ($array) {
|
||||||
|
if (isset($array[$obj->transaction_journal_id])) {
|
||||||
|
$obj->category_id = $array[$obj->transaction_journal_id]['id'];
|
||||||
|
$obj->category_name = $array[$obj->transaction_journal_id]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $transactions
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function categoryDataForTransactions(array $transactions): bool
|
||||||
|
{
|
||||||
|
$set = DB::table('category_transaction')
|
||||||
|
->leftJoin('categories', 'categories.id', '=', 'category_transaction.category_id')
|
||||||
|
->whereIn('category_transaction.transaction_id', $transactions)
|
||||||
|
->get(
|
||||||
|
[
|
||||||
|
'category_transaction.category_id',
|
||||||
|
'category_transaction.transaction_id',
|
||||||
|
'categories.name',
|
||||||
|
'categories.encrypted',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$set->each(
|
||||||
|
function ($obj) {
|
||||||
|
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$array = [];
|
||||||
|
foreach ($set as $obj) {
|
||||||
|
$array[$obj->transaction_id] = ['id' => $obj->category_id, 'name' => $obj->name];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->workSet->each(
|
||||||
|
function ($obj) use ($array) {
|
||||||
|
|
||||||
|
// first transaction
|
||||||
|
if (isset($array[$obj->id])) {
|
||||||
|
$obj->category_id = $array[$obj->id]['id'];
|
||||||
|
$obj->category_name = $array[$obj->id]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function extractJournalIds(): array
|
||||||
|
{
|
||||||
|
return $this->workSet->pluck('transaction_journal_id')->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function extractTransactionIds()
|
||||||
|
{
|
||||||
|
$set = $this->workSet->pluck('id')->toArray();
|
||||||
|
$opposing = $this->workSet->pluck('opposing_id')->toArray();
|
||||||
|
$complete = $set + $opposing;
|
||||||
|
|
||||||
|
return array_unique($complete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||||
|
*/
|
||||||
|
private function getWorkSet()
|
||||||
|
{
|
||||||
|
$accountIds = $this->accounts->pluck('id')->toArray();
|
||||||
|
$this->workSet = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||||
|
->leftJoin(
|
||||||
|
'transactions AS opposing', function (JoinClause $join) {
|
||||||
|
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
|
||||||
|
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
|
||||||
|
->where('transactions.identifier', '=', DB::raw('opposing.identifier'));
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||||
|
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
|
||||||
|
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
|
||||||
|
->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id')
|
||||||
|
->whereIn('transactions.account_id', $accountIds)
|
||||||
|
->where('transaction_journals.user_id', $this->job->user_id)
|
||||||
|
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
|
||||||
|
->where('transaction_journals.date', '<=', $this->end->format('Y-m-d'))
|
||||||
|
->where('transaction_journals.completed', 1)
|
||||||
|
->whereNull('transaction_journals.deleted_at')
|
||||||
|
->whereNull('transactions.deleted_at')
|
||||||
|
->whereNull('opposing.deleted_at')
|
||||||
|
->orderBy('transaction_journals.date', 'DESC')
|
||||||
|
->orderBy('transactions.identifier', 'ASC')
|
||||||
|
->get(
|
||||||
|
[
|
||||||
|
'transactions.id',
|
||||||
|
'transactions.amount',
|
||||||
|
'transactions.description',
|
||||||
|
'transactions.account_id',
|
||||||
|
'accounts.name as account_name',
|
||||||
|
'accounts.encrypted as account_name_encrypted',
|
||||||
|
'transactions.identifier',
|
||||||
|
|
||||||
|
'opposing.id as opposing_id',
|
||||||
|
'opposing.amount AS opposing_amount',
|
||||||
|
'opposing.description as opposing_description',
|
||||||
|
'opposing.account_id as opposing_account_id',
|
||||||
|
'opposing_accounts.name as opposing_account_name',
|
||||||
|
'opposing_accounts.encrypted as opposing_account_encrypted',
|
||||||
|
'opposing.identifier as opposing_identifier',
|
||||||
|
|
||||||
|
'transaction_journals.id as transaction_journal_id',
|
||||||
|
'transaction_journals.date',
|
||||||
|
'transaction_journals.description as journal_description',
|
||||||
|
'transaction_journals.encrypted as journal_encrypted',
|
||||||
|
'transaction_journals.transaction_type_id',
|
||||||
|
'transaction_types.type as transaction_type',
|
||||||
|
'transaction_journals.transaction_currency_id',
|
||||||
|
'transaction_currencies.code AS transaction_currency_code',
|
||||||
|
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,16 +26,14 @@ use Storage;
|
|||||||
*/
|
*/
|
||||||
class UploadCollector extends BasicCollector implements CollectorInterface
|
class UploadCollector extends BasicCollector implements CollectorInterface
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
private $expected;
|
|
||||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||||
private $exportDisk;
|
private $exportDisk;
|
||||||
private $importKeys = [];
|
|
||||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||||
private $uploadDisk;
|
private $uploadDisk;
|
||||||
|
/** @var string */
|
||||||
|
private $vintageFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* AttachmentCollector constructor.
|
* AttachmentCollector constructor.
|
||||||
*
|
*
|
||||||
* @param ExportJob $job
|
* @param ExportJob $job
|
||||||
@@ -51,50 +49,74 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
|||||||
$this->exportDisk = Storage::disk('export');
|
$this->exportDisk = Storage::disk('export');
|
||||||
|
|
||||||
// file names associated with the old import routine.
|
// file names associated with the old import routine.
|
||||||
$this->expected = 'csv-upload-' . auth()->user()->id . '-';
|
$this->vintageFormat = sprintf('csv-upload-%d-', auth()->user()->id);
|
||||||
|
|
||||||
// for the new import routine:
|
|
||||||
$this->getImportKeys();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Is called from the outside to actually start the export.
|
||||||
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function run(): bool
|
public function run(): bool
|
||||||
{
|
{
|
||||||
// grab upload directory.
|
// collect old upload files (names beginning with "csv-upload".
|
||||||
$files = $this->uploadDisk->files();
|
$this->collectVintageUploads();
|
||||||
|
|
||||||
foreach ($files as $entry) {
|
// then collect current upload files:
|
||||||
$this->processUpload($entry);
|
$this->collectModernUploads();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method collects all the uploads that are uploaded using the new importer. So after the summer of 2016.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function collectModernUploads(): bool
|
||||||
|
{
|
||||||
|
$set = $this->job->user->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
|
||||||
|
$keys = [];
|
||||||
|
if ($set->count() > 0) {
|
||||||
|
$keys = $set->pluck('key')->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$this->processModernUpload($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016.
|
||||||
*
|
*
|
||||||
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function getImportKeys()
|
private function collectVintageUploads(): bool
|
||||||
{
|
{
|
||||||
$set = auth()->user()->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
|
// grab upload directory.
|
||||||
if ($set->count() > 0) {
|
$files = $this->uploadDisk->files();
|
||||||
$keys = $set->pluck('key')->toArray();
|
|
||||||
$this->importKeys = $keys;
|
|
||||||
|
|
||||||
|
foreach ($files as $entry) {
|
||||||
|
$this->processVintageUpload($entry);
|
||||||
}
|
}
|
||||||
Log::debug('Valid import keys are ', $this->importKeys);
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This method tells you when the vintage upload file was actually uploaded.
|
||||||
|
*
|
||||||
* @param string $entry
|
* @param string $entry
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function getOriginalUploadDate(string $entry): string
|
private function getVintageUploadDate(string $entry): string
|
||||||
{
|
{
|
||||||
// this is an original upload.
|
// this is an original upload.
|
||||||
$parts = explode('-', str_replace(['.csv.encrypted', $this->expected], '', $entry));
|
$parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry));
|
||||||
$originalUpload = intval($parts[1]);
|
$originalUpload = intval($parts[1]);
|
||||||
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
|
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
|
||||||
|
|
||||||
@@ -102,33 +124,17 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Tells you if a file name is a vintage upload.
|
||||||
|
*
|
||||||
* @param string $entry
|
* @param string $entry
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function isImportFile(string $entry): bool
|
private function isVintageImport(string $entry): bool
|
||||||
{
|
{
|
||||||
$name = str_replace('.upload', '', $entry);
|
$len = strlen($this->vintageFormat);
|
||||||
if (in_array($name, $this->importKeys)) {
|
|
||||||
Log::debug(sprintf('Import file "%s" is in array', $name), $this->importKeys);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Log::debug(sprintf('Import file "%s" is NOT in array', $name), $this->importKeys);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $entry
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function isOldImport(string $entry): bool
|
|
||||||
{
|
|
||||||
$len = strlen($this->expected);
|
|
||||||
// file is part of the old import routine:
|
// file is part of the old import routine:
|
||||||
if (substr($entry, 0, $len) === $this->expected) {
|
if (substr($entry, 0, $len) === $this->vintageFormat) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -137,49 +143,62 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $entry
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function processUpload(string $entry)
|
private function processModernUpload(string $key): bool
|
||||||
{
|
|
||||||
// file is old import:
|
|
||||||
if ($this->isOldImport($entry)) {
|
|
||||||
$this->saveOldImportFile($entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// file is current import.
|
|
||||||
if ($this->isImportFile($entry)) {
|
|
||||||
$this->saveImportFile($entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $entry
|
|
||||||
*/
|
|
||||||
private function saveImportFile(string $entry)
|
|
||||||
{
|
{
|
||||||
// find job associated with import file:
|
// find job associated with import file:
|
||||||
$name = str_replace('.upload', '', $entry);
|
$job = $this->job->user->importJobs()->where('key', $key)->first();
|
||||||
$job = auth()->user()->importJobs()->where('key', $name)->first();
|
if (is_null($job)) {
|
||||||
$content = '';
|
return false;
|
||||||
try {
|
|
||||||
$content = Crypt::decrypt($this->uploadDisk->get($entry));
|
|
||||||
} catch (DecryptException $e) {
|
|
||||||
Log::error('Could not decrypt old import file ' . $entry . '. Skipped because ' . $e->getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($job) && strlen($content) > 0) {
|
// find the file for this import:
|
||||||
|
$content = '';
|
||||||
|
try {
|
||||||
|
$content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key)));
|
||||||
|
} catch (DecryptException $e) {
|
||||||
|
Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($content) > 0) {
|
||||||
// add to export disk.
|
// add to export disk.
|
||||||
$date = $job->created_at->format('Y-m-d');
|
$date = $job->created_at->format('Y-m-d');
|
||||||
$file = sprintf('%s-Old %s import dated %s.%s', $this->job->key, strtoupper($job->file_type), $date, $job->file_type);
|
$file = sprintf('%s-Old %s import dated %s.%s', $this->job->key, strtoupper($job->file_type), $date, $job->file_type);
|
||||||
$this->exportDisk->put($file, $content);
|
$this->exportDisk->put($file, $content);
|
||||||
$this->getFiles()->push($file);
|
$this->getEntries()->push($file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* If the file is a vintage upload, process it.
|
||||||
|
*
|
||||||
|
* @param string $entry
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function processVintageUpload(string $entry): bool
|
||||||
|
{
|
||||||
|
if ($this->isVintageImport($entry)) {
|
||||||
|
$this->saveVintageImportFile($entry);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will store the content of the old vintage upload somewhere.
|
||||||
|
*
|
||||||
* @param string $entry
|
* @param string $entry
|
||||||
*/
|
*/
|
||||||
private function saveOldImportFile(string $entry)
|
private function saveVintageImportFile(string $entry)
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
try {
|
try {
|
||||||
@@ -190,10 +209,10 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
|||||||
|
|
||||||
if (strlen($content) > 0) {
|
if (strlen($content) > 0) {
|
||||||
// add to export disk.
|
// add to export disk.
|
||||||
$date = $this->getOriginalUploadDate($entry);
|
$date = $this->getVintageUploadDate($entry);
|
||||||
$file = $this->job->key . '-Old import dated ' . $date . '.csv';
|
$file = $this->job->key . '-Old import dated ' . $date . '.csv';
|
||||||
$this->exportDisk->put($file, $content);
|
$this->exportDisk->put($file, $content);
|
||||||
$this->getFiles()->push($file);
|
$this->getEntries()->push($file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ConfigurationFile.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Export;
|
|
||||||
|
|
||||||
use FireflyIII\Export\Entry\Entry;
|
|
||||||
use FireflyIII\Models\ExportJob;
|
|
||||||
use Storage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ConfigurationFile
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Export
|
|
||||||
*/
|
|
||||||
class ConfigurationFile
|
|
||||||
{
|
|
||||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
|
||||||
private $exportDisk;
|
|
||||||
/** @var ExportJob */
|
|
||||||
private $job;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ConfigurationFile constructor.
|
|
||||||
*
|
|
||||||
* @param ExportJob $job
|
|
||||||
*/
|
|
||||||
public function __construct(ExportJob $job)
|
|
||||||
{
|
|
||||||
$this->job = $job;
|
|
||||||
$this->exportDisk = Storage::disk('export');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function make(): string
|
|
||||||
{
|
|
||||||
$fields = array_keys(Entry::getFieldsAndTypes());
|
|
||||||
$types = Entry::getFieldsAndTypes();
|
|
||||||
|
|
||||||
$configuration = [
|
|
||||||
'date-format' => 'Y-m-d', // unfortunately, this is hard-coded.
|
|
||||||
'has-headers' => true,
|
|
||||||
'map' => [], // we could build a map if necessary for easy re-import.
|
|
||||||
'roles' => [],
|
|
||||||
'mapped' => [],
|
|
||||||
'specifix' => [],
|
|
||||||
];
|
|
||||||
foreach ($fields as $field) {
|
|
||||||
$configuration['roles'][] = $types[$field];
|
|
||||||
}
|
|
||||||
$file = $this->job->key . '-configuration.json';
|
|
||||||
$this->exportDisk->put($file, json_encode($configuration, JSON_PRETTY_PRINT));
|
|
||||||
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,7 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Export\Entry;
|
namespace FireflyIII\Export\Entry;
|
||||||
|
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use Crypt;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To extend the exported object, in case of new features in Firefly III for example,
|
* To extend the exported object, in case of new features in Firefly III for example,
|
||||||
@@ -30,103 +29,85 @@ use Illuminate\Support\Collection;
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Class Entry
|
* Class Entry
|
||||||
|
* @SuppressWarnings(PHPMD.LongVariable)
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Export\Entry
|
* @package FireflyIII\Export\Entry
|
||||||
*/
|
*/
|
||||||
final class Entry
|
final class Entry
|
||||||
{
|
{
|
||||||
/** @var string */
|
// @formatter:off
|
||||||
public $amount;
|
public $journal_id;
|
||||||
/** @var EntryBill */
|
|
||||||
public $bill;
|
|
||||||
/** @var EntryBudget */
|
|
||||||
public $budget;
|
|
||||||
/** @var EntryCategory */
|
|
||||||
public $category;
|
|
||||||
/** @var string */
|
|
||||||
public $date;
|
public $date;
|
||||||
/** @var string */
|
|
||||||
public $description;
|
public $description;
|
||||||
/** @var EntryAccount */
|
|
||||||
public $destinationAccount;
|
public $currency_code;
|
||||||
/** @var Collection */
|
public $amount;
|
||||||
public $destinationAccounts;
|
|
||||||
/** @var EntryAccount */
|
public $transaction_type;
|
||||||
public $sourceAccount;
|
|
||||||
/** @var Collection */
|
public $source_account_id;
|
||||||
public $sourceAccounts;
|
public $source_account_name;
|
||||||
|
|
||||||
|
public $destination_account_id;
|
||||||
|
public $destination_account_name;
|
||||||
|
|
||||||
|
public $budget_id;
|
||||||
|
public $budget_name;
|
||||||
|
public $category_id;
|
||||||
|
public $category_name;
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry constructor.
|
* Entry constructor.
|
||||||
*/
|
*/
|
||||||
private function __construct()
|
private function __construct()
|
||||||
{
|
{
|
||||||
$this->sourceAccounts = new Collection;
|
|
||||||
$this->destinationAccounts = new Collection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param TransactionJournal $journal
|
* @param $object
|
||||||
*
|
*
|
||||||
* @return Entry
|
* @return Entry
|
||||||
*/
|
*/
|
||||||
public static function fromJournal(TransactionJournal $journal)
|
public static function fromObject($object): Entry
|
||||||
{
|
{
|
||||||
|
$entry = new self;
|
||||||
|
$entry->journal_id = $object->transaction_journal_id;
|
||||||
|
$entry->description = self::decrypt(intval($object->journal_encrypted), $object->journal_description);
|
||||||
|
$entry->amount = $object->amount;
|
||||||
|
$entry->date = $object->date;
|
||||||
|
$entry->transaction_type = $object->transaction_type;
|
||||||
|
$entry->currency_code = $object->transaction_currency_code;
|
||||||
|
$entry->source_account_id = $object->account_id;
|
||||||
|
$entry->source_account_name = self::decrypt(intval($object->account_name_encrypted), $object->account_name);
|
||||||
|
$entry->destination_account_id = $object->opposing_account_id;
|
||||||
|
$entry->destination_account_name = self::decrypt(intval($object->opposing_account_encrypted), $object->opposing_account_name);
|
||||||
|
$entry->category_id = $object->category_id ?? '';
|
||||||
|
$entry->category_name = $object->category_name ?? '';
|
||||||
|
$entry->budget_id = $object->budget_id ?? '';
|
||||||
|
$entry->budget_name = $object->budget_name ?? '';
|
||||||
|
|
||||||
$entry = new self;
|
// update description when transaction description is different:
|
||||||
$entry->description = $journal->description;
|
if (!is_null($object->description) && $object->description != $entry->description) {
|
||||||
$entry->date = $journal->date->format('Y-m-d');
|
$entry->description = $entry->description . ' (' . $object->description . ')';
|
||||||
$entry->amount = TransactionJournal::amount($journal);
|
|
||||||
|
|
||||||
$entry->budget = new EntryBudget($journal->budgets->first());
|
|
||||||
$entry->category = new EntryCategory($journal->categories->first());
|
|
||||||
$entry->bill = new EntryBill($journal->bill);
|
|
||||||
|
|
||||||
$sources = TransactionJournal::sourceAccountList($journal);
|
|
||||||
$destinations = TransactionJournal::destinationAccountList($journal);
|
|
||||||
$entry->sourceAccount = new EntryAccount($sources->first());
|
|
||||||
$entry->destinationAccount = new EntryAccount($destinations->first());
|
|
||||||
|
|
||||||
foreach ($sources as $source) {
|
|
||||||
$entry->sourceAccounts->push(new EntryAccount($source));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($destinations as $destination) {
|
|
||||||
$entry->destinationAccounts->push(new EntryAccount($destination));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $entry;
|
return $entry;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @param int $isEncrypted
|
||||||
|
* @param $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function getFieldsAndTypes(): array
|
protected static function decrypt(int $isEncrypted, $value)
|
||||||
{
|
{
|
||||||
// key = field name (see top of class)
|
if ($isEncrypted === 1) {
|
||||||
// value = field type (see csv.php under 'roles')
|
return Crypt::decrypt($value);
|
||||||
return [
|
}
|
||||||
'description' => 'description',
|
|
||||||
'amount' => 'amount',
|
return $value;
|
||||||
'date' => 'date-transaction',
|
|
||||||
'source_account_id' => 'account-id',
|
|
||||||
'source_account_name' => 'account-name',
|
|
||||||
'source_account_iban' => 'account-iban',
|
|
||||||
'source_account_type' => '_ignore',
|
|
||||||
'source_account_number' => 'account-number',
|
|
||||||
'destination_account_id' => 'opposing-id',
|
|
||||||
'destination_account_name' => 'opposing-name',
|
|
||||||
'destination_account_iban' => 'opposing-iban',
|
|
||||||
'destination_account_type' => '_ignore',
|
|
||||||
'destination_account_number' => 'account-number',
|
|
||||||
'budget_id' => 'budget-id',
|
|
||||||
'budget_name' => 'budget-name',
|
|
||||||
'category_id' => 'category-id',
|
|
||||||
'category_name' => 'category-name',
|
|
||||||
'bill_id' => 'bill-id',
|
|
||||||
'bill_name' => 'bill-name',
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* EntryAccount.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Export\Entry;
|
|
||||||
|
|
||||||
use FireflyIII\Models\Account;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class EntryAccount
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Export\Entry
|
|
||||||
*/
|
|
||||||
class EntryAccount
|
|
||||||
{
|
|
||||||
/** @var int */
|
|
||||||
public $accountId;
|
|
||||||
/** @var string */
|
|
||||||
public $iban;
|
|
||||||
/** @var string */
|
|
||||||
public $name;
|
|
||||||
/** @var string */
|
|
||||||
public $number;
|
|
||||||
/** @var string */
|
|
||||||
public $type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EntryAccount constructor.
|
|
||||||
*
|
|
||||||
* @param Account $account
|
|
||||||
*/
|
|
||||||
public function __construct(Account $account)
|
|
||||||
{
|
|
||||||
$this->accountId = $account->id;
|
|
||||||
$this->name = $account->name;
|
|
||||||
$this->iban = $account->iban;
|
|
||||||
$this->type = $account->accountType->type;
|
|
||||||
$this->number = $account->getMeta('accountNumber');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* EntryBill.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Export\Entry;
|
|
||||||
|
|
||||||
use FireflyIII\Models\Bill;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class EntryBill
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Export\Entry
|
|
||||||
*/
|
|
||||||
class EntryBill
|
|
||||||
{
|
|
||||||
/** @var int */
|
|
||||||
public $billId = '';
|
|
||||||
/** @var string */
|
|
||||||
public $name = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EntryBill constructor.
|
|
||||||
*
|
|
||||||
* @param Bill $bill
|
|
||||||
*/
|
|
||||||
public function __construct(Bill $bill = null)
|
|
||||||
{
|
|
||||||
if (!is_null($bill)) {
|
|
||||||
$this->billId = $bill->id;
|
|
||||||
$this->name = $bill->name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* EntryBudget.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Export\Entry;
|
|
||||||
|
|
||||||
use FireflyIII\Models\Budget;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class EntryBudget
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Export\Entry
|
|
||||||
*/
|
|
||||||
class EntryBudget
|
|
||||||
{
|
|
||||||
/** @var int */
|
|
||||||
public $budgetId = '';
|
|
||||||
/** @var string */
|
|
||||||
public $name = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EntryBudget constructor.
|
|
||||||
*
|
|
||||||
* @param Budget $budget
|
|
||||||
*/
|
|
||||||
public function __construct(Budget $budget = null)
|
|
||||||
{
|
|
||||||
if (!is_null($budget)) {
|
|
||||||
$this->budgetId = $budget->id;
|
|
||||||
$this->name = $budget->name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* EntryCategory.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Export\Entry;
|
|
||||||
|
|
||||||
use FireflyIII\Models\Category;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class EntryCategory
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Export\Entry
|
|
||||||
*/
|
|
||||||
class EntryCategory
|
|
||||||
{
|
|
||||||
/** @var int */
|
|
||||||
public $categoryId = '';
|
|
||||||
/** @var string */
|
|
||||||
public $name = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EntryCategory constructor.
|
|
||||||
*
|
|
||||||
* @param Category $category
|
|
||||||
*/
|
|
||||||
public function __construct(Category $category = null)
|
|
||||||
{
|
|
||||||
if (!is_null($category)) {
|
|
||||||
$this->categoryId = $category->id;
|
|
||||||
$this->name = $category->name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,9 +14,7 @@ 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 Illuminate\Support\Collection;
|
|
||||||
use League\Csv\Writer;
|
use League\Csv\Writer;
|
||||||
use SplFileObject;
|
use SplFileObject;
|
||||||
|
|
||||||
@@ -62,110 +60,24 @@ class CsvExporter extends BasicExporter implements ExporterInterface
|
|||||||
$writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
|
$writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
|
||||||
$rows = [];
|
$rows = [];
|
||||||
|
|
||||||
// Count the maximum number of sources and destinations each entry has. May need to expand the number of export fields:
|
// get field names for header row:
|
||||||
$maxSourceAccounts = 1;
|
$first = $this->getEntries()->first();
|
||||||
$maxDestAccounts = 1;
|
$headers = array_keys(get_object_vars($first));
|
||||||
/** @var Entry $entry */
|
$rows[] = $headers;
|
||||||
foreach ($this->getEntries() as $entry) {
|
|
||||||
$sources = $entry->sourceAccounts->count();
|
|
||||||
$destinations = $entry->destinationAccounts->count();
|
|
||||||
$maxSourceAccounts = max($maxSourceAccounts, $sources);
|
|
||||||
$maxDestAccounts = max($maxDestAccounts, $destinations);
|
|
||||||
}
|
|
||||||
$rows[] = array_keys($this->getFieldsAndTypes($maxSourceAccounts, $maxDestAccounts));
|
|
||||||
|
|
||||||
/** @var Entry $entry */
|
/** @var Entry $entry */
|
||||||
foreach ($this->getEntries() as $entry) {
|
foreach ($this->getEntries() as $entry) {
|
||||||
// order is defined in Entry::getFieldsAndTypes.
|
$line = [];
|
||||||
$current = [$entry->description, $entry->amount, $entry->date];
|
foreach ($headers as $header) {
|
||||||
$sourceData = $this->getAccountData($maxSourceAccounts, $entry->sourceAccounts);
|
$line[] = $entry->$header;
|
||||||
$current = array_merge($current, $sourceData);
|
}
|
||||||
$destData = $this->getAccountData($maxDestAccounts, $entry->destinationAccounts);
|
$rows[] = $line;
|
||||||
$current = array_merge($current, $destData);
|
|
||||||
$rest = [$entry->budget->budgetId, $entry->budget->name, $entry->category->categoryId, $entry->category->name, $entry->bill->billId,
|
|
||||||
$entry->bill->name];
|
|
||||||
$current = array_merge($current, $rest);
|
|
||||||
$rows[] = $current;
|
|
||||||
}
|
}
|
||||||
$writer->insertAll($rows);
|
$writer->insertAll($rows);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $max
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getAccountData(int $max, Collection $accounts): array
|
|
||||||
{
|
|
||||||
$current = [];
|
|
||||||
for ($i = 0; $i < $max; $i++) {
|
|
||||||
/** @var EntryAccount $source */
|
|
||||||
$source = $accounts->get($i);
|
|
||||||
$currentId = '';
|
|
||||||
$currentName = '';
|
|
||||||
$currentIban = '';
|
|
||||||
$currentType = '';
|
|
||||||
$currentNumber = '';
|
|
||||||
if ($source) {
|
|
||||||
$currentId = $source->accountId;
|
|
||||||
$currentName = $source->name;
|
|
||||||
$currentIban = $source->iban;
|
|
||||||
$currentType = $source->type;
|
|
||||||
$currentNumber = $source->number;
|
|
||||||
}
|
|
||||||
$current[] = $currentId;
|
|
||||||
$current[] = $currentName;
|
|
||||||
$current[] = $currentIban;
|
|
||||||
$current[] = $currentType;
|
|
||||||
$current[] = $currentNumber;
|
|
||||||
}
|
|
||||||
unset($source);
|
|
||||||
|
|
||||||
return $current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $sources
|
|
||||||
* @param int $destinations
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getFieldsAndTypes(int $sources, int $destinations): array
|
|
||||||
{
|
|
||||||
// key = field name (see top of class)
|
|
||||||
// value = field type (see csv.php under 'roles')
|
|
||||||
$array = [
|
|
||||||
'description' => 'description',
|
|
||||||
'amount' => 'amount',
|
|
||||||
'date' => 'date-transaction',
|
|
||||||
];
|
|
||||||
for ($i = 0; $i < $sources; $i++) {
|
|
||||||
$array['source_account_' . $i . '_id'] = 'account-id';
|
|
||||||
$array['source_account_' . $i . '_name'] = 'account-name';
|
|
||||||
$array['source_account_' . $i . '_iban'] = 'account-iban';
|
|
||||||
$array['source_account_' . $i . '_type'] = '_ignore';
|
|
||||||
$array['source_account_' . $i . '_number'] = 'account-number';
|
|
||||||
}
|
|
||||||
for ($i = 0; $i < $destinations; $i++) {
|
|
||||||
$array['destination_account_' . $i . '_id'] = 'account-id';
|
|
||||||
$array['destination_account_' . $i . '_name'] = 'account-name';
|
|
||||||
$array['destination_account_' . $i . '_iban'] = 'account-iban';
|
|
||||||
$array['destination_account_' . $i . '_type'] = '_ignore';
|
|
||||||
$array['destination_account_' . $i . '_number'] = 'account-number';
|
|
||||||
}
|
|
||||||
|
|
||||||
$array['budget_id'] = 'budget-id';
|
|
||||||
$array['budget_name'] = 'budget-name';
|
|
||||||
$array['category_id'] = 'category-id';
|
|
||||||
$array['category_name'] = 'category-name';
|
|
||||||
$array['bill_id'] = 'bill-id';
|
|
||||||
$array['bill_name'] = 'bill-name';
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function tempFile()
|
private function tempFile()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ interface ExporterInterface
|
|||||||
/**
|
/**
|
||||||
* @param Collection $entries
|
* @param Collection $entries
|
||||||
*
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public function setEntries(Collection $entries);
|
public function setEntries(Collection $entries);
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ 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\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;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
|
||||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
|
||||||
use Illuminate\Filesystem\FilesystemAdapter;
|
use Illuminate\Filesystem\FilesystemAdapter;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
use Storage;
|
use Storage;
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ use ZipArchive;
|
|||||||
*
|
*
|
||||||
* @package FireflyIII\Export
|
* @package FireflyIII\Export
|
||||||
*/
|
*/
|
||||||
class Processor
|
class Processor implements ProcessorInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
@@ -40,15 +40,11 @@ class Processor
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
public $includeAttachments;
|
public $includeAttachments;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
public $includeConfig;
|
|
||||||
/** @var bool */
|
|
||||||
public $includeOldUploads;
|
public $includeOldUploads;
|
||||||
/** @var ExportJob */
|
/** @var ExportJob */
|
||||||
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 */
|
||||||
@@ -68,7 +64,6 @@ class Processor
|
|||||||
$this->accounts = $settings['accounts'];
|
$this->accounts = $settings['accounts'];
|
||||||
$this->exportFormat = $settings['exportFormat'];
|
$this->exportFormat = $settings['exportFormat'];
|
||||||
$this->includeAttachments = $settings['includeAttachments'];
|
$this->includeAttachments = $settings['includeAttachments'];
|
||||||
$this->includeConfig = $settings['includeConfig'];
|
|
||||||
$this->includeOldUploads = $settings['includeOldUploads'];
|
$this->includeOldUploads = $settings['includeOldUploads'];
|
||||||
$this->job = $settings['job'];
|
$this->job = $settings['job'];
|
||||||
$this->journals = new Collection;
|
$this->journals = new Collection;
|
||||||
@@ -84,8 +79,9 @@ class Processor
|
|||||||
{
|
{
|
||||||
/** @var AttachmentCollector $attachmentCollector */
|
/** @var AttachmentCollector $attachmentCollector */
|
||||||
$attachmentCollector = app(AttachmentCollector::class, [$this->job]);
|
$attachmentCollector = app(AttachmentCollector::class, [$this->job]);
|
||||||
|
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||||
$attachmentCollector->run();
|
$attachmentCollector->run();
|
||||||
$this->files = $this->files->merge($attachmentCollector->getFiles());
|
$this->files = $this->files->merge($attachmentCollector->getEntries());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -95,9 +91,13 @@ class Processor
|
|||||||
*/
|
*/
|
||||||
public function collectJournals(): bool
|
public function collectJournals(): bool
|
||||||
{
|
{
|
||||||
/** @var JournalTaskerInterface $tasker */
|
/** @var JournalExportCollector $collector */
|
||||||
$tasker = app(JournalTaskerInterface::class);
|
$collector = app(JournalExportCollector::class, [$this->job]);
|
||||||
$this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
|
$collector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||||
|
$collector->setAccounts($this->settings['accounts']);
|
||||||
|
$collector->run();
|
||||||
|
$this->journals = $collector->getEntries();
|
||||||
|
Log::debug(sprintf('Count %d journals in collectJournals() ', $this->journals->count()));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ class Processor
|
|||||||
$uploadCollector = app(UploadCollector::class, [$this->job]);
|
$uploadCollector = app(UploadCollector::class, [$this->job]);
|
||||||
$uploadCollector->run();
|
$uploadCollector->run();
|
||||||
|
|
||||||
$this->files = $this->files->merge($uploadCollector->getFiles());
|
$this->files = $this->files->merge($uploadCollector->getEntries());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -122,22 +122,11 @@ class Processor
|
|||||||
public function convertJournals(): bool
|
public function convertJournals(): bool
|
||||||
{
|
{
|
||||||
$count = 0;
|
$count = 0;
|
||||||
/** @var TransactionJournal $journal */
|
foreach ($this->journals as $object) {
|
||||||
foreach ($this->journals as $journal) {
|
$this->exportEntries->push(Entry::fromObject($object));
|
||||||
$this->exportEntries->push(Entry::fromJournal($journal));
|
|
||||||
$count++;
|
$count++;
|
||||||
}
|
}
|
||||||
|
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function createConfigFile(): bool
|
|
||||||
{
|
|
||||||
$this->configurationMaker = app(ConfigurationFile::class, [$this->job]);
|
|
||||||
$this->files->push($this->configurationMaker->make());
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -166,7 +155,7 @@ class Processor
|
|||||||
$zip->close();
|
$zip->close();
|
||||||
|
|
||||||
// delete the files:
|
// delete the files:
|
||||||
$this->deleteFiles($disk);
|
$this->deleteFiles();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -194,10 +183,11 @@ class Processor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param FilesystemAdapter $disk
|
*
|
||||||
*/
|
*/
|
||||||
private function deleteFiles(FilesystemAdapter $disk)
|
private function deleteFiles()
|
||||||
{
|
{
|
||||||
|
$disk = Storage::disk('export');
|
||||||
foreach ($this->getFiles() as $file) {
|
foreach ($this->getFiles() as $file) {
|
||||||
$disk->delete($file);
|
$disk->delete($file);
|
||||||
}
|
}
|
||||||
|
|||||||
68
app/Export/ProcessorInterface.php
Normal file
68
app/Export/ProcessorInterface.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
150
app/Generator/Chart/Basic/ChartJsGenerator.php
Normal file
150
app/Generator/Chart/Basic/ChartJsGenerator.php
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<?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'
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function multiSet(array $data): array
|
||||||
|
{
|
||||||
|
reset($data);
|
||||||
|
$first = current($data);
|
||||||
|
$labels = is_array($first['entries']) ? 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'][] = $value;
|
||||||
|
$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;
|
|
||||||
|
|
||||||
}
|
|
||||||
173
app/Generator/Report/Audit/MonthReportGenerator.php
Normal file
173
app/Generator/Report/Audit/MonthReportGenerator.php
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<?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\JournalCollectorInterface;
|
||||||
|
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;
|
||||||
|
$auditData[$id] = $this->getAuditReport($account, $dayBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getAuditReport(Account $account, Carbon $date): array
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$journals = $journals->reverse();
|
||||||
|
$dayBeforeBalance = Steam::balance($account, $date);
|
||||||
|
$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.
|
||||||
|
*/
|
||||||
|
$return = [
|
||||||
|
'journals' => $journals->reverse(),
|
||||||
|
'exists' => $journals->count() > 0,
|
||||||
|
'end' => $this->end->formatLocalized(strval(trans('config.month_and_day'))),
|
||||||
|
'endBalance' => Steam::balance($account, $this->end),
|
||||||
|
'dayBefore' => $date->formatLocalized(strval(trans('config.month_and_day'))),
|
||||||
|
'dayBeforeBalance' => $dayBeforeBalance,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
}
|
||||||
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.
|
||||||
|
*/
|
||||||
|
}
|
||||||
248
app/Generator/Report/Budget/MonthReportGenerator.php
Normal file
248
app/Generator/Report/Budget/MonthReportGenerator.php
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<?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\JournalCollectorInterface;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [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.
|
||||||
|
*/
|
||||||
|
}
|
||||||
331
app/Generator/Report/Category/MonthReportGenerator.php
Normal file
331
app/Generator/Report/Category/MonthReportGenerator.php
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<?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\JournalCollectorInterface;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
||||||
|
* @param array $spent
|
||||||
|
* @param array $earned
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getObjectSummary(array $spent, array $earned): array
|
||||||
|
{
|
||||||
|
$return = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int $accountId
|
||||||
|
* @var string $entry
|
||||||
|
*/
|
||||||
|
foreach ($spent as $objectId => $entry) {
|
||||||
|
if (!isset($return[$objectId])) {
|
||||||
|
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$objectId]['spent'] = $entry;
|
||||||
|
}
|
||||||
|
unset($entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int $accountId
|
||||||
|
* @var string $entry
|
||||||
|
*/
|
||||||
|
foreach ($earned as $objectId => $entry) {
|
||||||
|
if (!isset($return[$objectId])) {
|
||||||
|
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$objectId]['earned'] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getTopExpenses(): Collection
|
||||||
|
{
|
||||||
|
$transactions = $this->getExpenses()->sortBy('transaction_amount');
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getTopIncome(): Collection
|
||||||
|
{
|
||||||
|
$transactions = $this->getIncome()->sortByDesc('transaction_amount');
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function summarizeByAccount(Collection $collection): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($collection as $transaction) {
|
||||||
|
$accountId = $transaction->account_id;
|
||||||
|
$result[$accountId] = $result[$accountId] ?? '0';
|
||||||
|
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
app/Generator/Report/Support.php
Normal file
83
app/Generator/Report/Support.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
return self::filterTransactions($collection, $accounts, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
* @param array $accounts
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public static function filterIncome(Collection $collection, array $accounts): Collection
|
||||||
|
{
|
||||||
|
return self::filterTransactions($collection, $accounts, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
* @param array $accounts
|
||||||
|
* @param int $modifier
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public static function filterTransactions(Collection $collection, array $accounts, int $modifier): Collection
|
||||||
|
{
|
||||||
|
$result = $collection->filter(
|
||||||
|
function (Transaction $transaction) use ($accounts, $modifier) {
|
||||||
|
$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') === $modifier) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* BudgetEventHandler.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\Handlers\Events;
|
|
||||||
|
|
||||||
|
|
||||||
use FireflyIII\Events\StoredBudgetLimit;
|
|
||||||
use FireflyIII\Events\UpdatedBudgetLimit;
|
|
||||||
use FireflyIII\Models\LimitRepetition;
|
|
||||||
use Illuminate\Database\QueryException;
|
|
||||||
use Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles budget related events.
|
|
||||||
*
|
|
||||||
* Class BudgetEventHandler
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Handlers\Events
|
|
||||||
*/
|
|
||||||
class BudgetEventHandler
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method creates a new budget limit repetition when a new budget limit has been created.
|
|
||||||
*
|
|
||||||
* @param StoredBudgetLimit $event
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function storeRepetition(StoredBudgetLimit $event):bool
|
|
||||||
{
|
|
||||||
$budgetLimit = $event->budgetLimit;
|
|
||||||
$end = $event->end;
|
|
||||||
$set = $budgetLimit->limitrepetitions()
|
|
||||||
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
|
|
||||||
->where('enddate', $end->format('Y-m-d 00:00:00'))
|
|
||||||
->get();
|
|
||||||
if ($set->count() == 0) {
|
|
||||||
$repetition = new LimitRepetition;
|
|
||||||
$repetition->startdate = $budgetLimit->startdate;
|
|
||||||
$repetition->enddate = $end;
|
|
||||||
$repetition->amount = $budgetLimit->amount;
|
|
||||||
$repetition->budgetLimit()->associate($budgetLimit);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$repetition->save();
|
|
||||||
} catch (QueryException $e) {
|
|
||||||
Log::error('Trying to save new LimitRepetition failed: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($set->count() == 1) {
|
|
||||||
$repetition = $set->first();
|
|
||||||
$repetition->amount = $budgetLimit->amount;
|
|
||||||
$repetition->save();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates, if present the budget limit repetition part of a budget limit.
|
|
||||||
*
|
|
||||||
* @param UpdatedBudgetLimit $event
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function update(UpdatedBudgetLimit $event): bool
|
|
||||||
{
|
|
||||||
$budgetLimit = $event->budgetLimit;
|
|
||||||
$end = $event->end;
|
|
||||||
$set = $budgetLimit->limitrepetitions()
|
|
||||||
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
|
|
||||||
->where('enddate', $end->format('Y-m-d 00:00:00'))
|
|
||||||
->get();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,11 +16,13 @@ namespace FireflyIII\Handlers\Events;
|
|||||||
use FireflyIII\Events\StoredTransactionJournal;
|
use FireflyIII\Events\StoredTransactionJournal;
|
||||||
use FireflyIII\Models\PiggyBank;
|
use FireflyIII\Models\PiggyBank;
|
||||||
use FireflyIII\Models\PiggyBankEvent;
|
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\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
|
||||||
@@ -41,31 +43,31 @@ class StoredJournalEventHandler
|
|||||||
/** @var TransactionJournal $journal */
|
/** @var TransactionJournal $journal */
|
||||||
$journal = $event->journal;
|
$journal = $event->journal;
|
||||||
$piggyBankId = $event->piggyBankId;
|
$piggyBankId = $event->piggyBankId;
|
||||||
|
Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId));
|
||||||
|
|
||||||
/** @var PiggyBank $piggyBank */
|
/*
|
||||||
$piggyBank = $journal->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
* Verify existence of piggy bank:
|
||||||
|
*/
|
||||||
|
if (!$this->verifyExistence($event)) {
|
||||||
|
Log::error(sprintf('No such piggy bank or no repetition on %s', $journal->date->format('Y-m-d')));
|
||||||
|
|
||||||
if (is_null($piggyBank)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// update piggy bank rep for date of transaction journal.
|
|
||||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
|
||||||
if (is_null($repetition)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$amount = TransactionJournal::amountPositive($journal);
|
/*
|
||||||
// if piggy account matches source account, the amount is positive
|
* Get relevant data:
|
||||||
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
|
*/
|
||||||
if (in_array($piggyBank->account_id, $sources)) {
|
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
||||||
$amount = bcmul($amount, '-1');
|
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||||
}
|
$amount = $this->getExactAmount($journal, $piggyBank, $repetition);
|
||||||
|
|
||||||
|
|
||||||
$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 $event */
|
||||||
|
$event = PiggyBankEvent::create(
|
||||||
|
['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]
|
||||||
|
);
|
||||||
|
Log::debug(sprintf('Created piggy bank event #%d', $event->id));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -73,14 +75,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,15 +112,92 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but I can live with it.
|
||||||
|
* @param TransactionJournal $journal
|
||||||
|
* @param PiggyBank $piggyBank
|
||||||
|
* @param PiggyBankRepetition $repetition
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getExactAmount(TransactionJournal $journal, PiggyBank $piggyBank, PiggyBankRepetition $repetition): string
|
||||||
|
{
|
||||||
|
$amount = TransactionJournal::amountPositive($journal);
|
||||||
|
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
|
||||||
|
$room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount));
|
||||||
|
$compare = bcmul($repetition->currentamount, '-1');
|
||||||
|
|
||||||
|
Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
||||||
|
|
||||||
|
// if piggy account matches source account, the amount is positive
|
||||||
|
if (in_array($piggyBank->account_id, $sources)) {
|
||||||
|
$amount = bcmul($amount, '-1');
|
||||||
|
Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if the amount is positive, make sure it fits in piggy bank:
|
||||||
|
if (bccomp($amount, '0') === 1 && bccomp($room, $amount) === -1) {
|
||||||
|
// amount is positive and $room is smaller than $amount
|
||||||
|
Log::debug(sprintf('Room in piggy bank for extra money is %f', $room));
|
||||||
|
Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
||||||
|
Log::debug(sprintf('New amount is %f', $room));
|
||||||
|
|
||||||
|
return $room;
|
||||||
|
}
|
||||||
|
|
||||||
|
// amount is negative and $currentamount is smaller than $amount
|
||||||
|
if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) {
|
||||||
|
Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount));
|
||||||
|
Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
||||||
|
Log::debug(sprintf('New amount is %f', $compare));
|
||||||
|
|
||||||
|
return $compare;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StoredTransactionJournal $event
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function verifyExistence(StoredTransactionJournal $event): bool
|
||||||
|
{
|
||||||
|
/** @var TransactionJournal $journal */
|
||||||
|
$journal = $event->journal;
|
||||||
|
$piggyBankId = $event->piggyBankId;
|
||||||
|
|
||||||
|
/** @var PiggyBank $piggyBank */
|
||||||
|
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
||||||
|
|
||||||
|
if (is_null($piggyBank)) {
|
||||||
|
Log::error('No such piggy bank!');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name));
|
||||||
|
// update piggy bank rep for date of transaction journal.
|
||||||
|
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||||
|
if (is_null($repetition)) {
|
||||||
|
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -13,15 +13,12 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Handlers\Events;
|
namespace FireflyIII\Handlers\Events;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use FireflyIII\Events\ConfirmedUser;
|
|
||||||
use FireflyIII\Events\RegisteredUser;
|
use FireflyIII\Events\RegisteredUser;
|
||||||
use FireflyIII\Events\ResentConfirmation;
|
use FireflyIII\Events\RequestedNewPassword;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Log;
|
use Log;
|
||||||
use Mail;
|
use Mail;
|
||||||
use Preferences;
|
|
||||||
use Session;
|
use Session;
|
||||||
use Swift_TransportException;
|
use Swift_TransportException;
|
||||||
|
|
||||||
@@ -32,7 +29,6 @@ use Swift_TransportException;
|
|||||||
*
|
*
|
||||||
* The method name reflects what is being done. This is in the present tense.
|
* The method name reflects what is being done. This is in the present tense.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @package FireflyIII\Handlers\Events
|
* @package FireflyIII\Handlers\Events
|
||||||
*/
|
*/
|
||||||
class UserEventHandler
|
class UserEventHandler
|
||||||
@@ -63,7 +59,7 @@ class UserEventHandler
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function onUserLogout(): bool
|
public function logoutUser(): bool
|
||||||
{
|
{
|
||||||
// dump stuff from the session:
|
// dump stuff from the session:
|
||||||
Session::forget('twofactor-authenticated');
|
Session::forget('twofactor-authenticated');
|
||||||
@@ -73,90 +69,32 @@ class UserEventHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will send a newly registered user a confirmation message, urging him or her to activate their account.
|
* @param RequestedNewPassword $event
|
||||||
*
|
|
||||||
* @param RegisteredUser $event
|
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function sendConfirmationMessage(RegisteredUser $event): bool
|
public function sendNewPassword(RequestedNewPassword $event): bool
|
||||||
{
|
{
|
||||||
$user = $event->user;
|
$email = $event->user->email;
|
||||||
$ipAddress = $event->ipAddress;
|
$ipAddress = $event->ipAddress;
|
||||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
$token = $event->token;
|
||||||
if ($confirmAccount === false) {
|
|
||||||
Preferences::setForUser($user, 'user_confirmed', true);
|
|
||||||
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
|
||||||
Preferences::mark();
|
|
||||||
|
|
||||||
return true;
|
$url = route('password.reset', [$token]);
|
||||||
}
|
|
||||||
$email = $user->email;
|
// send 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 {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If the user has somehow lost his or her confirmation message, this event will send it to the user again.
|
|
||||||
*
|
|
||||||
* At the moment, this method is exactly the same as the ::sendConfirmationMessage method, but that will change.
|
|
||||||
*
|
|
||||||
* @param ResentConfirmation $event
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
function sendConfirmationMessageAgain(ResentConfirmation $event): bool
|
|
||||||
{
|
|
||||||
$user = $event->user;
|
|
||||||
$ipAddress = $event->ipAddress;
|
|
||||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
|
||||||
if ($confirmAccount === false) {
|
|
||||||
Preferences::setForUser($user, 'user_confirmed', true);
|
|
||||||
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
|
||||||
Preferences::mark();
|
|
||||||
|
|
||||||
return 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will send the user a registration mail, welcoming him or her to Firefly III.
|
* This method will send the user a registration mail, welcoming him or her to Firefly III.
|
||||||
* This message is only sent when the configuration of Firefly III says so.
|
* This message is only sent when the configuration of Firefly III says so.
|
||||||
@@ -179,8 +117,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) {
|
||||||
@@ -189,37 +127,4 @@ class UserEventHandler
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When the user is confirmed, this method stores the IP address of the user
|
|
||||||
* as a preference. Since this preference cannot be edited, it is effectively hidden
|
|
||||||
* from the user yet stored conveniently.
|
|
||||||
*
|
|
||||||
* @param ConfirmedUser $event
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function storeConfirmationIpAddress(ConfirmedUser $event): bool
|
|
||||||
{
|
|
||||||
Preferences::setForUser($event->user, 'confirmation_ip_address', $event->ipAddress);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This message stores the users IP address on registration, in much the same
|
|
||||||
* fashion as the previous method.
|
|
||||||
*
|
|
||||||
* @param RegisteredUser $event
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function storeRegistrationIpAddress(RegisteredUser $event): bool
|
|
||||||
{
|
|
||||||
Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,11 +16,8 @@ use Crypt;
|
|||||||
use FireflyIII\Models\Attachment;
|
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 Log;
|
|
||||||
use Storage;
|
use Storage;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use TypeError;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AttachmentHelper
|
* Class AttachmentHelper
|
||||||
@@ -35,9 +32,9 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
/** @var MessageBag */
|
/** @var MessageBag */
|
||||||
public $messages;
|
public $messages;
|
||||||
/** @var array */
|
/** @var array */
|
||||||
protected $allowedMimes;
|
protected $allowedMimes = [];
|
||||||
/** @var int */
|
/** @var int */
|
||||||
protected $maxUploadSize;
|
protected $maxUploadSize = 0;
|
||||||
|
|
||||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||||
protected $uploadDisk;
|
protected $uploadDisk;
|
||||||
@@ -47,8 +44,8 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->maxUploadSize = config('firefly.maxUploadSize');
|
$this->maxUploadSize = intval(config('firefly.maxUploadSize'));
|
||||||
$this->allowedMimes = config('firefly.allowedMimes');
|
$this->allowedMimes = (array) config('firefly.allowedMimes');
|
||||||
$this->errors = new MessageBag;
|
$this->errors = new MessageBag;
|
||||||
$this->messages = new MessageBag;
|
$this->messages = new MessageBag;
|
||||||
$this->uploadDisk = Storage::disk('upload');
|
$this->uploadDisk = Storage::disk('upload');
|
||||||
@@ -83,20 +80,19 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Model $model
|
* @param Model $model
|
||||||
|
* @param array|null $files
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function saveAttachmentsForModel(Model $model): bool
|
public function saveAttachmentsForModel(Model $model, array $files = null): bool
|
||||||
{
|
{
|
||||||
$files = $this->getFiles();
|
|
||||||
|
|
||||||
if (!is_null($files) && !is_array($files)) {
|
|
||||||
$this->processFile($files, $model);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($files)) {
|
if (is_array($files)) {
|
||||||
$this->processFiles($files, $model);
|
foreach ($files as $entry) {
|
||||||
|
if (!is_null($entry)) {
|
||||||
|
$this->processFile($entry, $model);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -229,42 +225,4 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array|null|UploadedFile
|
|
||||||
*/
|
|
||||||
private function getFiles()
|
|
||||||
{
|
|
||||||
$files = null;
|
|
||||||
try {
|
|
||||||
if (Input::hasFile('attachments')) {
|
|
||||||
$files = Input::file('attachments');
|
|
||||||
}
|
|
||||||
} catch (TypeError $e) {
|
|
||||||
// Log it, do nothing else.
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $files;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $files
|
|
||||||
*
|
|
||||||
* @param Model $model
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function processFiles(array $files, Model $model): bool
|
|
||||||
{
|
|
||||||
foreach ($files as $entry) {
|
|
||||||
if (!is_null($entry)) {
|
|
||||||
$this->processFile($entry, $model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,6 @@ interface AttachmentHelperInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function saveAttachmentsForModel(Model $model): bool;
|
public function saveAttachmentsForModel(Model $model, array $files = null): bool;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
278
app/Helpers/Chart/MetaPieChart.php
Normal file
278
app/Helpers/Chart/MetaPieChart.php
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MetaPieChart.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Chart;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\Support;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MetaPieChart
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Chart
|
||||||
|
*/
|
||||||
|
class MetaPieChart implements MetaPieChartInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
protected $accounts;
|
||||||
|
/** @var Collection */
|
||||||
|
protected $budgets;
|
||||||
|
/** @var Collection */
|
||||||
|
protected $categories;
|
||||||
|
/** @var bool */
|
||||||
|
protected $collectOtherObjects = false;
|
||||||
|
/** @var Carbon */
|
||||||
|
protected $end;
|
||||||
|
/** @var array */
|
||||||
|
protected $grouping
|
||||||
|
= [
|
||||||
|
'account' => ['opposing_account_id'],
|
||||||
|
'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'],
|
||||||
|
'category' => ['transaction_journal_category_id', 'transaction_category_id'],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $repositories
|
||||||
|
= [
|
||||||
|
'account' => AccountRepositoryInterface::class,
|
||||||
|
'budget' => BudgetRepositoryInterface::class,
|
||||||
|
'category' => CategoryRepositoryInterface::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/** @var Carbon */
|
||||||
|
protected $start;
|
||||||
|
/** @var string */
|
||||||
|
protected $total = '0';
|
||||||
|
/** @var User */
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->accounts = new Collection;
|
||||||
|
$this->budgets = new Collection;
|
||||||
|
$this->categories = new Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $direction
|
||||||
|
* @param string $group
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function generate(string $direction, string $group): array
|
||||||
|
{
|
||||||
|
$transactions = $this->getTransactions($direction);
|
||||||
|
$grouped = $this->groupByFields($transactions, $this->grouping[$group]);
|
||||||
|
$chartData = $this->organizeByType($group, $grouped);
|
||||||
|
|
||||||
|
// also collect all other transactions
|
||||||
|
if ($this->collectOtherObjects && $direction === 'expense') {
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [$this->user]);
|
||||||
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcmul($sum, '-1');
|
||||||
|
$sum = bcsub($sum, $this->total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->collectOtherObjects && $direction === 'income') {
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||||
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcsub($sum, $this->total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chartData;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setBudgets(Collection $budgets): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->budgets = $budgets;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $categories
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setCategories(Collection $categories): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->categories = $categories;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $collectOtherObjects
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setCollectOtherObjects(bool $collectOtherObjects): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->collectOtherObjects = $collectOtherObjects;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setEnd(Carbon $end): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->end = $end;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $start
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setStart(Carbon $start): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->start = $start;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $user
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setUser(User $user): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTransactions(string $direction)
|
||||||
|
{
|
||||||
|
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
|
||||||
|
$modifier = -1;
|
||||||
|
if ($direction === 'expense') {
|
||||||
|
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
|
||||||
|
$modifier = 1;
|
||||||
|
}
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||||
|
$collector->setAccounts($this->accounts);
|
||||||
|
$collector->setRange($this->start, $this->end);
|
||||||
|
$collector->setTypes($types);
|
||||||
|
$collector->withOpposingAccount();
|
||||||
|
|
||||||
|
if ($direction === 'income') {
|
||||||
|
$collector->disableFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->budgets->count() > 0) {
|
||||||
|
$collector->setBudgets($this->budgets);
|
||||||
|
}
|
||||||
|
if ($this->categories->count() > 0) {
|
||||||
|
$collector->setCategories($this->categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
$accountIds = $this->accounts->pluck('id')->toArray();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$set = Support::filterTransactions($transactions, $accountIds, $modifier);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
* @param array $fields
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function groupByFields(Collection $set, array $fields)
|
||||||
|
{
|
||||||
|
$grouped = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($set as $transaction) {
|
||||||
|
$values = [];
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$values[] = intval($transaction->$field);
|
||||||
|
}
|
||||||
|
$value = max($values);
|
||||||
|
$grouped[$value] = $grouped[$value] ?? '0';
|
||||||
|
$grouped[$value] = bcadd($transaction->transaction_amount, $grouped[$value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $type
|
||||||
|
* @param array $array
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function organizeByType(string $type, array $array): array
|
||||||
|
{
|
||||||
|
$chartData = [];
|
||||||
|
$names = [];
|
||||||
|
$repository = app($this->repositories[$type], [$this->user]);
|
||||||
|
foreach ($array as $objectId => $amount) {
|
||||||
|
if (!isset($names[$objectId])) {
|
||||||
|
$object = $repository->find(intval($objectId));
|
||||||
|
$names[$objectId] = $object->name;
|
||||||
|
}
|
||||||
|
if (bccomp($amount, '0') === -1) {
|
||||||
|
$amount = bcmul($amount, '-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->total = bcadd($this->total, $amount);
|
||||||
|
$chartData[$names[$objectId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chartData;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
82
app/Helpers/Chart/MetaPieChartInterface.php
Normal file
82
app/Helpers/Chart/MetaPieChartInterface.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MetaPieChartInterface.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Chart;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface MetaPieChartInterface
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Chart
|
||||||
|
*/
|
||||||
|
interface MetaPieChartInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $direction
|
||||||
|
* @param string $group
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function generate(string $direction, string $group): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): MetaPieChartInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setBudgets(Collection $budgets): MetaPieChartInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $categories
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setCategories(Collection $categories): MetaPieChartInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $collectOtherObjects
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setCollectOtherObjects(bool $collectOtherObjects): MetaPieChartInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setEnd(Carbon $end): MetaPieChartInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $start
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setStart(Carbon $start): MetaPieChartInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $user
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setUser(User $user): MetaPieChartInterface;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Account.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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Account
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Helpers\Collection
|
|
||||||
*/
|
|
||||||
class Account
|
|
||||||
{
|
|
||||||
|
|
||||||
/** @var Collection */
|
|
||||||
protected $accounts;
|
|
||||||
/** @var string */
|
|
||||||
protected $difference = '';
|
|
||||||
/** @var string */
|
|
||||||
protected $end = '';
|
|
||||||
/** @var string */
|
|
||||||
protected $start = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Account constructor.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->accounts = new Collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function getAccounts(): Collection
|
|
||||||
{
|
|
||||||
return $this->accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $accounts
|
|
||||||
*/
|
|
||||||
public function setAccounts(Collection $accounts)
|
|
||||||
{
|
|
||||||
$this->accounts = $accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDifference(): string
|
|
||||||
{
|
|
||||||
return $this->difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $difference
|
|
||||||
*/
|
|
||||||
public function setDifference(string $difference)
|
|
||||||
{
|
|
||||||
$this->difference = $difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getEnd(): string
|
|
||||||
{
|
|
||||||
return $this->end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $end
|
|
||||||
*/
|
|
||||||
public function setEnd(string $end)
|
|
||||||
{
|
|
||||||
$this->end = $end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getStart(): string
|
|
||||||
{
|
|
||||||
return $this->start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $start
|
|
||||||
*/
|
|
||||||
public function setStart(string $start)
|
|
||||||
{
|
|
||||||
$this->start = $start;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,7 @@ namespace FireflyIII\Helpers\Collection;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Models\Budget as BudgetModel;
|
use FireflyIII\Models\Budget as BudgetModel;
|
||||||
|
use FireflyIII\Models\BudgetLimit;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,12 +35,10 @@ class BalanceLine
|
|||||||
|
|
||||||
/** @var BudgetModel */
|
/** @var BudgetModel */
|
||||||
protected $budget;
|
protected $budget;
|
||||||
/** @var Carbon */
|
/** @var BudgetLimit */
|
||||||
protected $endDate;
|
protected $budgetLimit;
|
||||||
/** @var int */
|
/** @var int */
|
||||||
protected $role = self::ROLE_DEFAULTROLE;
|
protected $role = self::ROLE_DEFAULTROLE;
|
||||||
/** @var Carbon */
|
|
||||||
protected $startDate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -90,20 +89,28 @@ class BalanceLine
|
|||||||
$this->budget = $budget;
|
$this->budget = $budget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BudgetLimit
|
||||||
|
*/
|
||||||
|
public function getBudgetLimit(): BudgetLimit
|
||||||
|
{
|
||||||
|
return $this->budgetLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BudgetLimit $budgetLimit
|
||||||
|
*/
|
||||||
|
public function setBudgetLimit(BudgetLimit $budgetLimit)
|
||||||
|
{
|
||||||
|
$this->budgetLimit = $budgetLimit;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Carbon
|
* @return Carbon
|
||||||
*/
|
*/
|
||||||
public function getEndDate()
|
public function getEndDate()
|
||||||
{
|
{
|
||||||
return $this->endDate;
|
return $this->budgetLimit->end_date ?? new Carbon;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Carbon $endDate
|
|
||||||
*/
|
|
||||||
public function setEndDate($endDate)
|
|
||||||
{
|
|
||||||
$this->endDate = $endDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,18 +134,11 @@ class BalanceLine
|
|||||||
*/
|
*/
|
||||||
public function getStartDate()
|
public function getStartDate()
|
||||||
{
|
{
|
||||||
return $this->startDate;
|
return $this->budgetLimit->start_date ?? new Carbon;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Carbon $startDate
|
|
||||||
*/
|
|
||||||
public function setStartDate($startDate)
|
|
||||||
{
|
|
||||||
$this->startDate = $startDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTitle(): string
|
public function getTitle(): string
|
||||||
@@ -147,13 +147,13 @@ class BalanceLine
|
|||||||
return $this->getBudget()->name;
|
return $this->getBudget()->name;
|
||||||
}
|
}
|
||||||
if ($this->getRole() == self::ROLE_DEFAULTROLE) {
|
if ($this->getRole() == self::ROLE_DEFAULTROLE) {
|
||||||
return trans('firefly.no_budget');
|
return strval(trans('firefly.no_budget'));
|
||||||
}
|
}
|
||||||
if ($this->getRole() == self::ROLE_TAGROLE) {
|
if ($this->getRole() == self::ROLE_TAGROLE) {
|
||||||
return trans('firefly.coveredWithTags');
|
return strval(trans('firefly.coveredWithTags'));
|
||||||
}
|
}
|
||||||
if ($this->getRole() == self::ROLE_DIFFROLE) {
|
if ($this->getRole() == self::ROLE_DIFFROLE) {
|
||||||
return trans('firefly.leftUnbalanced');
|
return strval(trans('firefly.leftUnbalanced'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
@@ -169,7 +169,7 @@ class BalanceLine
|
|||||||
*/
|
*/
|
||||||
public function leftOfRepetition(): string
|
public function leftOfRepetition(): string
|
||||||
{
|
{
|
||||||
$start = $this->budget->amount ?? '0';
|
$start = $this->budgetLimit->amount ?? '0';
|
||||||
/** @var BalanceEntry $balanceEntry */
|
/** @var BalanceEntry $balanceEntry */
|
||||||
foreach ($this->getBalanceEntries() as $balanceEntry) {
|
foreach ($this->getBalanceEntries() as $balanceEntry) {
|
||||||
$start = bcadd($balanceEntry->getSpent(), $start);
|
$start = bcadd($balanceEntry->getSpent(), $start);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ class Budget
|
|||||||
*/
|
*/
|
||||||
public function addBudgeted(string $add): Budget
|
public function addBudgeted(string $add): Budget
|
||||||
{
|
{
|
||||||
$add = strval(round($add, 2));
|
|
||||||
$this->budgeted = bcadd($this->budgeted, $add);
|
$this->budgeted = bcadd($this->budgeted, $add);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -73,7 +72,6 @@ class Budget
|
|||||||
*/
|
*/
|
||||||
public function addLeft(string $add): Budget
|
public function addLeft(string $add): Budget
|
||||||
{
|
{
|
||||||
$add = strval(round($add, 2));
|
|
||||||
$this->left = bcadd($this->left, $add);
|
$this->left = bcadd($this->left, $add);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -86,7 +84,6 @@ class Budget
|
|||||||
*/
|
*/
|
||||||
public function addOverspent(string $add): Budget
|
public function addOverspent(string $add): Budget
|
||||||
{
|
{
|
||||||
$add = strval(round($add, 2));
|
|
||||||
$this->overspent = bcadd($this->overspent, $add);
|
$this->overspent = bcadd($this->overspent, $add);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -99,7 +96,6 @@ class Budget
|
|||||||
*/
|
*/
|
||||||
public function addSpent(string $add): Budget
|
public function addSpent(string $add): Budget
|
||||||
{
|
{
|
||||||
$add = strval(round($add, 2));
|
|
||||||
$this->spent = bcadd($this->spent, $add);
|
$this->spent = bcadd($this->spent, $add);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -168,7 +164,7 @@ class Budget
|
|||||||
*/
|
*/
|
||||||
public function setOverspent(string $overspent): Budget
|
public function setOverspent(string $overspent): Budget
|
||||||
{
|
{
|
||||||
$this->overspent = strval(round($overspent, 2));
|
$this->overspent = $overspent;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -188,7 +184,7 @@ class Budget
|
|||||||
*/
|
*/
|
||||||
public function setSpent(string $spent): Budget
|
public function setSpent(string $spent): Budget
|
||||||
{
|
{
|
||||||
$this->spent = strval(round($spent, 2));
|
$this->spent = $spent;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Helpers\Collection;
|
namespace FireflyIII\Helpers\Collection;
|
||||||
|
|
||||||
use FireflyIII\Models\Budget as BudgetModel;
|
use FireflyIII\Models\Budget as BudgetModel;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -26,14 +26,14 @@ class BudgetLine
|
|||||||
|
|
||||||
/** @var BudgetModel */
|
/** @var BudgetModel */
|
||||||
protected $budget;
|
protected $budget;
|
||||||
|
/** @var BudgetLimit */
|
||||||
|
protected $budgetLimit;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $budgeted = '0';
|
protected $budgeted = '0';
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $left = '0';
|
protected $left = '0';
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $overspent = '0';
|
protected $overspent = '0';
|
||||||
/** @var LimitRepetition */
|
|
||||||
protected $repetition;
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $spent = '0';
|
protected $spent = '0';
|
||||||
|
|
||||||
@@ -57,6 +57,26 @@ class BudgetLine
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BudgetLimit
|
||||||
|
*/
|
||||||
|
public function getBudgetLimit(): BudgetLimit
|
||||||
|
{
|
||||||
|
return $this->budgetLimit ?? new BudgetLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BudgetLimit $budgetLimit
|
||||||
|
*
|
||||||
|
* @return BudgetLimit
|
||||||
|
*/
|
||||||
|
public function setBudgetLimit(BudgetLimit $budgetLimit): BudgetLine
|
||||||
|
{
|
||||||
|
$this->budgetLimit = $budgetLimit;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -117,26 +137,6 @@ class BudgetLine
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return LimitRepetition
|
|
||||||
*/
|
|
||||||
public function getRepetition(): LimitRepetition
|
|
||||||
{
|
|
||||||
return $this->repetition ?? new LimitRepetition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param LimitRepetition $repetition
|
|
||||||
*
|
|
||||||
* @return BudgetLine
|
|
||||||
*/
|
|
||||||
public function setRepetition(LimitRepetition $repetition): BudgetLine
|
|
||||||
{
|
|
||||||
$this->repetition = $repetition;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ class Category
|
|||||||
*/
|
*/
|
||||||
public function addTotal(string $add)
|
public function addTotal(string $add)
|
||||||
{
|
{
|
||||||
$add = strval(round($add, 2));
|
|
||||||
$this->total = bcadd($this->total, $add);
|
$this->total = bcadd($this->total, $add);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ class Category
|
|||||||
*/
|
*/
|
||||||
public function getTotal(): string
|
public function getTotal(): string
|
||||||
{
|
{
|
||||||
return strval(round($this->total, 2));
|
return $this->total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
755
app/Helpers/Collector/JournalCollector.php
Normal file
755
app/Helpers/Collector/JournalCollector.php
Normal file
@@ -0,0 +1,755 @@
|
|||||||
|
<?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;
|
||||||
|
/** @var Collection $set */
|
||||||
|
$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::info('Cannot filter internal transfers because no opposing information is present.');
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
$accountIds = $this->accountIds;
|
||||||
|
$set = $set->filter(
|
||||||
|
function (Transaction $transaction) use ($accountIds) {
|
||||||
|
// both id's in $accountids?
|
||||||
|
if (in_array($transaction->account_id, $accountIds) && in_array($transaction->opposing_account_id, $accountIds)) {
|
||||||
|
Log::debug(
|
||||||
|
sprintf(
|
||||||
|
'Transaction #%d has #%d and #%d in set, so removed',
|
||||||
|
$transaction->id, $transaction->account_id, $transaction->opposing_account_id
|
||||||
|
), $accountIds
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the set of accounts used by the collector includes more than one asset
|
||||||
|
* account, chances are the set include double entries: transfers get selected
|
||||||
|
* on both the source, and then again on the destination account.
|
||||||
|
*
|
||||||
|
* This method filters them out.
|
||||||
|
*
|
||||||
|
* @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('budgets as transaction_journal_budgets', 'transaction_journal_budgets.id', '=', 'budget_transaction_journal.budget_id');
|
||||||
|
$this->query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
|
||||||
|
$this->query->leftJoin('budgets as transaction_budgets', 'transaction_budgets.id', '=', 'budget_transaction.budget_id');
|
||||||
|
|
||||||
|
$this->fields[] = 'budget_transaction_journal.budget_id as transaction_journal_budget_id';
|
||||||
|
$this->fields[] = 'transaction_journal_budgets.encrypted as transaction_journal_budget_encrypted';
|
||||||
|
$this->fields[] = 'transaction_journal_budgets.name as transaction_journal_budget_name';
|
||||||
|
|
||||||
|
$this->fields[] = 'budget_transaction.budget_id as transaction_budget_id';
|
||||||
|
$this->fields[] = 'transaction_budgets.encrypted as transaction_budget_encrypted';
|
||||||
|
$this->fields[] = 'transaction_budgets.name as transaction_budget_name';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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(
|
||||||
|
'categories as transaction_journal_categories', 'transaction_journal_categories.id', '=', 'category_transaction_journal.category_id'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
|
||||||
|
$this->query->leftJoin('categories as transaction_categories', 'transaction_categories.id', '=', 'category_transaction.category_id');
|
||||||
|
|
||||||
|
$this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id';
|
||||||
|
$this->fields[] = 'transaction_journal_categories.encrypted as transaction_journal_category_encrypted';
|
||||||
|
$this->fields[] = 'transaction_journal_categories.name as transaction_journal_category_name';
|
||||||
|
|
||||||
|
$this->fields[] = 'category_transaction.category_id as transaction_category_id';
|
||||||
|
$this->fields[] = 'transaction_categories.encrypted as transaction_category_encrypted';
|
||||||
|
$this->fields[] = 'transaction_categories.name as transaction_category_name';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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,52 +26,56 @@ 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 $key
|
* @param string $language
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getFromCache(string $key): string
|
public function getFromCache(string $route, string $language): string
|
||||||
{
|
{
|
||||||
return Cache::get($key);
|
$line = sprintf('help.%s.%s', $route, $language);
|
||||||
|
|
||||||
|
return Cache::get($line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $language
|
* @param string $language
|
||||||
* @param string $route
|
* @param string $route
|
||||||
*
|
*
|
||||||
* @return array
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getFromGithub(string $language, string $route): array
|
public function getFromGithub(string $language, string $route): string
|
||||||
{
|
{
|
||||||
|
|
||||||
$uri = sprintf('https://raw.githubusercontent.com/JC5/firefly-iii-help/master/%s/%s.md', $language, $route);
|
$uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route);
|
||||||
$routeIndex = str_replace('.', '-', $route);
|
Log::debug(sprintf('Trying to get %s...', $uri));
|
||||||
$title = trans('help.' . $routeIndex);
|
$opt = ['useragent' => $this->userAgent];
|
||||||
$content = [
|
$content = '';
|
||||||
'text' => '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>',
|
try {
|
||||||
'title' => $title,
|
$result = Requests::get($uri, [], $opt);
|
||||||
];
|
} catch (Requests_Exception $e) {
|
||||||
|
Log::error($e);
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$result = Requests::get($uri);
|
Log::debug(sprintf('Status code is %d', $result->status_code));
|
||||||
|
|
||||||
|
|
||||||
if ($result->status_code === 200) {
|
if ($result->status_code === 200) {
|
||||||
$content['text'] = $result->body;
|
$content = trim($result->body);
|
||||||
}
|
}
|
||||||
|
if (strlen($content) > 0) {
|
||||||
|
Log::debug('Content is longer than zero. Expect something.');
|
||||||
if (strlen(trim($content['text'])) == 0) {
|
$converter = new CommonMarkConverter();
|
||||||
$content['text'] = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
|
$content = $converter->convertToHtml($content);
|
||||||
}
|
}
|
||||||
$converter = new CommonMarkConverter();
|
|
||||||
$content['text'] = $converter->convertToHtml($content['text']);
|
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,33 +84,47 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param string $route
|
* @param string $route
|
||||||
|
* @param string $language
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function inCache(string $route):bool
|
public function inCache(string $route, string $language): bool
|
||||||
{
|
{
|
||||||
return Cache::has('help.' . $route . '.title') && Cache::has('help.' . $route . '.text');
|
$line = sprintf('help.%s.%s', $route, $language);
|
||||||
|
$result = Cache::has($line);
|
||||||
|
if ($result) {
|
||||||
|
Log::debug(sprintf('Cache has this entry: %s', 'help.' . $route . '.' . $language));
|
||||||
|
}
|
||||||
|
if (!$result) {
|
||||||
|
Log::debug(sprintf('Cache does not have this entry: %s', 'help.' . $route . '.' . $language));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param string $route
|
* @param string $route
|
||||||
* @param string $language
|
* @param string $language
|
||||||
* @param array $content
|
* @param string $content
|
||||||
*
|
*
|
||||||
* @internal param $title
|
|
||||||
*/
|
*/
|
||||||
public function putInCache(string $route, string $language, array $content)
|
public function putInCache(string $route, string $language, string $content)
|
||||||
{
|
{
|
||||||
Cache::put('help.' . $route . '.text.' . $language, $content['text'], 10080); // a week.
|
$key = sprintf('help.%s.%s', $route, $language);
|
||||||
Cache::put('help.' . $route . '.title.' . $language, $content['title'], 10080);
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,20 @@ interface HelpInterface
|
|||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $key
|
* @param string $route
|
||||||
|
* @param string $language
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getFromCache(string $key): string;
|
public function getFromCache(string $route, string $language): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $language
|
* @param string $language
|
||||||
* @param string $route
|
* @param string $route
|
||||||
*
|
*
|
||||||
* @return array
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getFromGithub(string $language, string $route):array;
|
public function getFromGithub(string $language, string $route): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $route
|
* @param string $route
|
||||||
@@ -44,15 +45,16 @@ interface HelpInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $route
|
* @param string $route
|
||||||
|
* @param string $language
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function inCache(string $route): bool;
|
public function inCache(string $route, string $language): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $route
|
* @param string $route
|
||||||
* @param string $language
|
* @param string $language
|
||||||
* @param array $content
|
* @param string $content
|
||||||
*/
|
*/
|
||||||
public function putInCache(string $route, string $language, array $content);
|
public function putInCache(string $route, string $language, string $content);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,17 +19,18 @@ use FireflyIII\Helpers\Collection\Balance;
|
|||||||
use FireflyIII\Helpers\Collection\BalanceEntry;
|
use FireflyIII\Helpers\Collection\BalanceEntry;
|
||||||
use FireflyIII\Helpers\Collection\BalanceHeader;
|
use FireflyIII\Helpers\Collection\BalanceHeader;
|
||||||
use FireflyIII\Helpers\Collection\BalanceLine;
|
use FireflyIII\Helpers\Collection\BalanceLine;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
|
||||||
use FireflyIII\Models\Tag;
|
use FireflyIII\Models\Tag;
|
||||||
use FireflyIII\Models\TransactionType;
|
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
|
||||||
*
|
*
|
||||||
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // I can't really help it.
|
||||||
* @package FireflyIII\Helpers\Report
|
* @package FireflyIII\Helpers\Report
|
||||||
*/
|
*/
|
||||||
class BalanceReportHelper implements BalanceReportHelperInterface
|
class BalanceReportHelper implements BalanceReportHelperInterface
|
||||||
@@ -51,27 +52,29 @@ 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
|
||||||
{
|
{
|
||||||
$balance = new Balance;
|
Log::debug('Start of balance report');
|
||||||
$header = new BalanceHeader;
|
$balance = new Balance;
|
||||||
$limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end);
|
$header = new BalanceHeader;
|
||||||
|
$budgetLimits = $this->budgetRepository->getAllBudgetLimits($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 BudgetLimit $budgetLimit */
|
||||||
foreach ($limitRepetitions as $repetition) {
|
foreach ($budgetLimits as $budgetLimit) {
|
||||||
$budget = $this->budgetRepository->find($repetition->budget_id);
|
$line = $this->createBalanceLine($budgetLimit, $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 +84,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,27 +143,22 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Budget $budget
|
* @param BudgetLimit $budgetLimit
|
||||||
* @param LimitRepetition $repetition
|
* @param Collection $accounts
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
*
|
||||||
* @return BalanceLine
|
* @return BalanceLine
|
||||||
*/
|
*/
|
||||||
private function createBalanceLine(Budget $budget, LimitRepetition $repetition, Collection $accounts): BalanceLine
|
private function createBalanceLine(BudgetLimit $budgetLimit, Collection $accounts): BalanceLine
|
||||||
{
|
{
|
||||||
$line = new BalanceLine;
|
$line = new BalanceLine;
|
||||||
$budget->amount = $repetition->amount;
|
$line->setBudget($budgetLimit->budget);
|
||||||
$line->setBudget($budget);
|
$line->setBudgetLimit($budgetLimit);
|
||||||
$line->setStartDate($repetition->startdate);
|
|
||||||
$line->setEndDate($repetition->enddate);
|
|
||||||
|
|
||||||
// loop accounts:
|
// loop accounts:
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
$balanceEntry = new BalanceEntry;
|
$balanceEntry = new BalanceEntry;
|
||||||
$balanceEntry->setAccount($account);
|
$balanceEntry->setAccount($account);
|
||||||
$spent = $this->budgetRepository->spentInPeriod(
|
$spent = $this->budgetRepository->spentInPeriod(new Collection([$budgetLimit->budget]), new Collection([$account]), $budgetLimit->start_date, $budgetLimit->end_date);
|
||||||
new Collection([$budget]), new Collection([$account]), $repetition->startdate, $repetition->enddate
|
|
||||||
);
|
|
||||||
$balanceEntry->setSpent($spent);
|
$balanceEntry->setSpent($spent);
|
||||||
$line->addBalanceEntry($balanceEntry);
|
$line->addBalanceEntry($balanceEntry);
|
||||||
}
|
}
|
||||||
@@ -212,7 +213,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
|||||||
$empty = new BalanceLine;
|
$empty = new BalanceLine;
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
$spent = $this->budgetRepository->spentInPeriodWithoutBudget(new Collection([$account]), $start, $end);
|
$spent = $this->budgetRepository->spentInPeriodWoBudget(new Collection([$account]), $start, $end);
|
||||||
// budget
|
// budget
|
||||||
$budgetEntry = new BalanceEntry;
|
$budgetEntry = new BalanceEntry;
|
||||||
$budgetEntry->setAccount($account);
|
$budgetEntry->setAccount($account);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ use Carbon\Carbon;
|
|||||||
use FireflyIII\Helpers\Collection\Budget as BudgetCollection;
|
use FireflyIII\Helpers\Collection\Budget as BudgetCollection;
|
||||||
use FireflyIII\Helpers\Collection\BudgetLine;
|
use FireflyIII\Helpers\Collection\BudgetLine;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,60 +43,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // at 43, its ok.
|
|
||||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
|
* @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
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
@@ -111,13 +57,9 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
|||||||
|
|
||||||
/** @var Budget $budget */
|
/** @var Budget $budget */
|
||||||
foreach ($set as $budget) {
|
foreach ($set as $budget) {
|
||||||
$repetitions = $budget->limitrepetitions()->before($end)->after($start)->get();
|
$budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
|
||||||
|
if ($budgetLimits->count() == 0) { // no budget limit(s) for this budget
|
||||||
// no repetition(s) for this budget:
|
$spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);// spent for budget in time range
|
||||||
if ($repetitions->count() == 0) {
|
|
||||||
// spent for budget in time range:
|
|
||||||
$spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
|
|
||||||
|
|
||||||
if ($spent > 0) {
|
if ($spent > 0) {
|
||||||
$budgetLine = new BudgetLine;
|
$budgetLine = new BudgetLine;
|
||||||
$budgetLine->setBudget($budget)->setOverspent($spent);
|
$budgetLine->setBudget($budget)->setOverspent($spent);
|
||||||
@@ -125,26 +67,20 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// one or more repetitions for budget:
|
/** @var BudgetLimit $budgetLimit */
|
||||||
/** @var LimitRepetition $repetition */
|
foreach ($budgetLimits as $budgetLimit) { // one or more repetitions for budget
|
||||||
foreach ($repetitions as $repetition) {
|
$data = $this->calculateExpenses($budget, $budgetLimit, $accounts);
|
||||||
$data = $this->calculateExpenses($budget, $repetition, $accounts);
|
|
||||||
|
|
||||||
$budgetLine = new BudgetLine;
|
$budgetLine = new BudgetLine;
|
||||||
$budgetLine->setBudget($budget)->setRepetition($repetition)
|
$budgetLine->setBudget($budget)->setBudgetLimit($budgetLimit)
|
||||||
->setLeft($data['left'])->setSpent($data['expenses'])->setOverspent($data['overspent'])
|
->setLeft($data['left'])->setSpent($data['expenses'])->setOverspent($data['overspent'])
|
||||||
->setBudgeted(strval($repetition->amount));
|
->setBudgeted(strval($budgetLimit->amount));
|
||||||
|
|
||||||
$object->addBudgeted(strval($repetition->amount))->addSpent($data['spent'])
|
$object->addBudgeted(strval($budgetLimit->amount))->addSpent($data['spent'])
|
||||||
->addLeft($data['left'])->addOverspent($data['overspent'])->addBudgetLine($budgetLine);
|
->addLeft($data['left'])->addOverspent($data['overspent'])->addBudgetLine($budgetLine);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
$noBudget = $this->repository->spentInPeriodWoBudget($accounts, $start, $end); // stuff outside of budgets
|
||||||
// stuff outside of budgets:
|
|
||||||
|
|
||||||
$noBudget = $this->repository->spentInPeriodWithoutBudget($accounts, $start, $end);
|
|
||||||
$budgetLine = new BudgetLine;
|
$budgetLine = new BudgetLine;
|
||||||
$budgetLine->setOverspent($noBudget)->setSpent($noBudget);
|
$budgetLine->setOverspent($noBudget)->setSpent($noBudget);
|
||||||
$object->addOverspent($noBudget)->addBudgetLine($budgetLine);
|
$object->addOverspent($noBudget)->addBudgetLine($budgetLine);
|
||||||
@@ -183,95 +119,22 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
|
* @param Budget $budget
|
||||||
* and sum up everything in the array in the given range.
|
* @param BudgetLimit $budgetLimit
|
||||||
*
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param array $array
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function getSumOfRange(Carbon $start, Carbon $end, array $array)
|
|
||||||
{
|
|
||||||
$sum = '0';
|
|
||||||
$currentStart = clone $start; // to not mess with the original one
|
|
||||||
$currentEnd = clone $end; // to not mess with the original one
|
|
||||||
|
|
||||||
while ($currentStart <= $currentEnd) {
|
|
||||||
$date = $currentStart->format('Y-m-d');
|
|
||||||
if (isset($array[$date])) {
|
|
||||||
$sum = bcadd($sum, $array[$date]);
|
|
||||||
}
|
|
||||||
$currentStart->addDay();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Budget $budget
|
|
||||||
* @param LimitRepetition $repetition
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function calculateExpenses(Budget $budget, LimitRepetition $repetition, Collection $accounts): array
|
private function calculateExpenses(Budget $budget, BudgetLimit $budgetLimit, Collection $accounts): array
|
||||||
{
|
{
|
||||||
$array = [];
|
$array = [];
|
||||||
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate);
|
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $budgetLimit->start_date, $budgetLimit->end_date);
|
||||||
$array['left'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : '0';
|
$array['left'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? bcadd($budgetLimit->amount, $expenses) : '0';
|
||||||
$array['spent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0';
|
$array['spent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? $expenses : '0';
|
||||||
$array['overspent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $repetition->amount);
|
$array['overspent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $budgetLimit->amount);
|
||||||
$array['expenses'] = $expenses;
|
$array['expenses'] = $expenses;
|
||||||
|
|
||||||
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,26 +37,24 @@ 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method generates a full report for the given period on all
|
* This method generates a full report for the given period on all
|
||||||
* the users bills and their payments.
|
* the users bills and their payments.
|
||||||
*
|
*
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
|
||||||
|
*
|
||||||
* Excludes bills which have not had a payment on the mentioned accounts.
|
* Excludes bills which have not had a payment on the mentioned accounts.
|
||||||
*
|
*
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
@@ -77,115 +68,41 @@ 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?
|
|
||||||
|
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
if ($billLine->isActive() || $billLine->isHit()) {
|
||||||
if ($billLine->isActive()) {
|
|
||||||
$collection->addBill($billLine);
|
$collection->addBill($billLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$collection->filterBills();
|
||||||
|
|
||||||
return $collection;
|
return $collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return CategoryCollection
|
|
||||||
*/
|
|
||||||
public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts): CategoryCollection
|
|
||||||
{
|
|
||||||
$object = new CategoryCollection;
|
|
||||||
/** @var CategoryRepositoryInterface $repository */
|
|
||||||
$repository = app(CategoryRepositoryInterface::class);
|
|
||||||
$categories = $repository->getCategories();
|
|
||||||
|
|
||||||
/** @var Category $category */
|
|
||||||
foreach ($categories as $category) {
|
|
||||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
|
|
||||||
// CategoryCollection expects the amount in $spent:
|
|
||||||
$category->spent = $spent;
|
|
||||||
$object->addCategory($category);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
*
|
*
|
||||||
@@ -230,104 +147,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,37 +42,6 @@ interface ReportHelperInterface
|
|||||||
*/
|
*/
|
||||||
public function getBillReport(Carbon $start, Carbon $end, Collection $accounts): BillCollection;
|
public function getBillReport(Carbon $start, Carbon $end, Collection $accounts): BillCollection;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return CategoryCollection
|
|
||||||
*/
|
|
||||||
public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts): 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 +49,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,17 +13,23 @@ 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\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\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\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Input;
|
use Log;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Session;
|
use Session;
|
||||||
@@ -44,20 +50,39 @@ class AccountController extends Controller
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
View::share('mainTitleIcon', 'fa-credit-card');
|
|
||||||
View::share('title', trans('firefly.accounts'));
|
// translations:
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
View::share('mainTitleIcon', 'fa-credit-card');
|
||||||
|
View::share('title', trans('firefly.accounts'));
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $what
|
* @param string $what
|
||||||
*
|
*
|
||||||
* @return View
|
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|View
|
||||||
*/
|
*/
|
||||||
public function create(string $what = 'asset')
|
public function create(string $what = 'asset')
|
||||||
{
|
{
|
||||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
/** @var CurrencyRepositoryInterface $repository */
|
||||||
$subTitle = trans('firefly.make_new_' . $what . '_account');
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
Session::flash('preFilled', []);
|
$currencies = ExpandedForm::makeSelectList($repository->get());
|
||||||
|
$defaultCurrency = Amount::getDefaultCurrency();
|
||||||
|
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||||
|
$subTitle = trans('firefly.make_new_' . $what . '_account');
|
||||||
|
$roles = [];
|
||||||
|
foreach (config('firefly.accountRoles') as $role) {
|
||||||
|
$roles[$role] = strval(trans('firefly.account_role_' . $role));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// pre fill some data
|
||||||
|
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) {
|
||||||
@@ -67,7 +92,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', 'roles'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,24 +118,32 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param Request $request
|
||||||
* @param ARI $repository
|
* @param ARI $repository
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
*/
|
*/
|
||||||
public function destroy(ARI $repository, Account $account)
|
public function destroy(Request $request, ARI $repository, Account $account)
|
||||||
{
|
{
|
||||||
$type = $account->accountType->type;
|
$type = $account->accountType->type;
|
||||||
$typeName = config('firefly.shortNamesByFullName.' . $type);
|
$typeName = config('firefly.shortNamesByFullName.' . $type);
|
||||||
$name = $account->name;
|
$name = $account->name;
|
||||||
$moveTo = $repository->find(intval(Input::get('move_account_before_delete')));
|
$accountId = $account->id;
|
||||||
|
$moveTo = $repository->find(intval($request->get('move_account_before_delete')));
|
||||||
|
|
||||||
$repository->destroy($account, $moveTo);
|
$repository->destroy($account, $moveTo);
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,6 +157,14 @@ 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());
|
||||||
|
$roles = [];
|
||||||
|
foreach (config('firefly.accountRoles') as $role) {
|
||||||
|
$roles[$role] = strval(trans('firefly.account_role_' . $role));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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) {
|
||||||
@@ -144,15 +185,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' => round($account->virtual_balance, 2),
|
'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', 'roles'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,8 +206,7 @@ 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);
|
||||||
@@ -185,6 +227,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);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -192,100 +235,99 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $tasker
|
* @param Request $request
|
||||||
* @param ARI $repository
|
* @param JournalCollectorInterface $collector
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
*
|
*
|
||||||
* @return View
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function show(AccountTaskerInterface $tasker, ARI $repository, Account $account)
|
public function show(Request $request, JournalCollectorInterface $collector, Account $account)
|
||||||
{
|
{
|
||||||
|
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
||||||
|
return $this->redirectToOriginalAccount($account);
|
||||||
|
}
|
||||||
// show journals from current period only:
|
// show journals from current period only:
|
||||||
$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));
|
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
/** @var Carbon $end */
|
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||||
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$page = intval(Input::get('page'));
|
$chartUri = route('chart.account.single', [$account->id]);
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$accountType = $account->accountType->type;
|
||||||
$offset = ($page - 1) * $pageSize;
|
|
||||||
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
|
// grab those journals:
|
||||||
$count = $set->count();
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
|
||||||
$subSet = $set->splice($offset, $pageSize);
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
|
|
||||||
$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', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||||
$cache = new CacheProperties;
|
|
||||||
$cache->addProperty($start);
|
|
||||||
$cache->addProperty($end);
|
|
||||||
$cache->addProperty('account-show');
|
|
||||||
$cache->addProperty($account->id);
|
|
||||||
|
|
||||||
|
|
||||||
if ($cache->has()) {
|
|
||||||
$entries = $cache->get();
|
|
||||||
|
|
||||||
return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// only include asset accounts when this account is an asset:
|
|
||||||
$assets = new Collection;
|
|
||||||
if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) {
|
|
||||||
$assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
|
||||||
}
|
|
||||||
|
|
||||||
while ($end >= $start) {
|
|
||||||
$end = Navigation::startOfPeriod($end, $range);
|
|
||||||
$currentEnd = Navigation::endOfPeriod($end, $range);
|
|
||||||
$spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
|
||||||
$earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
|
||||||
$dateStr = $end->format('Y-m-d');
|
|
||||||
$dateName = Navigation::periodShow($end, $range);
|
|
||||||
$entries->push([$dateStr, $dateName, $spent, $earned]);
|
|
||||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
$cache->store($entries);
|
|
||||||
|
|
||||||
return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $tasker
|
* @param Request $request
|
||||||
* @param Account $account
|
* @param ARI $repository
|
||||||
* @param string $date
|
* @param Account $account
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function showWithDate(AccountTaskerInterface $tasker, Account $account, string $date)
|
public function showAll(Request $request, AccountRepositoryInterface $repository, Account $account)
|
||||||
{
|
{
|
||||||
$carbon = new Carbon($date);
|
$subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||||
$start = Navigation::startOfPeriod($carbon, $range);
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$end = Navigation::endOfPeriod($carbon, $range);
|
$chartUri = route('chart.account.all', [$account->id]);
|
||||||
$subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
|
|
||||||
$page = intval(Input::get('page'));
|
// replace with journal collector:
|
||||||
$page = $page === 0 ? 1 : $page;
|
/** @var JournalCollectorInterface $collector */
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||||
$offset = ($page - 1) * $pageSize;
|
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||||
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
|
$journals = $collector->getPaginatedJournals();
|
||||||
$count = $set->count();
|
$journals->setPath('accounts/show/' . $account->id . '/all');
|
||||||
$subSet = $set->splice($offset, $pageSize);
|
|
||||||
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
|
// get oldest and newest journal for account:
|
||||||
|
$start = $repository->oldestJournalDate($account);
|
||||||
|
$end = $repository->newestJournalDate($account);
|
||||||
|
|
||||||
|
// same call, except "entries".
|
||||||
|
return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
* @param Account $account
|
||||||
|
* @param string $date
|
||||||
|
*
|
||||||
|
* @return View
|
||||||
|
*/
|
||||||
|
public function showByDate(Request $request, Account $account, string $date)
|
||||||
|
{
|
||||||
|
$carbon = new Carbon($date);
|
||||||
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
$start = Navigation::startOfPeriod($carbon, $range);
|
||||||
|
$end = Navigation::endOfPeriod($carbon, $range);
|
||||||
|
$subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
|
||||||
|
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||||
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
|
$chartUri = route('chart.account.period', [$account->id, $carbon->format('Y-m-d')]);
|
||||||
|
$accountType = $account->accountType->type;
|
||||||
|
|
||||||
|
// replace with journal collector:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($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'));
|
// generate entries for each period (and cache those)
|
||||||
|
$entries = $this->periodEntries($account);
|
||||||
|
|
||||||
|
// same call, except "entries".
|
||||||
|
return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -297,23 +339,8 @@ class AccountController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(AccountFormRequest $request, ARI $repository)
|
public function store(AccountFormRequest $request, ARI $repository)
|
||||||
{
|
{
|
||||||
$accountData = [
|
$data = $request->getAccountData();
|
||||||
'name' => trim($request->input('name')),
|
$account = $repository->store($data);
|
||||||
'accountType' => $request->input('what'),
|
|
||||||
'virtualBalance' => round($request->input('virtualBalance'), 2),
|
|
||||||
'virtualBalanceCurrency' => intval($request->input('amount_currency_id_virtualBalance')),
|
|
||||||
'active' => true,
|
|
||||||
'user' => auth()->user()->id,
|
|
||||||
'iban' => trim($request->input('iban')),
|
|
||||||
'accountNumber' => trim($request->input('accountNumber')),
|
|
||||||
'accountRole' => $request->input('accountRole'),
|
|
||||||
'openingBalance' => round($request->input('openingBalance'), 2),
|
|
||||||
'openingBalanceDate' => new Carbon((string)$request->input('openingBalanceDate')),
|
|
||||||
'openingBalanceCurrency' => intval($request->input('amount_currency_id_openingBalance')),
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
$account = $repository->store($accountData);
|
|
||||||
|
|
||||||
Session::flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
|
Session::flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
@@ -325,7 +352,7 @@ class AccountController extends Controller
|
|||||||
Preferences::set('frontPageAccounts', $frontPage);
|
Preferences::set('frontPageAccounts', $frontPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intval(Input::get('create_another')) === 1) {
|
if (intval($request->get('create_another')) === 1) {
|
||||||
// set value so create routine will not overwrite URL:
|
// set value so create routine will not overwrite URL:
|
||||||
Session::put('accounts.create.fromStore', true);
|
Session::put('accounts.create.fromStore', true);
|
||||||
|
|
||||||
@@ -345,27 +372,13 @@ class AccountController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(AccountFormRequest $request, ARI $repository, Account $account)
|
public function update(AccountFormRequest $request, ARI $repository, Account $account)
|
||||||
{
|
{
|
||||||
|
$data = $request->getAccountData();
|
||||||
$accountData = [
|
$repository->update($account, $data);
|
||||||
'name' => $request->input('name'),
|
|
||||||
'active' => $request->input('active'),
|
|
||||||
'user' => auth()->user()->id,
|
|
||||||
'iban' => $request->input('iban'),
|
|
||||||
'accountNumber' => $request->input('accountNumber'),
|
|
||||||
'accountRole' => $request->input('accountRole'),
|
|
||||||
'virtualBalance' => round($request->input('virtualBalance'), 2),
|
|
||||||
'openingBalance' => round($request->input('openingBalance'), 2),
|
|
||||||
'openingBalanceDate' => new Carbon((string)$request->input('openingBalanceDate')),
|
|
||||||
'openingBalanceCurrency' => intval($request->input('amount_currency_id_openingBalance')),
|
|
||||||
'ccType' => $request->input('ccType'),
|
|
||||||
'ccMonthlyPaymentDate' => $request->input('ccMonthlyPaymentDate'),
|
|
||||||
];
|
|
||||||
$repository->update($account, $accountData);
|
|
||||||
|
|
||||||
Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
|
Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval(Input::get('return_to_edit')) === 1) {
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
// set value so edit routine will not overwrite URL:
|
// set value so edit routine will not overwrite URL:
|
||||||
Session::put('accounts.edit.fromUpdate', true);
|
Session::put('accounts.edit.fromUpdate', true);
|
||||||
|
|
||||||
@@ -390,6 +403,88 @@ 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, clone $end]);
|
||||||
|
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
$cache->store($entries);
|
||||||
|
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function redirectToOriginalAccount(Account $account)
|
||||||
|
{
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
$transaction = $account->transactions()->first();
|
||||||
|
if (is_null($transaction)) {
|
||||||
|
throw new FireflyException('Expected a transaction. This account has none. BEEP, error.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$journal = $transaction->transactionJournal;
|
||||||
|
/** @var Transaction $opposingTransaction */
|
||||||
|
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
|
||||||
|
|
||||||
|
if (is_null($opposingTransaction)) {
|
||||||
|
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -37,8 +36,15 @@ class ConfigurationController extends Controller
|
|||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
View::share('title', strval(trans('firefly.administration')));
|
|
||||||
View::share('mainTitleIcon', 'fa-hand-spock-o');
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
View::share('title', strval(trans('firefly.administration')));
|
||||||
|
View::share('mainTitleIcon', 'fa-hand-spock-o');
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +58,14 @@ 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;
|
||||||
|
$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'));
|
return view(
|
||||||
|
'admin.configuration.index',
|
||||||
|
compact('subTitle', 'subTitleIcon', 'singleUserMode', 'isDemoSite', 'siteOwner')
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,13 +74,14 @@ 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:
|
||||||
$singleUserMode = intval($request->get('single_user_mode')) === 1 ? true : false;
|
$data = $request->getConfigurationData();
|
||||||
|
|
||||||
// store config values
|
// store config values
|
||||||
FireflyConfig::set('single_user_mode', $singleUserMode);
|
FireflyConfig::set('single_user_mode', $data['single_user_mode']);
|
||||||
|
FireflyConfig::set('is_demo_site', $data['is_demo_site']);
|
||||||
|
|
||||||
// flash message
|
// flash message
|
||||||
Session::flash('success', strval(trans('firefly.configuration_updated')));
|
Session::flash('success', strval(trans('firefly.configuration_updated')));
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* DomainController.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\Admin;
|
|
||||||
|
|
||||||
|
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
|
||||||
use FireflyIII\Support\Facades\FireflyConfig;
|
|
||||||
use FireflyIII\User;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Session;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class DomainController
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Http\Controllers\Admin
|
|
||||||
*/
|
|
||||||
class DomainController extends Controller
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function domains()
|
|
||||||
{
|
|
||||||
|
|
||||||
$title = strval(trans('firefly.administration'));
|
|
||||||
$mainTitleIcon = 'fa-hand-spock-o';
|
|
||||||
$subTitle = strval(trans('firefly.blocked_domains'));
|
|
||||||
$subTitleIcon = 'fa-exclamation-circle';
|
|
||||||
$domains = FireflyConfig::get('blocked-domains', [])->data;
|
|
||||||
|
|
||||||
// known domains
|
|
||||||
$knownDomains = $this->getKnownDomains();
|
|
||||||
|
|
||||||
return view('admin.domains.index', compact('title', 'mainTitleIcon', 'knownDomains', 'subTitle', 'subTitleIcon', 'domains'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Request $request
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
|
||||||
*/
|
|
||||||
public function manual(Request $request)
|
|
||||||
{
|
|
||||||
if (strlen($request->get('domain')) === 0) {
|
|
||||||
Session::flash('error', trans('firefly.no_domain_filled_in'));
|
|
||||||
|
|
||||||
return redirect(route('admin.users.domains'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$domain = strtolower($request->get('domain'));
|
|
||||||
$blocked = FireflyConfig::get('blocked-domains', [])->data;
|
|
||||||
|
|
||||||
if (in_array($domain, $blocked)) {
|
|
||||||
Session::flash('error', trans('firefly.domain_already_blocked', ['domain' => $domain]));
|
|
||||||
|
|
||||||
return redirect(route('admin.users.domains'));
|
|
||||||
}
|
|
||||||
$blocked[] = $domain;
|
|
||||||
FireflyConfig::set('blocked-domains', $blocked);
|
|
||||||
|
|
||||||
Session::flash('success', trans('firefly.domain_is_now_blocked', ['domain' => $domain]));
|
|
||||||
|
|
||||||
return redirect(route('admin.users.domains'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $domain
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
|
||||||
*/
|
|
||||||
public function toggleDomain(string $domain)
|
|
||||||
{
|
|
||||||
$blocked = FireflyConfig::get('blocked-domains', [])->data;
|
|
||||||
|
|
||||||
if (in_array($domain, $blocked)) {
|
|
||||||
$key = array_search($domain, $blocked);
|
|
||||||
unset($blocked[$key]);
|
|
||||||
sort($blocked);
|
|
||||||
|
|
||||||
FireflyConfig::set('blocked-domains', $blocked);
|
|
||||||
Session::flash('message', trans('firefly.domain_now_unblocked', ['domain' => $domain]));
|
|
||||||
|
|
||||||
|
|
||||||
return redirect(route('admin.users.domains'));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$blocked[] = $domain;
|
|
||||||
|
|
||||||
FireflyConfig::set('blocked-domains', $blocked);
|
|
||||||
Session::flash('message', trans('firefly.domain_now_blocked', ['domain' => $domain]));
|
|
||||||
|
|
||||||
return redirect(route('admin.users.domains'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getKnownDomains(): array
|
|
||||||
{
|
|
||||||
$users = User::get();
|
|
||||||
$set = [];
|
|
||||||
$filtered = [];
|
|
||||||
/** @var User $user */
|
|
||||||
foreach ($users as $user) {
|
|
||||||
$email = $user->email;
|
|
||||||
$parts = explode('@', $email);
|
|
||||||
$domain = $parts[1];
|
|
||||||
$set[] = $domain;
|
|
||||||
}
|
|
||||||
$set = array_unique($set);
|
|
||||||
// filter for already banned domains:
|
|
||||||
$blocked = FireflyConfig::get('blocked-domains', [])->data;
|
|
||||||
|
|
||||||
foreach ($set as $domain) {
|
|
||||||
// in the block array? ignore it.
|
|
||||||
if (!in_array($domain, $blocked)) {
|
|
||||||
$filtered[] = $domain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
asort($filtered);
|
|
||||||
|
|
||||||
return $filtered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ namespace FireflyIII\Http\Controllers\Admin;
|
|||||||
|
|
||||||
|
|
||||||
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 +30,75 @@ 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'));
|
$subTitle = strval(trans('firefly.user_administration'));
|
||||||
$mainTitleIcon = 'fa-hand-spock-o';
|
$subTitleIcon = 'fa-users';
|
||||||
$subTitle = strval(trans('firefly.user_administration'));
|
$users = $repository->all();
|
||||||
$subTitleIcon = 'fa-users';
|
|
||||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
|
||||||
$users = $repository->all();
|
|
||||||
|
|
||||||
// add meta stuff.
|
// add meta stuff.
|
||||||
$users->each(
|
$users->each(
|
||||||
function (User $user) use ($confirmAccount) {
|
function (User $user) {
|
||||||
// is user activated?
|
$list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret'];
|
||||||
$isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data;
|
$preferences = Preferences::getArrayForUser($user, $list);
|
||||||
$user->activated = true;
|
|
||||||
if ($isConfirmed === false && $confirmAccount === true) {
|
|
||||||
$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 = ($is2faEnabled && $has2faSecret) ? true : false;
|
||||||
if ($is2faEnabled && $has2faSecret) {
|
$user->prefs = $preferences;
|
||||||
$user->has2FA = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return view('admin.users.index', compact('title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'users'));
|
return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,40 +114,51 @@ class UserController extends Controller
|
|||||||
$mainTitleIcon = 'fa-hand-spock-o';
|
$mainTitleIcon = 'fa-hand-spock-o';
|
||||||
$subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email]));
|
$subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email]));
|
||||||
$subTitleIcon = 'fa-user';
|
$subTitleIcon = 'fa-user';
|
||||||
|
$information = $repository->getUserData($user);
|
||||||
// get IP info:
|
|
||||||
$defaultIp = '0.0.0.0';
|
|
||||||
$regPref = Preferences::getForUser($user, 'registration_ip_address');
|
|
||||||
$registration = $defaultIp;
|
|
||||||
$conPref = Preferences::getForUser($user, 'confirmation_ip_address');
|
|
||||||
$confirmation = $defaultIp;
|
|
||||||
if (!is_null($regPref)) {
|
|
||||||
$registration = $regPref->data;
|
|
||||||
}
|
|
||||||
if (!is_null($conPref)) {
|
|
||||||
$confirmation = $conPref->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$registrationHost = '';
|
|
||||||
$confirmationHost = '';
|
|
||||||
|
|
||||||
if ($registration != $defaultIp) {
|
|
||||||
$registrationHost = gethostbyaddr($registration);
|
|
||||||
}
|
|
||||||
if ($confirmation != $defaultIp) {
|
|
||||||
$confirmationHost = gethostbyaddr($confirmation);
|
|
||||||
}
|
|
||||||
|
|
||||||
$information = $repository->getUserData($user);
|
|
||||||
|
|
||||||
return view(
|
return view(
|
||||||
'admin.users.show',
|
'admin.users.show',
|
||||||
compact(
|
compact(
|
||||||
'title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'information',
|
'title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'information', 'user'
|
||||||
'user', 'registration', 'confirmation', 'registrationHost', 'confirmationHost'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,23 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Crypt;
|
|
||||||
use File;
|
use File;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Http\Requests\AttachmentFormRequest;
|
use FireflyIII\Http\Requests\AttachmentFormRequest;
|
||||||
use FireflyIII\Models\Attachment;
|
use FireflyIII\Models\Attachment;
|
||||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||||
use Input;
|
use Illuminate\Http\Response as LaravelResponse;
|
||||||
use Log;
|
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
use Session;
|
use Session;
|
||||||
use Storage;
|
|
||||||
use URL;
|
use URL;
|
||||||
use View;
|
use View;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AttachmentController
|
* Class AttachmentController
|
||||||
*
|
*
|
||||||
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it's 13.
|
||||||
|
*
|
||||||
* @package FireflyIII\Http\Controllers
|
* @package FireflyIII\Http\Controllers
|
||||||
*/
|
*/
|
||||||
class AttachmentController extends Controller
|
class AttachmentController extends Controller
|
||||||
@@ -42,8 +41,16 @@ class AttachmentController extends Controller
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
View::share('mainTitleIcon', 'fa-paperclip');
|
|
||||||
View::share('title', trans('firefly.attachments'));
|
// translations:
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
View::share('mainTitleIcon', 'fa-paperclip');
|
||||||
|
View::share('title', trans('firefly.attachments'));
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,25 +89,21 @@ class AttachmentController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Attachment $attachment
|
* @param AttachmentRepositoryInterface $repository
|
||||||
|
* @param Attachment $attachment
|
||||||
*
|
*
|
||||||
|
* @return mixed
|
||||||
* @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)]);
|
/** @var LaravelResponse $response */
|
||||||
|
$response = response($content, 200);
|
||||||
return response($content, 200)
|
$response
|
||||||
->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)
|
||||||
@@ -111,6 +114,7 @@ class AttachmentController extends Controller
|
|||||||
->header('Pragma', 'public')
|
->header('Pragma', 'public')
|
||||||
->header('Content-Length', strlen($content));
|
->header('Content-Length', strlen($content));
|
||||||
|
|
||||||
|
return $response;
|
||||||
}
|
}
|
||||||
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.');
|
||||||
}
|
}
|
||||||
@@ -143,7 +147,6 @@ class AttachmentController extends Controller
|
|||||||
{
|
{
|
||||||
$image = 'images/page_green.png';
|
$image = 'images/page_green.png';
|
||||||
|
|
||||||
|
|
||||||
if ($attachment->mime == 'application/pdf') {
|
if ($attachment->mime == 'application/pdf') {
|
||||||
$image = 'images/page_white_acrobat.png';
|
$image = 'images/page_white_acrobat.png';
|
||||||
}
|
}
|
||||||
@@ -164,19 +167,13 @@ class AttachmentController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
|
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||||
{
|
{
|
||||||
|
$data = $request->getAttachmentData();
|
||||||
$attachmentData = [
|
$repository->update($attachment, $data);
|
||||||
'title' => $request->input('title'),
|
|
||||||
'description' => $request->input('description'),
|
|
||||||
'notes' => $request->input('notes'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$repository->update($attachment, $attachmentData);
|
|
||||||
|
|
||||||
Session::flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
|
Session::flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval(Input::get('return_to_edit')) === 1) {
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
// set value so edit routine will not overwrite URL:
|
// set value so edit routine will not overwrite URL:
|
||||||
Session::put('attachments.edit.fromUpdate', true);
|
Session::put('attachments.edit.fromUpdate', true);
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user