mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-10 21:31:20 +00:00
Compare commits
5195 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
2fa0d55f39 | ||
|
|
5bff509346 | ||
|
|
a147e9b74a | ||
|
|
0d87f7c4ca | ||
|
|
8c675615df | ||
|
|
7edd1bff40 | ||
|
|
3bfcb1f3ab | ||
|
|
7b6c63e6a8 | ||
|
|
5500e5b0aa | ||
|
|
e4d249e73c | ||
|
|
091f6e918b | ||
|
|
5d9b68c3e7 | ||
|
|
12a6a61100 | ||
|
|
7ce3b8d4ef | ||
|
|
3d9b855849 | ||
|
|
2346d2ec05 | ||
|
|
a4c081c8a5 | ||
|
|
316980efbd | ||
|
|
a05bc0eed0 | ||
|
|
4d1c271da6 | ||
|
|
0dd7ecbfbe | ||
|
|
0dc188b083 | ||
|
|
6a553f77f3 | ||
|
|
a74cef439b | ||
|
|
9a3cd27700 | ||
|
|
801c7c0ab6 | ||
|
|
a95a4e783a | ||
|
|
af1ee9db93 | ||
|
|
fcdb6fd2a7 | ||
|
|
97c0fb389d | ||
|
|
a9c3992331 | ||
|
|
a38e057fa7 | ||
|
|
f83aaf77f1 | ||
|
|
d92768ecbf | ||
|
|
b9308cd74a | ||
|
|
78b577bc9d | ||
|
|
7d247897ed | ||
|
|
5dcbdec491 | ||
|
|
9bf980431e | ||
|
|
da60bfbcff | ||
|
|
92553cbc7e | ||
|
|
8e48e53f17 | ||
|
|
2f9a4bb79a | ||
|
|
ac968dd6cd | ||
|
|
6e4f2c0c8a | ||
|
|
d662c18ed7 | ||
|
|
e4ea234707 | ||
|
|
0b526c0168 | ||
|
|
2acde5c72a | ||
|
|
ec8cf2c459 | ||
|
|
3598780d54 | ||
|
|
35dd8ac6e6 | ||
|
|
5ff7c7ffab | ||
|
|
399db47826 | ||
|
|
148956a60d | ||
|
|
3670053a58 | ||
|
|
e8e2b9704f | ||
|
|
fcdeebcc06 | ||
|
|
586ed82e88 | ||
|
|
cc400d1e2e | ||
|
|
6edbfb27aa | ||
|
|
8fc9251b93 | ||
|
|
10af888a97 | ||
|
|
89f2328846 | ||
|
|
48e8cd20b4 | ||
|
|
394ef23eda | ||
|
|
62aa1eb487 | ||
|
|
1500018ccc | ||
|
|
23fad62d46 | ||
|
|
3cbf00734f | ||
|
|
1dc17dd59d | ||
|
|
f8935c92ea | ||
|
|
de6f838413 | ||
|
|
e8a095e543 | ||
|
|
717c1d080e | ||
|
|
0ae9afd325 | ||
|
|
d1b56c2afa | ||
|
|
8ef7c5ac33 | ||
|
|
7180a40cd8 | ||
|
|
71804af624 | ||
|
|
85dc7f3643 | ||
|
|
a866d13b75 | ||
|
|
fcb5e4eabc | ||
|
|
ade1cf9c19 | ||
|
|
0f1ec7d003 | ||
|
|
7e038afece | ||
|
|
9bb8e182fa | ||
|
|
e94ae126fd | ||
|
|
5bb8c6a366 | ||
|
|
30844df5d4 | ||
|
|
63e4a410a7 | ||
|
|
ee9a5d91e2 | ||
|
|
171ab8a4c3 | ||
|
|
96740aaac4 | ||
|
|
2017720096 | ||
|
|
b77ea6d316 | ||
|
|
f5adb4047f | ||
|
|
b082858866 | ||
|
|
a8a014189d | ||
|
|
39ea9e85a7 | ||
|
|
a4d2ed74fc | ||
|
|
90f2e27f1f | ||
|
|
a3359ba47a | ||
|
|
1d2d3523d6 | ||
|
|
3f40751a1a | ||
|
|
b5b55e862c | ||
|
|
c64771b76b | ||
|
|
ea7ee7ee9a | ||
|
|
a1f797c4d1 | ||
|
|
d0c92a2244 | ||
|
|
6e90c033b1 | ||
|
|
24f62b8fce | ||
|
|
d43936155c | ||
|
|
39dab4fdd9 | ||
|
|
c0fdf44ad2 | ||
|
|
4d91f7d23a | ||
|
|
49af6522a8 | ||
|
|
3c5f9487a8 | ||
|
|
f5cb87f5c3 | ||
|
|
cf543613c9 | ||
|
|
5c239c91db | ||
|
|
9920504232 | ||
|
|
5540697dbd | ||
|
|
b355c18e0c | ||
|
|
1e90485c5f | ||
|
|
dc784c53b5 | ||
|
|
5a47391a64 | ||
|
|
8a106bd16a | ||
|
|
a31ac79173 | ||
|
|
0d0a604254 | ||
|
|
724d25f2c2 | ||
|
|
8ed22d452d | ||
|
|
d7fef45a56 | ||
|
|
dc22802dec | ||
|
|
ce5af7b1d9 | ||
|
|
0a147e5c9c | ||
|
|
7d21255f7f | ||
|
|
13f952f182 | ||
|
|
b494be228b | ||
|
|
0fdaac53d0 | ||
|
|
e1b3a08878 | ||
|
|
dc893588b0 | ||
|
|
b9fcc443ec | ||
|
|
d8586c8043 | ||
|
|
4252a3e53b | ||
|
|
dbb5cdb9cf | ||
|
|
9bdfecbfdc | ||
|
|
3ec8a8c375 | ||
|
|
f85e4a24e5 | ||
|
|
0dda87c78e | ||
|
|
2fc09ff9d7 | ||
|
|
e4e0e21293 | ||
|
|
15089f0d7e | ||
|
|
7232c1d7bb | ||
|
|
9a53d8c21c | ||
|
|
0d198193db | ||
|
|
45bc23b8af | ||
|
|
3323c31765 | ||
|
|
bb7c26b77c | ||
|
|
9101d6a2c0 | ||
|
|
ad2b254be0 | ||
|
|
abc4f856ce | ||
|
|
b3b66a8f92 | ||
|
|
2f7cf9b916 | ||
|
|
b822e0c6e7 | ||
|
|
6fef9ee72b | ||
|
|
ab6dd0a1ec | ||
|
|
9deef5ac92 | ||
|
|
577187babe | ||
|
|
577290e813 | ||
|
|
f3b9798216 | ||
|
|
4dcaa96d16 | ||
|
|
0dcbf451d6 | ||
|
|
e87f6ca40e | ||
|
|
1f34e33d8c | ||
|
|
258e87e127 | ||
|
|
3ec8efcfc1 | ||
|
|
70ea227bd0 | ||
|
|
27c832ed58 | ||
|
|
a31b4ccf01 | ||
|
|
d221ea68d0 | ||
|
|
dc9fe58536 | ||
|
|
f871e29bdb | ||
|
|
1357352276 | ||
|
|
e169754693 | ||
|
|
1cfe4f40ba | ||
|
|
5545d1c1ba | ||
|
|
e5f7228fa9 | ||
|
|
11385494eb | ||
|
|
ae328de469 | ||
|
|
0574a706c8 | ||
|
|
70bb85a75b | ||
|
|
8cd901b57b | ||
|
|
8b9818c48e | ||
|
|
a95099fa46 | ||
|
|
221e4b7fc0 | ||
|
|
6cff3eb61e | ||
|
|
e4fd97ae77 | ||
|
|
5ca9099654 | ||
|
|
6e33e26ddf | ||
|
|
04461a4ab8 | ||
|
|
a4b9bbff54 | ||
|
|
4ad4252a77 | ||
|
|
aac0c9ab98 | ||
|
|
c123e1044a | ||
|
|
d25d0454fc | ||
|
|
f38984398d | ||
|
|
a07799cfa4 | ||
|
|
7c52f297ee | ||
|
|
50a3279b30 | ||
|
|
0ea14eb987 | ||
|
|
cf1b98e569 | ||
|
|
63fb435002 | ||
|
|
2f8263f53a | ||
|
|
fb649779d6 | ||
|
|
bb58f605f7 | ||
|
|
fccdf56c5d | ||
|
|
bd53410c71 | ||
|
|
7d63f124c4 | ||
|
|
9ffc5d857c | ||
|
|
2f93784acd | ||
|
|
d00fbe4eb3 | ||
|
|
51d9f041ae | ||
|
|
b872ab8b86 | ||
|
|
3d25fd79ca | ||
|
|
3aad78e6ef | ||
|
|
7d5bb72b0c | ||
|
|
46e5aae8bb | ||
|
|
b9e2ee7af3 | ||
|
|
c1a2892788 | ||
|
|
2078183e0d | ||
|
|
fab1d53714 | ||
|
|
3fe831c7d8 | ||
|
|
6958f71cfd | ||
|
|
a7351f348d | ||
|
|
5379e03447 | ||
|
|
539058e32d | ||
|
|
11b6b5a63c | ||
|
|
1155096226 | ||
|
|
2920dd356e | ||
|
|
9c3dac8170 | ||
|
|
49f9909b15 | ||
|
|
e4fef6dfc3 | ||
|
|
2da087401e | ||
|
|
629baf9de5 | ||
|
|
29a930dae5 | ||
|
|
d920537dd2 | ||
|
|
106b04a5da | ||
|
|
176752e219 | ||
|
|
8d19f60091 | ||
|
|
e937aa2f74 | ||
|
|
c3ccc4ccdf | ||
|
|
6c5cd705c0 | ||
|
|
ce003f217b | ||
|
|
8c7ef49eb6 | ||
|
|
29af4bd1b9 | ||
|
|
2ce5142b06 | ||
|
|
f3a8a25872 | ||
|
|
598e97d028 | ||
|
|
fa110279de | ||
|
|
8fdeaf73cc | ||
|
|
a7ffdf062a | ||
|
|
3bad92dd6d | ||
|
|
2e88024bca | ||
|
|
ca2301959c | ||
|
|
3285fae7f0 | ||
|
|
312079657b | ||
|
|
18c183afd6 | ||
|
|
f424d9cf20 | ||
|
|
5c3da9fd9e | ||
|
|
1f321fadd4 | ||
|
|
65f5d27b12 | ||
|
|
cdb591de7f | ||
|
|
a0ea3882e1 | ||
|
|
a9444ac702 | ||
|
|
d0c6afc3a9 | ||
|
|
e7a0a5937c | ||
|
|
08992b5298 | ||
|
|
6490a4240d | ||
|
|
43a7544dd7 | ||
|
|
0fe70dae17 | ||
|
|
7079e76886 | ||
|
|
0ec021c375 | ||
|
|
ff3396e286 | ||
|
|
78912903ce | ||
|
|
4c015e2d12 | ||
|
|
172634a55a | ||
|
|
58ca7d551a | ||
|
|
fd8ed4c9aa | ||
|
|
c03f5f5ff0 | ||
|
|
6775fc58c8 | ||
|
|
9f250fc61b | ||
|
|
94609f1419 | ||
|
|
53402aa5e2 | ||
|
|
5aadb29905 | ||
|
|
d4f8c41d80 | ||
|
|
a2e14f8b8d | ||
|
|
98c4ac955a | ||
|
|
70b63e1736 | ||
|
|
106665a468 | ||
|
|
da5e48d769 | ||
|
|
85f484e73c | ||
|
|
d287ae97f8 | ||
|
|
117bb602dc | ||
|
|
e440d55034 | ||
|
|
7f5b94fe43 | ||
|
|
c58eea6654 | ||
|
|
bbed5d0701 | ||
|
|
2775690fc8 | ||
|
|
1fd9f6e724 | ||
|
|
3d63903128 | ||
|
|
ef876a165a | ||
|
|
c4d6aaeef3 | ||
|
|
37b735c2e3 | ||
|
|
62d30c7b0e | ||
|
|
9cac7d46c0 | ||
|
|
99b3e24836 | ||
|
|
ffb699cb06 | ||
|
|
2947ec0002 | ||
|
|
5c4d010bde | ||
|
|
955306d877 | ||
|
|
015935ed55 | ||
|
|
48f4cceb19 | ||
|
|
9850220aac | ||
|
|
c4ac1460f0 | ||
|
|
b9e1e01337 | ||
|
|
76649cb7de | ||
|
|
5e310776b4 | ||
|
|
4d7fa11172 | ||
|
|
28962007c1 | ||
|
|
2111873bcf | ||
|
|
0aaf9a6fda | ||
|
|
186b704509 | ||
|
|
efe9933721 | ||
|
|
200366f5be | ||
|
|
c9bd72337d | ||
|
|
d4510440b8 | ||
|
|
5a9cf698f7 | ||
|
|
5826fec519 | ||
|
|
7035600984 | ||
|
|
b1dfb5811f | ||
|
|
51570a5d08 | ||
|
|
f065f3a0b8 | ||
|
|
47376f1f35 | ||
|
|
bcfe2c6410 | ||
|
|
09d63b584d | ||
|
|
5f5469a7d4 | ||
|
|
38800d61b0 | ||
|
|
1186e95c51 | ||
|
|
d870e0f42e | ||
|
|
0db9852769 | ||
|
|
7e3f9048fe | ||
|
|
1ba88f182b | ||
|
|
c8f5a6b7ad | ||
|
|
d26bbf3cdc | ||
|
|
0de72b6914 | ||
|
|
e41c89bd59 | ||
|
|
541d9ebdd9 | ||
|
|
1e724712e0 | ||
|
|
3682467ae3 | ||
|
|
7707c81b2d | ||
|
|
e434de72a3 | ||
|
|
ce191fa6d3 | ||
|
|
90865a5284 | ||
|
|
684f6e0b5c | ||
|
|
c287bc139c | ||
|
|
1392275b81 | ||
|
|
87c0f1d86e | ||
|
|
a4a723cfc6 | ||
|
|
921e2c06f4 | ||
|
|
cb9433f4b9 | ||
|
|
1be6af820e | ||
|
|
3b686b6d1c | ||
|
|
697566fe42 | ||
|
|
5130ba7ea4 | ||
|
|
c9e46a4dd1 | ||
|
|
f5b3dc36e3 | ||
|
|
f1e8d1cf1e | ||
|
|
e8e7ab01d2 | ||
|
|
9244994233 | ||
|
|
ae768a8525 | ||
|
|
162c762973 | ||
|
|
57b5981904 | ||
|
|
275d19e71d | ||
|
|
189b11befa | ||
|
|
a56a5fc228 | ||
|
|
cbe3fb73a8 | ||
|
|
3d58fbebec | ||
|
|
b947ff50ed | ||
|
|
18d2741814 | ||
|
|
93a54780ab | ||
|
|
3d201db6fc | ||
|
|
9ffc0936ee | ||
|
|
fbf9e00208 | ||
|
|
2c826451d1 | ||
|
|
617a5c0606 | ||
|
|
8331a7e34a | ||
|
|
8ee1676f0a | ||
|
|
5dc8620c43 | ||
|
|
d2733a4df0 | ||
|
|
ae649223d8 | ||
|
|
6267930938 | ||
|
|
bdee8cde77 | ||
|
|
e63f216905 | ||
|
|
ec18165698 | ||
|
|
307e6a2337 | ||
|
|
b80d8cf774 | ||
|
|
7a5ef6013a | ||
|
|
13b92c47d9 | ||
|
|
5a79bc0a99 | ||
|
|
beda6ec3a9 | ||
|
|
c619b8730b | ||
|
|
c14ec8b006 | ||
|
|
10c7786248 | ||
|
|
08ff08685c | ||
|
|
8091dbfdfa | ||
|
|
8dc106b79a | ||
|
|
7527433738 | ||
|
|
1502e08a7a | ||
|
|
c9679f1d4f | ||
|
|
6b976dd6b9 | ||
|
|
8da4abf655 | ||
|
|
2e26193333 | ||
|
|
ada43bc0dd | ||
|
|
a447c886c4 | ||
|
|
288e713f94 | ||
|
|
f8eb1fa44a | ||
|
|
afc794513f | ||
|
|
7e6d3c9d6b | ||
|
|
44960e8e42 | ||
|
|
41430c3bb2 | ||
|
|
2f435019e0 | ||
|
|
480e70dfac | ||
|
|
c4818334e7 | ||
|
|
a5d5f86aed | ||
|
|
78afb771b1 | ||
|
|
a74a646777 | ||
|
|
87f9ca3bb2 | ||
|
|
d54e264a91 | ||
|
|
99aea5ce7a | ||
|
|
e10d5e89e5 | ||
|
|
0105456828 | ||
|
|
5c7df5c04d | ||
|
|
563ede822f | ||
|
|
786cfc21c7 | ||
|
|
8f0856e31a | ||
|
|
bea075c74d | ||
|
|
5f82accb61 | ||
|
|
95f50ca2fd | ||
|
|
d8b76bdfd2 | ||
|
|
74a9edaaf7 | ||
|
|
7dd858be39 | ||
|
|
7d7ff71384 | ||
|
|
003177fa49 | ||
|
|
3c3a83330d | ||
|
|
724f423692 | ||
|
|
65b8882ed4 | ||
|
|
66d7fd7d4c | ||
|
|
782a6f289c | ||
|
|
70d936bb8f | ||
|
|
dda3082c7e | ||
|
|
ed948cc965 | ||
|
|
0347649f42 | ||
|
|
c66fe2b541 | ||
|
|
8c1ae76c7a | ||
|
|
04dee404c0 | ||
|
|
b22dd29835 | ||
|
|
536a5cd1c8 | ||
|
|
b7f1bcf7c9 | ||
|
|
5e590072dd | ||
|
|
d204b9b752 | ||
|
|
22d1121193 | ||
|
|
e23c6521b9 | ||
|
|
b0cb9663a6 | ||
|
|
95b7da89f0 | ||
|
|
b0a5b53abb | ||
|
|
ce78c8993f | ||
|
|
2538b4a885 | ||
|
|
109d96ad16 | ||
|
|
4cbb0d9716 | ||
|
|
65ecea3b1c | ||
|
|
495b80f5ef | ||
|
|
e113736887 | ||
|
|
042b7a4966 | ||
|
|
5ab8cb38d3 | ||
|
|
fc4ab29244 | ||
|
|
5a43e6cb9f | ||
|
|
4effc95c5f | ||
|
|
962965b5b7 | ||
|
|
260b611293 | ||
|
|
d2131c371b | ||
|
|
eedf6a07f0 | ||
|
|
5cd1e7c100 | ||
|
|
6a750a998f | ||
|
|
bd818b2dea | ||
|
|
4164ebcc69 | ||
|
|
60d732067b | ||
|
|
b7b52707fb | ||
|
|
f373c72679 | ||
|
|
ec2027b8db | ||
|
|
a84de5db77 | ||
|
|
5065b1ee03 | ||
|
|
a0aa114ee6 | ||
|
|
823839fbf6 | ||
|
|
1c93d8bf79 | ||
|
|
626404407e | ||
|
|
446ab62d38 | ||
|
|
0d39161ec3 | ||
|
|
29be16dcba | ||
|
|
b0bb790386 | ||
|
|
e64b40d58b | ||
|
|
4870945af2 | ||
|
|
a547a5f3f9 | ||
|
|
e5eabdf7e7 | ||
|
|
f78d56b149 | ||
|
|
771926c779 | ||
|
|
6090efe2df | ||
|
|
863227c55c | ||
|
|
5a6967cefd | ||
|
|
5166171e5d | ||
|
|
3e36a29c23 | ||
|
|
20e1e50032 | ||
|
|
36bc483edb | ||
|
|
aa59227786 | ||
|
|
2d8449ed68 | ||
|
|
d7ab482ae1 | ||
|
|
cfcc4ce88a | ||
|
|
eda44bbed0 | ||
|
|
988049061d | ||
|
|
ebb1c5ae25 | ||
|
|
ce7eebac5c | ||
|
|
b7c446f7db | ||
|
|
7c39a04c4b | ||
|
|
037d84b810 | ||
|
|
529bf50c85 | ||
|
|
d2b4bd78a9 | ||
|
|
e1c146a5c1 | ||
|
|
ed9acbdfde | ||
|
|
dc825d5a9c | ||
|
|
9f8faf15f1 | ||
|
|
934656c954 | ||
|
|
d233a2df3c | ||
|
|
7c7740d3ba | ||
|
|
cda6cfb4cd | ||
|
|
a90d095609 | ||
|
|
98e683329e | ||
|
|
3588bd881c | ||
|
|
0460811e6c | ||
|
|
27f5fe18df | ||
|
|
6d944ec98f | ||
|
|
adf6691470 | ||
|
|
dd8b500efd | ||
|
|
4e1ff8c4a3 | ||
|
|
e73d590ead | ||
|
|
5cc22f49cf | ||
|
|
eb3d2b1749 | ||
|
|
21a197ba46 | ||
|
|
0b74707638 | ||
|
|
16dc8b7d68 | ||
|
|
5a8abe004e | ||
|
|
b211d72c8b | ||
|
|
36f3eb8b2f | ||
|
|
cb1cb9f328 | ||
|
|
3344bb7263 | ||
|
|
5e1167b8ae | ||
|
|
b80db054e2 | ||
|
|
c66df3cb2c | ||
|
|
ac8ff4e565 | ||
|
|
bfa7ee90f4 | ||
|
|
77c9e37584 | ||
|
|
80350f8423 | ||
|
|
3c1ff4d21f | ||
|
|
55b8f03590 | ||
|
|
1fd7852309 | ||
|
|
9c5292962f | ||
|
|
c05c6e72c0 | ||
|
|
bdcd033952 | ||
|
|
4ec6bcc8c7 | ||
|
|
11ea4b6d47 | ||
|
|
e4f45b5370 | ||
|
|
9baadd3793 | ||
|
|
94a79876ce | ||
|
|
42c3d1fa68 | ||
|
|
0e3ccebd0b | ||
|
|
4af8272faa | ||
|
|
0ef3d0cf03 | ||
|
|
f266b92ef1 | ||
|
|
462c9fb3aa | ||
|
|
568186828c | ||
|
|
8bcc319b7d | ||
|
|
baff9780de | ||
|
|
d8b8f98738 | ||
|
|
b714eaac06 | ||
|
|
14b94a5bd2 | ||
|
|
ea014a6504 | ||
|
|
e28e66e8f1 | ||
|
|
b47a140c2f | ||
|
|
19d7e27fa9 | ||
|
|
2d368f226e | ||
|
|
75d81f8f18 | ||
|
|
e3437ba697 | ||
|
|
84f299c33b | ||
|
|
3d4489efe6 | ||
|
|
6aa50e3c00 | ||
|
|
b70498c337 | ||
|
|
f34aa77d1d | ||
|
|
3833a41acb | ||
|
|
b5a5a216cd | ||
|
|
d9da2a57b6 | ||
|
|
1591b61b77 | ||
|
|
66f2df9677 | ||
|
|
5199377113 | ||
|
|
da202317c0 | ||
|
|
1d2a4e707e | ||
|
|
edf9dbc6e8 | ||
|
|
dfbe6e5b6e | ||
|
|
d551333fa2 | ||
|
|
01cab599bb | ||
|
|
1c8834fffb | ||
|
|
22e6ea700f | ||
|
|
7f7d6cf893 | ||
|
|
a94d476b75 | ||
|
|
eb5e55a272 | ||
|
|
53c80aaef8 | ||
|
|
607d0115f0 | ||
|
|
b4f18dbe77 | ||
|
|
51d97cdca5 | ||
|
|
2cd593157f | ||
|
|
ec70fde557 | ||
|
|
950576d38b | ||
|
|
ce5304277d | ||
|
|
53760766a0 | ||
|
|
ed863986a7 | ||
|
|
89ff5a83b5 | ||
|
|
fe0b62b9b4 | ||
|
|
32c8ddbe1b | ||
|
|
2cfbfd8649 | ||
|
|
3f6c19dec4 | ||
|
|
93421b50f9 | ||
|
|
54e829173a | ||
|
|
4fe38bd31b | ||
|
|
fb0638e824 | ||
|
|
108794a6b6 | ||
|
|
9c16fc1380 | ||
|
|
99c219ed97 | ||
|
|
ec12238ea1 | ||
|
|
bdbd22f98b | ||
|
|
b8e1944d20 | ||
|
|
8883d185fe | ||
|
|
19e40e9976 | ||
|
|
fa85b2b5b2 | ||
|
|
5cf0131d75 | ||
|
|
3948cb8e6c | ||
|
|
f43938726a | ||
|
|
8c8bb7a930 | ||
|
|
3972882a33 | ||
|
|
0ef5eeeb55 | ||
|
|
ef48a79d0c | ||
|
|
2bb883219c | ||
|
|
23c0bb49c4 | ||
|
|
13e59105ec | ||
|
|
98c057c516 | ||
|
|
e293d69798 | ||
|
|
b097e29104 | ||
|
|
29fbd46e33 | ||
|
|
6a15afc723 | ||
|
|
c56a39a726 | ||
|
|
4b80e46d26 | ||
|
|
a6f3e61520 | ||
|
|
cce4ef19e5 | ||
|
|
1951491c04 | ||
|
|
87b72e4bcd | ||
|
|
2c6643d691 | ||
|
|
d02df46517 | ||
|
|
0c0cc417ee | ||
|
|
f0c03e8a3b | ||
|
|
345766f387 | ||
|
|
3fa659632c | ||
|
|
95b92b7d1e | ||
|
|
466e988627 | ||
|
|
dc3c967c9f | ||
|
|
1fc31b4d8f | ||
|
|
e4a4b0a4eb | ||
|
|
a9c026884d | ||
|
|
e893000ce9 | ||
|
|
32eeb3424d | ||
|
|
ab523b6102 | ||
|
|
b062222c45 | ||
|
|
2dddc843ce | ||
|
|
b74c1c8cf9 | ||
|
|
d5d4bb2c4b | ||
|
|
89ac27ad10 | ||
|
|
48b169c026 | ||
|
|
0715b7406e | ||
|
|
6e4991a34b | ||
|
|
8730af59bc | ||
|
|
bdcc2c6c9f | ||
|
|
6f0f6e86a1 | ||
|
|
3962d9da92 | ||
|
|
5ed53d5f04 | ||
|
|
ddb28b78c3 | ||
|
|
7699b6b4ea | ||
|
|
d783d05462 | ||
|
|
33bf373151 | ||
|
|
8116644526 | ||
|
|
dc4665e82a | ||
|
|
732a85e51d | ||
|
|
32190db8bb | ||
|
|
25d3a115e0 | ||
|
|
30e3ed6410 | ||
|
|
8b5a775dc5 | ||
|
|
2942c3a4be | ||
|
|
768d3bb3e8 | ||
|
|
250b2c2f53 | ||
|
|
c8440af9a5 | ||
|
|
37fe2d22b0 | ||
|
|
b0b5d90976 | ||
|
|
27b0c7f425 | ||
|
|
7c23571806 | ||
|
|
ea378c8d82 | ||
|
|
0fb45974ef | ||
|
|
6490f9c128 | ||
|
|
2d7f1af52c | ||
|
|
ac27659a59 | ||
|
|
11d59c8bd1 | ||
|
|
a6936cbd02 | ||
|
|
3be84d76ef | ||
|
|
9aed1f344f | ||
|
|
4fff264630 | ||
|
|
2e444da2a3 | ||
|
|
7a216f95ca | ||
|
|
f22a9799a1 | ||
|
|
90bdc40393 | ||
|
|
622a97c8d8 | ||
|
|
03691c81c2 | ||
|
|
885b56c465 | ||
|
|
23cc7be231 | ||
|
|
bb82b0eb79 | ||
|
|
2e05f640b8 | ||
|
|
697a02ffee | ||
|
|
ad402021ed | ||
|
|
67caf6ef1f | ||
|
|
3277858c5a | ||
|
|
3fbedf323f | ||
|
|
144a6130f2 | ||
|
|
fa38c975b6 | ||
|
|
c14fa1021c | ||
|
|
5e78cc02bd | ||
|
|
429ef80fb9 | ||
|
|
e4d93cad27 | ||
|
|
d9a4840e37 | ||
|
|
a93070b98d | ||
|
|
c05a942862 | ||
|
|
f9a7879c1e | ||
|
|
bdfbc6d6a7 | ||
|
|
21181d8d8e | ||
|
|
1c7a27b816 | ||
|
|
e953f58c74 | ||
|
|
9250cee9e7 | ||
|
|
bb075d15ff | ||
|
|
a31f16bba7 | ||
|
|
88a41c37f3 | ||
|
|
d21885ca98 | ||
|
|
d774f8e870 | ||
|
|
08b5e66628 | ||
|
|
6fdfec3967 | ||
|
|
ab4616a3ad | ||
|
|
cb34ff4c83 | ||
|
|
bdbead256c | ||
|
|
f200086d01 | ||
|
|
ef97c3b42d | ||
|
|
ed8b41e8ec | ||
|
|
aada3371b7 | ||
|
|
38a9782bdf | ||
|
|
c6ac81dcf6 | ||
|
|
2d3f3f0fde | ||
|
|
993a2c7823 | ||
|
|
87b36cf7e3 | ||
|
|
742f2c8d9f | ||
|
|
0d7ac5f1d8 | ||
|
|
3adccff611 | ||
|
|
08b8bd27f9 | ||
|
|
1059c7e2be | ||
|
|
3ebcd5f738 | ||
|
|
4627ea1dec | ||
|
|
7ed24e78d5 | ||
|
|
ae02e3fd8d | ||
|
|
cc88d5962e | ||
|
|
70c8a524cd | ||
|
|
9a8efc8a58 | ||
|
|
f0f67b87c4 | ||
|
|
1a3ec98896 | ||
|
|
efff44cba9 | ||
|
|
7227418c4c | ||
|
|
29f93a9b73 | ||
|
|
532aa2acd0 | ||
|
|
6aca410f37 | ||
|
|
f20656b516 | ||
|
|
bcd7866e10 | ||
|
|
c1b8d44209 | ||
|
|
50e947fbbe | ||
|
|
189d969ee6 | ||
|
|
201790ff8d | ||
|
|
cc26ce4143 | ||
|
|
745e0aa525 | ||
|
|
6602b1587a | ||
|
|
6698b57f52 | ||
|
|
628268d47c | ||
|
|
56a635212a | ||
|
|
7e67eb17e0 | ||
|
|
935d72d672 | ||
|
|
fccb510186 | ||
|
|
e7ea0dc842 | ||
|
|
a40d6b0649 | ||
|
|
b7429a358f | ||
|
|
cf8b4e2f76 | ||
|
|
48c865e57a | ||
|
|
059764cd23 | ||
|
|
c205eee6fd | ||
|
|
7b22099608 | ||
|
|
b56b42d6fb | ||
|
|
ef6ef57e67 | ||
|
|
47d3cf1d45 | ||
|
|
9cfac9a884 | ||
|
|
dc172476e1 | ||
|
|
6fc7763380 | ||
|
|
ce45f21dba | ||
|
|
2de713c946 | ||
|
|
3d1dfe20b3 | ||
|
|
0404735ccb | ||
|
|
83f200f5a2 | ||
|
|
a29eb9b993 | ||
|
|
c939be97fb | ||
|
|
f31e62a532 | ||
|
|
34c195159e | ||
|
|
322fef2db1 | ||
|
|
7aa2565e89 | ||
|
|
8f3572f2d3 | ||
|
|
fb165ef28b | ||
|
|
a26acf4a25 | ||
|
|
645400e17f | ||
|
|
ccad52b80f | ||
|
|
f6303deaa4 | ||
|
|
d3a3083b85 | ||
|
|
99452056ec | ||
|
|
fca21bab4d | ||
|
|
9c58b77f01 | ||
|
|
b5c5f67fcc | ||
|
|
0f17423465 | ||
|
|
f093e29bd1 | ||
|
|
fe9b8e834d | ||
|
|
64f273120e | ||
|
|
31c1dd466b | ||
|
|
99b369bf45 | ||
|
|
e2a1535c44 | ||
|
|
3dfa88020e | ||
|
|
a220094941 | ||
|
|
ababdacf89 | ||
|
|
114dd5fc2a | ||
|
|
c058629172 | ||
|
|
79d7f577e4 | ||
|
|
631bafc6ab | ||
|
|
be58da539e | ||
|
|
a508ac9dda | ||
|
|
538fd2cef0 | ||
|
|
42557446a1 | ||
|
|
b9586481fc | ||
|
|
a0434e3271 | ||
|
|
15a5bcc21a | ||
|
|
edb838045b | ||
|
|
41a2e657b1 | ||
|
|
18b80a69c2 | ||
|
|
ce80fb39e8 | ||
|
|
2f19ff314b | ||
|
|
a0fd4b505a | ||
|
|
919aa70251 | ||
|
|
1aea4045a3 | ||
|
|
4cded0bf57 | ||
|
|
cd0585c7c4 | ||
|
|
8281279452 | ||
|
|
8f1bf846fe | ||
|
|
c26bf557d0 | ||
|
|
499b92cdd1 | ||
|
|
3dce5e162f | ||
|
|
f9de65c035 | ||
|
|
6f95e9a3cb | ||
|
|
ce09da084c | ||
|
|
5d50f92dd3 | ||
|
|
d054e085d6 | ||
|
|
59cdfa6fe6 | ||
|
|
ee0e9011b2 | ||
|
|
654ebe0c48 | ||
|
|
a40859c0bb | ||
|
|
74e3f6dee5 | ||
|
|
e553bfb204 | ||
|
|
96caace068 | ||
|
|
5310ccebbe | ||
|
|
ccd55257cd | ||
|
|
583687f3a7 | ||
|
|
e06dc86bf7 | ||
|
|
8828aa0621 | ||
|
|
e472e105f2 | ||
|
|
6ad10f1772 | ||
|
|
fcf7d98834 | ||
|
|
90ad06c65c | ||
|
|
888e9beb4c | ||
|
|
69b338ca85 | ||
|
|
0c009445d4 | ||
|
|
c5fb734e42 | ||
|
|
d2c8475504 | ||
|
|
f38d99653f | ||
|
|
cdce43d226 | ||
|
|
89ab0a7e97 | ||
|
|
c7250bfcba | ||
|
|
4a9b693da8 | ||
|
|
fff35aa820 | ||
|
|
a9a542d68f | ||
|
|
35ff3f0cbf | ||
|
|
abf92b6df3 | ||
|
|
8517fc9f24 | ||
|
|
21b473108f | ||
|
|
d4367f73a2 | ||
|
|
7ff46c3cd6 | ||
|
|
4a88e241b5 | ||
|
|
8da0317b19 | ||
|
|
297c2e244d | ||
|
|
a51d752a35 | ||
|
|
7800b0a7f5 | ||
|
|
5ce4104644 | ||
|
|
b4f1bbf793 | ||
|
|
ca33bea6b7 | ||
|
|
42d20ff693 | ||
|
|
22a14416ad | ||
|
|
98bd9bdaa0 | ||
|
|
13bac92a24 | ||
|
|
d9eb14d6e5 | ||
|
|
6ec0471e8b | ||
|
|
8008311d9c | ||
|
|
fcf16051a2 | ||
|
|
3a236456cd | ||
|
|
2606d77c62 | ||
|
|
3d2f33c120 | ||
|
|
4f14969464 | ||
|
|
7234f011ec | ||
|
|
5b949d6e00 | ||
|
|
2fd476ada8 | ||
|
|
2e7703bc97 | ||
|
|
9b69a6addd | ||
|
|
500243b0b3 | ||
|
|
3ef84dc1fc | ||
|
|
b04d68d087 | ||
|
|
715c381eb2 | ||
|
|
ca32ae4561 | ||
|
|
6b277c5e67 | ||
|
|
1ac64fd0b3 | ||
|
|
f2c1dd41d0 | ||
|
|
869360f26c | ||
|
|
dbdc334931 | ||
|
|
b317d1a171 | ||
|
|
b00c7b0ce3 | ||
|
|
a61eafeac2 | ||
|
|
5978b1c421 | ||
|
|
9f16799453 | ||
|
|
74fe0ee4e4 | ||
|
|
e8880232b3 | ||
|
|
556e9f1df7 | ||
|
|
beb301e781 | ||
|
|
056c809754 | ||
|
|
fa47eac9ff | ||
|
|
bf4a7846dd | ||
|
|
b0cc6dd714 | ||
|
|
d6e2d8e4a2 | ||
|
|
bbfc962727 | ||
|
|
8ddb357e5a | ||
|
|
560c10898f | ||
|
|
04f5214bb7 | ||
|
|
4c35d52234 | ||
|
|
01be5e3e23 | ||
|
|
46dc4102e0 | ||
|
|
cf31049c51 | ||
|
|
c624c4342f | ||
|
|
701d7baca8 | ||
|
|
8e45959483 | ||
|
|
6858d67897 | ||
|
|
9b23cbd2c2 | ||
|
|
ec1bb300e2 | ||
|
|
69a8cad47b | ||
|
|
bcaca0eca3 | ||
|
|
a1f79e58db | ||
|
|
2cc5fdcf62 | ||
|
|
dd1b4e21f5 | ||
|
|
91eb052c22 | ||
|
|
ecefcfabc0 | ||
|
|
8e42e71528 | ||
|
|
a2275ae111 | ||
|
|
3976803d8f | ||
|
|
7921fc74fd | ||
|
|
1732acfee2 | ||
|
|
3a4fc65712 | ||
|
|
9ce9caba02 | ||
|
|
07f4995c8f | ||
|
|
8200c7248a | ||
|
|
bd9f1d5398 | ||
|
|
ce0ca23d79 | ||
|
|
35863c8d3a | ||
|
|
dad73ce9df | ||
|
|
f716692591 | ||
|
|
6387114a18 | ||
|
|
55ab39ca55 | ||
|
|
28a4f724d5 | ||
|
|
f2d06bcea1 | ||
|
|
4d1771614a | ||
|
|
61efabb3b5 | ||
|
|
b00458c2b9 | ||
|
|
594f9822c7 | ||
|
|
9b3131b95e | ||
|
|
d7ad32a8cc | ||
|
|
59127e4029 | ||
|
|
7edf70a77b | ||
|
|
3fd90a37fb | ||
|
|
356ec276bc | ||
|
|
ad8d945c1d | ||
|
|
a3bf30a77b | ||
|
|
3f829a3114 | ||
|
|
537b4ae003 | ||
|
|
08b4c9ea5c | ||
|
|
6b00f5a97d | ||
|
|
d259df9a47 | ||
|
|
aeaebd082f | ||
|
|
0c9c0f2032 | ||
|
|
155801ab2b | ||
|
|
deeeb06488 | ||
|
|
28fd719ce3 | ||
|
|
317075aa6d | ||
|
|
eaf2efc510 | ||
|
|
88555bbea2 | ||
|
|
a4d7bf4ebe | ||
|
|
50e5c21735 | ||
|
|
2ddacf48d4 | ||
|
|
cd66d2c7b0 | ||
|
|
2e7c26c539 | ||
|
|
f0f47530bf | ||
|
|
e7be4e3e49 | ||
|
|
7a7ce7fcea | ||
|
|
bd104a7ea8 | ||
|
|
31d6789ff0 | ||
|
|
bdcb7372a5 | ||
|
|
b8df2226ae | ||
|
|
106bae5c97 | ||
|
|
651297fa0b | ||
|
|
1b2873fc5f | ||
|
|
f82a2b3bc5 | ||
|
|
4cb616ebeb | ||
|
|
1bcc975d7b | ||
|
|
98fd5b8858 | ||
|
|
6f08482aaf | ||
|
|
f7d06b9759 | ||
|
|
a178fed0c6 | ||
|
|
ff342f6789 | ||
|
|
b1c2f8faa1 | ||
|
|
6f59e79b28 | ||
|
|
6c22bad77a | ||
|
|
1cf0125d1b | ||
|
|
49211482b0 | ||
|
|
b3aecec11d | ||
|
|
7cb86d0254 | ||
|
|
0ba9389ca2 | ||
|
|
1902787104 | ||
|
|
b3e32db073 | ||
|
|
01d53bdb85 | ||
|
|
51acc34a80 | ||
|
|
694447e66c | ||
|
|
05b7a610ef | ||
|
|
1e799402ee | ||
|
|
5f9c61c4b4 | ||
|
|
12b6791e8b | ||
|
|
8024ad123e | ||
|
|
2bd2f5a5aa | ||
|
|
37d79b2a1c | ||
|
|
cd057045e6 | ||
|
|
ddfbd69e8b | ||
|
|
28fa1264b7 | ||
|
|
a6c3189833 | ||
|
|
0d2fe08dc9 | ||
|
|
8cfe25bfac | ||
|
|
f9e2a677d9 | ||
|
|
2ef1c54417 | ||
|
|
f9c3c0c8ae | ||
|
|
44a15551ba | ||
|
|
732e181312 | ||
|
|
919187f7fd | ||
|
|
7840a5ea49 | ||
|
|
3a33ac1455 | ||
|
|
2eb7d8ba91 | ||
|
|
7ec5cce2c8 | ||
|
|
8fdc6c11e1 | ||
|
|
1a110de597 | ||
|
|
317aa591c3 | ||
|
|
0d44f82c86 | ||
|
|
9cac61dc33 | ||
|
|
52481a6e8b | ||
|
|
b3e18f4e56 | ||
|
|
aa25ac774e | ||
|
|
af7da586aa | ||
|
|
b671da900a | ||
|
|
d8bb83e8c4 | ||
|
|
bc3d64a2ff | ||
|
|
70e72c246d | ||
|
|
7c8c82edd7 | ||
|
|
5dc556f0af | ||
|
|
c8c69f1a66 | ||
|
|
985019e117 | ||
|
|
b9620b3a21 | ||
|
|
e7bb4a8ec6 | ||
|
|
bb5f935d7a | ||
|
|
17dad27610 | ||
|
|
ee960d76c7 | ||
|
|
dfb595193a | ||
|
|
0a91a40c1b | ||
|
|
12f4305691 | ||
|
|
dd865b0dfb | ||
|
|
7cbfa9fcd4 | ||
|
|
28d880a7c4 | ||
|
|
e83d45fce5 | ||
|
|
693ff3cc66 | ||
|
|
706b095f95 | ||
|
|
8e2e96d513 | ||
|
|
28bce44f69 | ||
|
|
53d88dfd50 | ||
|
|
42daf7ed32 | ||
|
|
e5402ea7c1 | ||
|
|
05e8d6b578 | ||
|
|
505f340917 | ||
|
|
46856c9394 | ||
|
|
c63a2ad39d | ||
|
|
68a7078614 | ||
|
|
2e15f1e2a3 | ||
|
|
a790838222 | ||
|
|
cd14360d62 | ||
|
|
74c94a60a6 | ||
|
|
56cbc7683e | ||
|
|
bf778d2fca | ||
|
|
6e5bca8306 | ||
|
|
7e8fea6ed3 | ||
|
|
43ff3e11ed | ||
|
|
f1750e3c35 | ||
|
|
8b1fe26c84 | ||
|
|
6ab09a3603 | ||
|
|
df45298c1f | ||
|
|
4a64dce737 | ||
|
|
27ad428b5e | ||
|
|
2da33bae43 | ||
|
|
3dab683a45 | ||
|
|
d08fa37ccf | ||
|
|
4cd7976f63 | ||
|
|
569e8b6180 | ||
|
|
9a443bd08e | ||
|
|
efb37ae709 | ||
|
|
4658ef9918 | ||
|
|
2b6a1c9cb1 | ||
|
|
c4606b1854 | ||
|
|
d47b946d00 | ||
|
|
a886e8087d | ||
|
|
d473455680 | ||
|
|
40245ef43b | ||
|
|
aa057d6a57 | ||
|
|
5c03e64f46 | ||
|
|
1d4d156749 | ||
|
|
4fc6da1fa1 | ||
|
|
9fac48fcea | ||
|
|
ff4fdd3740 | ||
|
|
da1cce035a | ||
|
|
49292bbb2d | ||
|
|
3e502db772 | ||
|
|
426351bb54 | ||
|
|
4d6e244100 | ||
|
|
71253b23d5 | ||
|
|
076ff7c7ba | ||
|
|
94b6c7975a | ||
|
|
0b08010221 | ||
|
|
bc67113d77 | ||
|
|
5974bdcc2a | ||
|
|
f9696287a4 | ||
|
|
807dede90a | ||
|
|
1f2b37b70e | ||
|
|
7288ba0fd7 | ||
|
|
f48c17cf88 | ||
|
|
fba9cc3739 | ||
|
|
e04388a230 | ||
|
|
fbe3be169d | ||
|
|
4e4ae0fca0 | ||
|
|
2f278c8618 | ||
|
|
905ae3437b | ||
|
|
7679721007 | ||
|
|
9f29a2e04f | ||
|
|
ff5bf0c6e5 | ||
|
|
0eb8c2b7ba | ||
|
|
deb140e3bc | ||
|
|
9a1710eb27 | ||
|
|
e478238d77 | ||
|
|
c9c9410908 | ||
|
|
42372cabd5 | ||
|
|
ab9c991530 | ||
|
|
9429d84cf8 | ||
|
|
b22774a599 | ||
|
|
ca3b0a2ab1 | ||
|
|
62f76d08ad | ||
|
|
92bff24d43 | ||
|
|
eddd3e508f | ||
|
|
649eb65bb1 | ||
|
|
8fb3348a7c | ||
|
|
6c2df1a783 | ||
|
|
55afc98108 | ||
|
|
b875eb31d2 | ||
|
|
060b031272 | ||
|
|
95c4c4a238 | ||
|
|
4424e48926 | ||
|
|
b8c7876454 | ||
|
|
8256f60340 | ||
|
|
1d0cb45e4c | ||
|
|
d1a184e3f2 | ||
|
|
aa38b31015 | ||
|
|
466067bd95 | ||
|
|
c9b56efaaa | ||
|
|
e56920edee | ||
|
|
0e0c475c83 | ||
|
|
241190c4da | ||
|
|
69d0c31ae5 | ||
|
|
c3993fd943 | ||
|
|
8a3b7d7c1a | ||
|
|
87f14617cc | ||
|
|
9f24f765ea | ||
|
|
48c802e5cc | ||
|
|
388d141d82 | ||
|
|
7fa93e97db | ||
|
|
030d241130 | ||
|
|
bac392d331 | ||
|
|
1542d5e386 | ||
|
|
f1b6b3386a | ||
|
|
7d05999ced | ||
|
|
d477feb930 | ||
|
|
9465a6d2b5 | ||
|
|
c3f78b698d | ||
|
|
a4e699f781 | ||
|
|
cd871e5aad | ||
|
|
c9b215684c | ||
|
|
82a4c706b0 | ||
|
|
082e6f5e99 | ||
|
|
b90c7c09b6 | ||
|
|
1ee72a6ce5 | ||
|
|
f72a8c5c06 | ||
|
|
ba5db2c15f | ||
|
|
f5b89ca783 | ||
|
|
4ef840e210 | ||
|
|
e2d0ee125f | ||
|
|
8e1dbc03d9 | ||
|
|
9c0893fa8c | ||
|
|
0dfb97c5f7 | ||
|
|
cb52af28e7 | ||
|
|
4b4384b1a8 | ||
|
|
62483e748b | ||
|
|
9b10984d81 | ||
|
|
c5e283a13e | ||
|
|
df918e8529 | ||
|
|
aa1193a9eb | ||
|
|
8f7f263a48 | ||
|
|
0bad227548 | ||
|
|
78bcd3e1bb | ||
|
|
554640c345 | ||
|
|
baecc256f6 | ||
|
|
10a2a4cf5b | ||
|
|
c5dc073d49 | ||
|
|
d48e4c66b2 | ||
|
|
d357142075 | ||
|
|
af17381e04 | ||
|
|
2e7d339d7e | ||
|
|
c6cddad13c | ||
|
|
54bd54b521 | ||
|
|
8cbbb970e1 | ||
|
|
fbbf7f75c3 | ||
|
|
5a23b95352 | ||
|
|
6f9675b6d6 | ||
|
|
bb8ce30552 | ||
|
|
581e2ad431 | ||
|
|
45deb493ba | ||
|
|
4531d0ee32 | ||
|
|
a26e8a5f83 | ||
|
|
be74fbd677 | ||
|
|
ee8c83bbd8 | ||
|
|
dbd32abdd1 | ||
|
|
f300287814 | ||
|
|
dd361bb9ad | ||
|
|
744205cb6e | ||
|
|
86c22c9fdd | ||
|
|
d7a66f6782 | ||
|
|
902f310eb0 | ||
|
|
3bb107e192 | ||
|
|
78f8b1454d | ||
|
|
70ba9a4db5 | ||
|
|
93d9c44585 | ||
|
|
f0bb462f1c | ||
|
|
151e8e8f17 | ||
|
|
23f474b003 | ||
|
|
15104de84c | ||
|
|
55e4875662 | ||
|
|
5c3165efa2 | ||
|
|
3690a53dc9 | ||
|
|
fb119cc208 | ||
|
|
7343304284 | ||
|
|
c1ecc62ac1 | ||
|
|
bd40615f8e | ||
|
|
0df8d096f0 | ||
|
|
749dd1e8e3 | ||
|
|
ade6558769 | ||
|
|
6303b172b1 | ||
|
|
636ae193a7 | ||
|
|
aff8493204 | ||
|
|
8f2d4494d5 | ||
|
|
7a2bb4eb96 | ||
|
|
4916c06797 | ||
|
|
a818ab0942 | ||
|
|
9aa89393c0 | ||
|
|
216b304fe1 | ||
|
|
cf8a687b4d | ||
|
|
d1d6ba4114 | ||
|
|
a5ac713310 | ||
|
|
6fa8c33672 | ||
|
|
dd2b019d3c | ||
|
|
3a12ad192f | ||
|
|
d669b75352 | ||
|
|
746d50d459 | ||
|
|
f741552d91 | ||
|
|
c38e4b86b4 | ||
|
|
b044d85e90 | ||
|
|
24d111f026 | ||
|
|
f50058e3c2 | ||
|
|
ce9445168c | ||
|
|
27b8b4b35a | ||
|
|
9ccb67db8a | ||
|
|
3d82afd4e6 | ||
|
|
5f9cb61160 | ||
|
|
7f54b70c24 | ||
|
|
a156dce281 | ||
|
|
c0b0e58720 | ||
|
|
170ffbae04 | ||
|
|
6755ec7eb0 | ||
|
|
c8bc9a096a | ||
|
|
dc7f5a562b | ||
|
|
3b5b51578d | ||
|
|
41188a1bd6 | ||
|
|
d9bafe34eb | ||
|
|
43dbba5378 | ||
|
|
168ed5ac56 | ||
|
|
e5b4a55d8e | ||
|
|
167c057e8a | ||
|
|
71bf054ab1 | ||
|
|
111fcd77c4 | ||
|
|
287c1c4ffa | ||
|
|
3d69dc786d | ||
|
|
57a3f20c13 | ||
|
|
eab2c57594 | ||
|
|
b019962f34 | ||
|
|
5c59c819b6 | ||
|
|
93b97b8d72 | ||
|
|
f1f922031a | ||
|
|
390cace775 | ||
|
|
28fdad9426 | ||
|
|
9155c13e08 | ||
|
|
e8776d44c5 | ||
|
|
5ee8d04800 | ||
|
|
eb31934fb7 | ||
|
|
000f86d318 | ||
|
|
d9b3ebc82f | ||
|
|
91b3ca047a | ||
|
|
08131e42af | ||
|
|
a013553a6c | ||
|
|
7b2fe8eb4a | ||
|
|
610f782054 | ||
|
|
94700f4064 | ||
|
|
0f12ebb31c | ||
|
|
00a8a9ac0e | ||
|
|
92616c6ae3 | ||
|
|
97db618cd8 | ||
|
|
2832d308f1 | ||
|
|
a7ecdf715f | ||
|
|
935dc3ff9f | ||
|
|
99d14e8cbe | ||
|
|
664fde2344 | ||
|
|
27c45eface | ||
|
|
f83bc3c8b3 | ||
|
|
0d5efb8d27 | ||
|
|
bf9c1c1875 | ||
|
|
049d866f62 | ||
|
|
d672f0c2ad | ||
|
|
7b040e8583 | ||
|
|
e1cf285272 | ||
|
|
53f7f13362 | ||
|
|
f710677cdc | ||
|
|
b5fbc8b632 | ||
|
|
1fdc0a196c | ||
|
|
559f429f5e | ||
|
|
ae4b198d3d | ||
|
|
e088ecbbad | ||
|
|
f6b7bd5b44 | ||
|
|
f5a21f64c0 | ||
|
|
f5cbed7c0c | ||
|
|
59fff8928b | ||
|
|
8743b49a17 | ||
|
|
19dc91937e | ||
|
|
4846cb7b14 | ||
|
|
54e0ac816b | ||
|
|
9ce960b3d7 | ||
|
|
cc9d1c4bfd | ||
|
|
d211555beb | ||
|
|
9350557d10 | ||
|
|
ea6c54cad0 | ||
|
|
81287602d7 | ||
|
|
441ba79ec7 | ||
|
|
c9e4a09da6 | ||
|
|
c84f4e2bc0 | ||
|
|
6938f8ab89 | ||
|
|
4fdd2c851f | ||
|
|
78b56712fc | ||
|
|
6dcdb0f9a0 | ||
|
|
cfab837b96 | ||
|
|
b8461cdeb6 | ||
|
|
e07d65ef6b | ||
|
|
80cbc35c89 | ||
|
|
2faf84780f | ||
|
|
ebdb1a8836 | ||
|
|
2dfa51652a | ||
|
|
1cf1ea230f | ||
|
|
86e127ebff | ||
|
|
a313265785 | ||
|
|
37693ad08d | ||
|
|
e8180d252b | ||
|
|
9957c95c68 | ||
|
|
3354d53a48 | ||
|
|
ddaa53b940 | ||
|
|
c2e04a11bf | ||
|
|
00d059b8df | ||
|
|
f3fff6f1c5 | ||
|
|
9360eb6a70 | ||
|
|
26fb03e6c8 | ||
|
|
eed3d021d9 | ||
|
|
48728e8418 | ||
|
|
f39f25760a | ||
|
|
10ea60daaf | ||
|
|
f2fa5e140b | ||
|
|
be5ff35b13 | ||
|
|
bd5d73d1e6 | ||
|
|
cc712b0c75 | ||
|
|
a5ac84e1b9 | ||
|
|
cbe1f762ca | ||
|
|
e6db49c20c | ||
|
|
4909fcc8b4 | ||
|
|
68cdfd00b0 | ||
|
|
323c16ebe1 | ||
|
|
c20a38b7b0 | ||
|
|
20993342a2 | ||
|
|
1b3198c143 | ||
|
|
173d0290ea | ||
|
|
ae4cd8da12 | ||
|
|
a494398332 | ||
|
|
c82bdd1f5a | ||
|
|
e6ac3ccfad | ||
|
|
b13b58a2b2 | ||
|
|
2b6790f83f | ||
|
|
fe30850568 | ||
|
|
493c71b89e | ||
|
|
deaebc373c | ||
|
|
e834489206 | ||
|
|
1b316e462e | ||
|
|
7bf75128a8 | ||
|
|
3857e8d49f | ||
|
|
b16149b842 | ||
|
|
32ed8a4d8d | ||
|
|
f949d2476b | ||
|
|
0620830b10 | ||
|
|
7ee25693aa | ||
|
|
7cb86add64 | ||
|
|
fbc9720f7a | ||
|
|
ffdd37ddd5 | ||
|
|
51062bc80b | ||
|
|
31533b5ef8 | ||
|
|
3649b595df | ||
|
|
83b7c9aa32 | ||
|
|
4e3c59a2da | ||
|
|
dcbfe90cf7 | ||
|
|
f69be86c74 | ||
|
|
820722f44e | ||
|
|
2ff806dedc | ||
|
|
a755badd5f | ||
|
|
4df1e5393b | ||
|
|
8c55bd179f | ||
|
|
ccfc5ece66 | ||
|
|
6c12f1bc86 | ||
|
|
88839e9610 | ||
|
|
4f6a733238 | ||
|
|
dc9083a764 | ||
|
|
6f231b840b | ||
|
|
61a703e605 | ||
|
|
8e0e9734a5 | ||
|
|
2afb455bac | ||
|
|
01792f91e2 | ||
|
|
651dff0750 | ||
|
|
9cbfbd41dc | ||
|
|
168d6f403c | ||
|
|
41f200e630 | ||
|
|
0809cfdc6d | ||
|
|
466d739da8 | ||
|
|
daf65cb387 | ||
|
|
2605f60983 | ||
|
|
fcf6cdb134 | ||
|
|
209258b507 | ||
|
|
f80bc214f9 | ||
|
|
f76990bb9b | ||
|
|
eef68c9b31 | ||
|
|
006c2ae186 | ||
|
|
8b66ff6afe | ||
|
|
4cc6d57e6e | ||
|
|
af1c6b22bb | ||
|
|
5958990ed5 | ||
|
|
cd4cbdc197 | ||
|
|
1f538be16e | ||
|
|
9703439a4c | ||
|
|
42203ba872 | ||
|
|
cd60b852a1 | ||
|
|
5b1d9e1a0d | ||
|
|
e02657a7c7 | ||
|
|
c352eb0c74 | ||
|
|
81b9d5da09 | ||
|
|
b9b0413510 | ||
|
|
97770da619 | ||
|
|
521623797e | ||
|
|
15d3414443 | ||
|
|
245f06c93a | ||
|
|
33899f0e2f | ||
|
|
4697fbdeef | ||
|
|
06174d6afb | ||
|
|
b2bb16c1e0 | ||
|
|
27aae279e6 | ||
|
|
e9ee93beb7 | ||
|
|
20941dedd3 | ||
|
|
5ac88623ed | ||
|
|
768508dd4b | ||
|
|
668633e764 | ||
|
|
3dbb1f034d | ||
|
|
1270e5d15c | ||
|
|
afec8480fb | ||
|
|
8720511046 | ||
|
|
a7a00ecf40 | ||
|
|
88bbafd3e8 | ||
|
|
ae3258b449 | ||
|
|
cf4d7cfeef | ||
|
|
46ee2a0568 | ||
|
|
ce250c85fc | ||
|
|
1087b9f5df | ||
|
|
0dc74d9d14 | ||
|
|
98b272383f | ||
|
|
e722daafd0 | ||
|
|
670ab059d1 | ||
|
|
09f826ceba | ||
|
|
df5e3c9be9 | ||
|
|
31c336b3b1 | ||
|
|
b3a419b2f3 | ||
|
|
11f63fa6ce | ||
|
|
4b35059140 | ||
|
|
7836feba63 | ||
|
|
1e54366f1f | ||
|
|
d039bb0e03 | ||
|
|
166d32f073 | ||
|
|
321890992e | ||
|
|
d644403d8a | ||
|
|
44e76f9518 | ||
|
|
f9703eca4c | ||
|
|
48551e8bf5 | ||
|
|
74b538805b | ||
|
|
35f493beff | ||
|
|
06d11c9133 | ||
|
|
da7eb615db | ||
|
|
5da5024ad3 | ||
|
|
c1346d4c86 | ||
|
|
c38a4a15ff | ||
|
|
bdec94ead6 | ||
|
|
69bebf202f | ||
|
|
38895afea6 | ||
|
|
70ed4188b6 | ||
|
|
d889094863 | ||
|
|
c96eb8753e | ||
|
|
5a7607f6c6 | ||
|
|
71bb88529a | ||
|
|
b26164a168 | ||
|
|
19444551e4 | ||
|
|
e0b2a6e627 | ||
|
|
2973765866 | ||
|
|
2a08a25064 | ||
|
|
a55e291908 | ||
|
|
2003d37a9a | ||
|
|
29145bf6cf | ||
|
|
caa1ff120a | ||
|
|
ef4e964c94 | ||
|
|
1f263f60a7 | ||
|
|
5fc7cafcbe | ||
|
|
397c4926eb | ||
|
|
a14544398b | ||
|
|
d439dceac1 | ||
|
|
af29b31ea8 | ||
|
|
b311ef70bc | ||
|
|
9f9d744406 | ||
|
|
e094871bc9 | ||
|
|
dfc95cee45 | ||
|
|
fdca234721 | ||
|
|
cf5cc626d7 | ||
|
|
358d9aac7d | ||
|
|
681bc580c4 | ||
|
|
4a2768f8d1 | ||
|
|
05f8773fa0 | ||
|
|
5e5fdfdd51 | ||
|
|
5e744390c0 | ||
|
|
cb5fa401cb | ||
|
|
2980860377 | ||
|
|
9ff0b282f3 | ||
|
|
4bc1c032bd | ||
|
|
9fcb00f10b | ||
|
|
723e461559 | ||
|
|
9b24e6d448 | ||
|
|
e622774775 | ||
|
|
84ce9bc94b | ||
|
|
a3a1bc30b1 | ||
|
|
b4c9a7698e | ||
|
|
8b2d7fc32f | ||
|
|
90e66cbd94 | ||
|
|
fd9a7080ea | ||
|
|
c0fad106f0 | ||
|
|
78c8243184 | ||
|
|
780abecd53 | ||
|
|
ea6896816d | ||
|
|
95a456860a | ||
|
|
b1b2fda155 | ||
|
|
5847f534c3 | ||
|
|
e8e8163fa7 | ||
|
|
6a21d82dcf | ||
|
|
d6b47656bc | ||
|
|
8c37ef3a95 | ||
|
|
35deed1d10 | ||
|
|
ba32a665f1 | ||
|
|
bbd19be554 | ||
|
|
c360cc6db6 | ||
|
|
7e4b9af315 | ||
|
|
9b03e6b124 | ||
|
|
013e16e15f | ||
|
|
180ec52798 | ||
|
|
c7b47b4453 | ||
|
|
4b00db7662 | ||
|
|
78a7b995d2 | ||
|
|
f7c50a123a | ||
|
|
9617d17aca | ||
|
|
d9884ddf73 | ||
|
|
8157f0a958 | ||
|
|
b7580a5f83 | ||
|
|
66703b30b3 | ||
|
|
53677e3c64 | ||
|
|
feef6a1756 | ||
|
|
5f299b895b | ||
|
|
4e1bb5fbac | ||
|
|
47ccc513ad | ||
|
|
cce1a01936 | ||
|
|
6f2b1a6a76 | ||
|
|
8526907f50 | ||
|
|
bc192a8e54 | ||
|
|
9ff6f8fc52 | ||
|
|
6573bd6b4b | ||
|
|
9dc3f614af | ||
|
|
3888b8cceb | ||
|
|
294df4a2b3 | ||
|
|
265dd37212 | ||
|
|
eb7c79ad27 | ||
|
|
de111c7100 | ||
|
|
e892c9a824 | ||
|
|
5eb0e18cae | ||
|
|
27cabb398e | ||
|
|
64dbb14241 | ||
|
|
bb4e2be9eb | ||
|
|
7d1de0da17 | ||
|
|
bf16c9a42b | ||
|
|
1a7b1ce499 | ||
|
|
efc9bc71a7 | ||
|
|
fc5b315af0 | ||
|
|
7a4a78628d | ||
|
|
d16fb30a62 | ||
|
|
2d177e660e | ||
|
|
2f131dc170 | ||
|
|
94810e371a | ||
|
|
59731878f6 | ||
|
|
54ede8aa18 | ||
|
|
b415b6b043 | ||
|
|
70c922cdc5 | ||
|
|
068fc32cb2 | ||
|
|
3dcdacc3b8 | ||
|
|
a6594358d8 | ||
|
|
f98921da46 | ||
|
|
25747fbcf2 | ||
|
|
aac5c2b13c | ||
|
|
cc810a5b6f | ||
|
|
1b3592d959 | ||
|
|
d75614e9a7 | ||
|
|
08703e282f | ||
|
|
2904baf44e | ||
|
|
f99e46bf75 | ||
|
|
9f87890ead | ||
|
|
638184cf66 | ||
|
|
03babfe75c | ||
|
|
238ed3c788 | ||
|
|
6a9d931ba3 | ||
|
|
a3d2a9e00b | ||
|
|
39b88e8207 | ||
|
|
449c6dfde5 | ||
|
|
7cc47ca0b1 | ||
|
|
95f4a83f41 | ||
|
|
35154dc7a3 | ||
|
|
0fd0d7d080 | ||
|
|
658265c938 | ||
|
|
38fe9e7e1c | ||
|
|
77056dcf8d | ||
|
|
026683a8e1 | ||
|
|
6ab6dd6ac3 | ||
|
|
83de3482ce | ||
|
|
919a35aed3 | ||
|
|
ad3defb071 | ||
|
|
9c929ecd1b | ||
|
|
f79c9f7cf1 | ||
|
|
8e75c345d9 | ||
|
|
44886d9aad | ||
|
|
c2d444347d | ||
|
|
5cb497596d | ||
|
|
1857469d2f | ||
|
|
ea71b4843d | ||
|
|
97727e2e3d | ||
|
|
f81e7da8bb | ||
|
|
8e827bf83b | ||
|
|
9e1fa284ca | ||
|
|
3bf800be6e | ||
|
|
635b9f9dba | ||
|
|
52a0d7cf7b | ||
|
|
a34516932b | ||
|
|
929a2a30a2 | ||
|
|
ffa88eeb08 | ||
|
|
51b45b4ed4 | ||
|
|
f263844793 | ||
|
|
18c46df9aa | ||
|
|
15846e157b | ||
|
|
bc59f2db0d | ||
|
|
cd2be8c1a4 | ||
|
|
f958115c50 | ||
|
|
e7d677bfb6 | ||
|
|
3e80ffc52b | ||
|
|
d0c7a5c076 | ||
|
|
f3f4e6b354 | ||
|
|
5a45b25614 | ||
|
|
0b5ee1edfc | ||
|
|
da3dc599f9 | ||
|
|
f013b435ab | ||
|
|
5f6975a113 | ||
|
|
c5dee29e4b | ||
|
|
633ee02f13 | ||
|
|
6b750c909a | ||
|
|
5f8b6640a9 | ||
|
|
dd42d8437c | ||
|
|
67a178591d | ||
|
|
f5e5659c1f | ||
|
|
8b0f0fb615 | ||
|
|
209116e766 | ||
|
|
79392ab656 | ||
|
|
3ca1207231 | ||
|
|
cec1b147f2 | ||
|
|
46cfcfa3e7 | ||
|
|
b833e8dfa2 | ||
|
|
77b843efd8 | ||
|
|
db72ad7c60 | ||
|
|
eadc630fcb | ||
|
|
170c1793cc | ||
|
|
9f7c6c2d0c | ||
|
|
72d054c55c | ||
|
|
524edfe7c2 | ||
|
|
c25c5623d2 | ||
|
|
4f38b77ef6 | ||
|
|
5862803434 | ||
|
|
5b3beded39 | ||
|
|
c61fb7a598 | ||
|
|
33d9148029 | ||
|
|
63969f5a33 | ||
|
|
edde18aeef | ||
|
|
657116d361 | ||
|
|
e16269daa8 | ||
|
|
c07591ff5c | ||
|
|
75a478ad54 | ||
|
|
8dae8b1a7f | ||
|
|
15fd8cf486 | ||
|
|
55333156ac | ||
|
|
8cdcba3231 | ||
|
|
8bab9e84e2 | ||
|
|
2faae83912 | ||
|
|
5a61a11a61 | ||
|
|
a6d71988f2 | ||
|
|
7069e242ae | ||
|
|
56ee830558 | ||
|
|
6dd12729e6 | ||
|
|
14a48303cb | ||
|
|
72cf6c9c0f | ||
|
|
144ee6b8ca | ||
|
|
8967d86da6 | ||
|
|
18c6edbb5d | ||
|
|
53de3c4717 | ||
|
|
ad577e4e81 | ||
|
|
44811a3e7c | ||
|
|
1ab3f05b3a | ||
|
|
5e76488ae7 | ||
|
|
32771fe7e1 | ||
|
|
9b40cc6881 | ||
|
|
2e35260bbb | ||
|
|
a067704277 | ||
|
|
de281818ac | ||
|
|
c49bfad38d | ||
|
|
c1ba591b26 | ||
|
|
719af38a61 | ||
|
|
ac61dfae6b | ||
|
|
813fb679a7 | ||
|
|
e7562781f7 | ||
|
|
56d36b7f53 | ||
|
|
53b3f7f821 | ||
|
|
08a53156bd | ||
|
|
8985cd6309 | ||
|
|
3833da7410 | ||
|
|
4210cd10db | ||
|
|
a7bd1c6892 | ||
|
|
52b0111afa | ||
|
|
7921d128e4 | ||
|
|
d7e838701a | ||
|
|
289bcb22aa | ||
|
|
3fe57b7983 | ||
|
|
32e92c2a16 | ||
|
|
1b3d208540 | ||
|
|
6a8bf0aa62 | ||
|
|
56715556ed | ||
|
|
838330b909 | ||
|
|
69553b138b | ||
|
|
36d7a02994 | ||
|
|
301528e2d2 | ||
|
|
0303b45707 | ||
|
|
ba722e8ed5 | ||
|
|
289e5a5442 | ||
|
|
fdad96e2bc | ||
|
|
af994e4dae | ||
|
|
006d68e279 | ||
|
|
29dc122ad3 | ||
|
|
cf4a8c6204 | ||
|
|
3c73fe92bf | ||
|
|
6637590797 | ||
|
|
b8bab11acd | ||
|
|
a2f600feac | ||
|
|
80dd62ef0a | ||
|
|
827b1c9cd8 | ||
|
|
2e4fcf803d | ||
|
|
d00d95fc6f | ||
|
|
3e3ab9bd25 | ||
|
|
6eecc7722d | ||
|
|
ada4aaf69a | ||
|
|
93244c1f78 | ||
|
|
be056cea6b | ||
|
|
659ca8be14 | ||
|
|
ea9af8366d | ||
|
|
80edd47d36 | ||
|
|
d7746b3649 | ||
|
|
c4c4fbc34c | ||
|
|
59f57c96e9 | ||
|
|
a2f852fecf | ||
|
|
ad114ed329 | ||
|
|
c4c3d0f07f | ||
|
|
6cf8102de5 | ||
|
|
e7e4aa2218 | ||
|
|
6d84f4b6c1 | ||
|
|
ce3e9ffd11 | ||
|
|
3ada260e0e | ||
|
|
8fdd0cb795 | ||
|
|
913e05a2e6 | ||
|
|
fa1f703ef6 | ||
|
|
4004c53e1b | ||
|
|
4838670649 | ||
|
|
a985e09282 | ||
|
|
9bd1503cb4 | ||
|
|
a2ccbf7844 | ||
|
|
61bbe8a905 | ||
|
|
59bc5d22d1 | ||
|
|
1423d5b314 | ||
|
|
152d0eb1d0 | ||
|
|
6426d1df06 | ||
|
|
9284eb3fe9 | ||
|
|
afdae8bc1e | ||
|
|
2a7085e593 | ||
|
|
2408fb3ed4 | ||
|
|
8316afb176 | ||
|
|
e59fd098a3 | ||
|
|
e044199693 | ||
|
|
8f8e29fc22 | ||
|
|
8de5384158 | ||
|
|
216c659335 | ||
|
|
041ca8a5d3 | ||
|
|
fe4f1b306d | ||
|
|
a0972d99fb | ||
|
|
e332bfef7c | ||
|
|
cba5e226d8 | ||
|
|
5aff0c4943 | ||
|
|
cb49c00f4d | ||
|
|
e26d797d57 | ||
|
|
938581527e | ||
|
|
c38ae09735 | ||
|
|
28c3cfe084 | ||
|
|
4a2823bcba | ||
|
|
18eba02026 | ||
|
|
d4690ce580 | ||
|
|
a785c450b1 | ||
|
|
7480dc4a19 | ||
|
|
ad01891a67 | ||
|
|
67fe35d564 | ||
|
|
7f19b6957a | ||
|
|
0a54caf202 | ||
|
|
4b4c1c7f8f | ||
|
|
d071f3947e | ||
|
|
b3d99cd210 | ||
|
|
90e696f82c | ||
|
|
958fcd1cfa | ||
|
|
8f57c7dcb3 | ||
|
|
77262f52a4 | ||
|
|
16bfbc8a12 | ||
|
|
1fd375b875 | ||
|
|
46131ad39d | ||
|
|
0b5c5b2ae9 | ||
|
|
55be174037 | ||
|
|
a17b7025f1 | ||
|
|
170cf7fd77 | ||
|
|
23cdb4d326 | ||
|
|
cbbe529572 | ||
|
|
0b382426e9 | ||
|
|
1cbbf9baa4 | ||
|
|
8d41ff7b79 | ||
|
|
e3b6057bf8 | ||
|
|
66a4042cad | ||
|
|
56c08d8302 | ||
|
|
d4e759754d | ||
|
|
a96e171cbf | ||
|
|
bd4a8c8397 | ||
|
|
04f71b3b43 | ||
|
|
d124de51db | ||
|
|
d87d12a0f5 | ||
|
|
f2b08346d0 | ||
|
|
d3682a6727 | ||
|
|
371bbd9508 | ||
|
|
a8a28f442f | ||
|
|
65ddd8a736 | ||
|
|
8bb27de233 | ||
|
|
37e2f097ba | ||
|
|
1966d87ce6 | ||
|
|
7b8c86e1e3 | ||
|
|
de634da513 | ||
|
|
96836e2d6c | ||
|
|
8a9d576f61 | ||
|
|
791d12fbb4 | ||
|
|
d1329be2fa | ||
|
|
3ed6561702 | ||
|
|
7a0587f433 | ||
|
|
0fe682bfe6 | ||
|
|
0f685e8789 | ||
|
|
420771c233 | ||
|
|
5e3e9271ca | ||
|
|
1e603c0833 | ||
|
|
03e1673e92 | ||
|
|
9f992f003d | ||
|
|
f50244a41f | ||
|
|
8b9607f9b5 | ||
|
|
af107ad5e8 | ||
|
|
8926d62165 | ||
|
|
45344ee347 | ||
|
|
baf9ebab15 | ||
|
|
b7dab817f2 | ||
|
|
fb2fa54480 | ||
|
|
2c966a1234 | ||
|
|
143ea69c0d | ||
|
|
8a02ead013 | ||
|
|
930d5ab941 | ||
|
|
e54a56d3a8 | ||
|
|
f5216c0d85 | ||
|
|
ebc77540b9 | ||
|
|
28d2583c10 | ||
|
|
b0988a7b00 | ||
|
|
2c4920db2d | ||
|
|
8321663815 | ||
|
|
ac0280d460 | ||
|
|
ba6f4268f0 | ||
|
|
0bd18f94ac | ||
|
|
66fb63661f | ||
|
|
d9b16beb0a | ||
|
|
6a01a9bdfd | ||
|
|
b81b34a706 | ||
|
|
3750a00b5f | ||
|
|
05ddcc169d | ||
|
|
f571a5f1bd | ||
|
|
48b786adea | ||
|
|
d691fa9b4d | ||
|
|
da50f9e419 | ||
|
|
103de5e18a | ||
|
|
dac6efd98b | ||
|
|
46aa7f81b2 | ||
|
|
0683c7cd67 | ||
|
|
50d7aa7b6a | ||
|
|
b781215d0a | ||
|
|
866bc2f3bd | ||
|
|
49d4705014 | ||
|
|
9163fcfccb | ||
|
|
7e10641461 | ||
|
|
cdc0e3cfd8 | ||
|
|
466e81d56a | ||
|
|
5953f691d1 | ||
|
|
e2790ca6c1 | ||
|
|
66dbd48b76 | ||
|
|
73cfbbd2ba | ||
|
|
38bc38bf26 | ||
|
|
e12d13c838 | ||
|
|
fcc3af6136 | ||
|
|
491298e1cb | ||
|
|
72b5895217 | ||
|
|
0a8f4017bd | ||
|
|
cb985f5897 | ||
|
|
968ec0853f | ||
|
|
0bde72d3df | ||
|
|
45293fbd42 | ||
|
|
72997065f0 | ||
|
|
a838dc163d | ||
|
|
3d15a4ca6d | ||
|
|
51c7d4fb1b | ||
|
|
fa586dba7e | ||
|
|
8ad40389f2 | ||
|
|
b3333cc2d3 | ||
|
|
3699a7ba9a | ||
|
|
204e521ba4 | ||
|
|
c9bab3e5c3 | ||
|
|
3d00e20238 | ||
|
|
c3958ed3c4 | ||
|
|
e5b88be5fa | ||
|
|
425552988a | ||
|
|
a81dd8abe5 | ||
|
|
bac8154a5b | ||
|
|
737d15fa0e | ||
|
|
5f2317af7f | ||
|
|
2bd1f783e5 | ||
|
|
d6c0c9f963 | ||
|
|
21b6ad7a41 | ||
|
|
a65d609fdc | ||
|
|
04e676b936 | ||
|
|
be8eaaffdf | ||
|
|
28c753523f | ||
|
|
7fbd0b2ffc | ||
|
|
7ffb48a87a | ||
|
|
d485270e1f | ||
|
|
5fd688b266 | ||
|
|
3716668e0c | ||
|
|
ae7fd18c34 | ||
|
|
4f59b1d32f | ||
|
|
90cb3279df | ||
|
|
cf0c7ef6b2 | ||
|
|
47c23781d9 | ||
|
|
e258c050f7 | ||
|
|
57801b2f34 | ||
|
|
710e9c9423 | ||
|
|
deefef83bd | ||
|
|
51e30aed66 | ||
|
|
8d109a3cfe | ||
|
|
3424e019b5 | ||
|
|
c6b4bceb67 | ||
|
|
afb4155015 | ||
|
|
8d99baf38a | ||
|
|
b91cb60328 | ||
|
|
c0d62237fc | ||
|
|
223ea80860 | ||
|
|
5a77bef494 | ||
|
|
80c0efe821 | ||
|
|
8044d89557 | ||
|
|
4f0ed97410 | ||
|
|
af7952f204 | ||
|
|
d8dcae856b | ||
|
|
7296796ed9 | ||
|
|
a2c2bb4948 | ||
|
|
72ebfdc20e | ||
|
|
16b95ea78a | ||
|
|
c04f08dfd8 | ||
|
|
a30793e818 | ||
|
|
e39e1eaf21 | ||
|
|
ab22d2cbaa | ||
|
|
96ddbe7227 | ||
|
|
4d09235aef | ||
|
|
136b8975e3 | ||
|
|
e21b1eca17 | ||
|
|
244b90b1d4 | ||
|
|
b318f3f940 | ||
|
|
e211c9812e | ||
|
|
eef28d96f4 | ||
|
|
c8227e09ee | ||
|
|
3e05fd91d9 | ||
|
|
450baba56a | ||
|
|
17a8c4918c | ||
|
|
0e2419d61a | ||
|
|
79b1a2ca6d | ||
|
|
2213c68155 | ||
|
|
2492b1fa96 | ||
|
|
6c6598dac5 | ||
|
|
a137112e66 | ||
|
|
8642ae8180 | ||
|
|
894c4dc5a7 | ||
|
|
d96063ea6e | ||
|
|
c3dc193f3e | ||
|
|
3c2952009e | ||
|
|
f4ade470df | ||
|
|
2e33b43389 | ||
|
|
92799699bc | ||
|
|
7ab0508167 | ||
|
|
3c65c28936 | ||
|
|
43892da07e | ||
|
|
7c436920a4 | ||
|
|
89d565e63b | ||
|
|
150b6fe5b6 | ||
|
|
0e77574c26 | ||
|
|
df23863443 | ||
|
|
581bf11b21 | ||
|
|
d602d4b429 | ||
|
|
d1d4a52934 | ||
|
|
375d113769 | ||
|
|
9b83974bff | ||
|
|
3c68c99bd5 | ||
|
|
ec4b37c596 | ||
|
|
ba9601d21c | ||
|
|
50c13fd469 | ||
|
|
7af072b8fc | ||
|
|
faa128d41e | ||
|
|
868fe46932 | ||
|
|
e9e4307ce5 | ||
|
|
774d4844a9 | ||
|
|
586c53e670 | ||
|
|
68e073fbff | ||
|
|
8101dc37b1 | ||
|
|
63f16c458d | ||
|
|
821e007e95 | ||
|
|
1656a2f11a | ||
|
|
4dbc135dce | ||
|
|
fc886f6bc1 | ||
|
|
f93e480466 | ||
|
|
fe807e23f8 | ||
|
|
ecf61c31f1 | ||
|
|
4feff18af5 | ||
|
|
a07c52e0d8 | ||
|
|
7bb07d7f55 | ||
|
|
f12dfc8a14 | ||
|
|
be030f15c4 | ||
|
|
f5fb6c063b | ||
|
|
fb722f06b9 | ||
|
|
c0ea19e15e | ||
|
|
cdeb1ad87c | ||
|
|
0dbe4e94fa | ||
|
|
b5e2e8aa1d | ||
|
|
9502010248 | ||
|
|
fea0557b47 | ||
|
|
ed4fcc9011 | ||
|
|
ed12ea7cfb | ||
|
|
73e526645e | ||
|
|
72aeafb2b5 | ||
|
|
cc1af60cb4 | ||
|
|
359fab315f | ||
|
|
6a9574bab9 | ||
|
|
83d6158483 | ||
|
|
63ef89b6cc | ||
|
|
b0beab4cd3 | ||
|
|
a34782575f | ||
|
|
142bdc9430 | ||
|
|
14b79cb0a4 | ||
|
|
ce5beeaf2c | ||
|
|
31114a2ca5 | ||
|
|
32528094ad | ||
|
|
0a2a01c44c | ||
|
|
c1888dc3ac | ||
|
|
4d76afbe01 | ||
|
|
76d7a97f93 | ||
|
|
8b1366b20a | ||
|
|
e0f9685578 | ||
|
|
5235657954 | ||
|
|
a15fbc8094 | ||
|
|
546f1d9c50 | ||
|
|
74231f552a | ||
|
|
b250a10e3c | ||
|
|
a9f1b31dd6 | ||
|
|
7fe393acaf | ||
|
|
04faba4db5 | ||
|
|
91bba40c20 | ||
|
|
79e39f7de8 | ||
|
|
9c09353559 | ||
|
|
50752a5bfe | ||
|
|
d59879db7d | ||
|
|
aab125da27 | ||
|
|
74fc731f96 | ||
|
|
bd0050fec2 | ||
|
|
aa5e313b92 | ||
|
|
e89d613b7e | ||
|
|
8757929ead | ||
|
|
e0a9b19802 | ||
|
|
308da6dc6e | ||
|
|
b6960fb0e5 | ||
|
|
137208c3fd | ||
|
|
d7a9a62a1d | ||
|
|
075315bdaa | ||
|
|
3948fcd614 | ||
|
|
8e61e129ab | ||
|
|
20cffd0502 | ||
|
|
5df09dab09 | ||
|
|
7446b911e5 | ||
|
|
f15267c1ab | ||
|
|
9c9fc2b5dc | ||
|
|
28f601b54b | ||
|
|
18b8a05014 | ||
|
|
910c995ed8 | ||
|
|
498468aa2c | ||
|
|
637aebcb34 | ||
|
|
9afd5cb277 | ||
|
|
bc525e7272 | ||
|
|
a80180780d | ||
|
|
0d73086c37 | ||
|
|
02ae39238d | ||
|
|
43d6b51d42 | ||
|
|
84566310de | ||
|
|
6a6ec9fbe4 | ||
|
|
84a7f825d7 | ||
|
|
0372c1aaf1 | ||
|
|
c9fff197f7 | ||
|
|
6900392e43 | ||
|
|
c00bcd78cc | ||
|
|
b2b82124e6 | ||
|
|
3de57c668f | ||
|
|
43669648ce | ||
|
|
3b73b416d5 | ||
|
|
5153591c8f | ||
|
|
3172bc90da | ||
|
|
76a1b2cd51 | ||
|
|
bdf7eee72f | ||
|
|
2d4b148b2c | ||
|
|
d67db74ca2 | ||
|
|
516725456f | ||
|
|
001d72a484 | ||
|
|
c555e28988 | ||
|
|
af13d1943f | ||
|
|
52df2edc8f | ||
|
|
cd08484a13 | ||
|
|
f38d38f139 | ||
|
|
93b6c68938 | ||
|
|
a4cc25175a | ||
|
|
3fb14b4708 | ||
|
|
6bdb6db330 | ||
|
|
d05c165ace | ||
|
|
ab53cdb896 | ||
|
|
c1f142af78 | ||
|
|
6e261abb73 | ||
|
|
39af9e4414 | ||
|
|
5b50abb2c7 | ||
|
|
13bda0a264 | ||
|
|
1658c666ab | ||
|
|
170aebfe54 | ||
|
|
c4ef379d0e | ||
|
|
18b038d8ff | ||
|
|
12ee5da872 | ||
|
|
601f9f86bb | ||
|
|
d7329a5915 | ||
|
|
9e7b730002 | ||
|
|
2d59d845bc | ||
|
|
c2645894e0 | ||
|
|
3751106317 | ||
|
|
60bb639351 | ||
|
|
74c50930bd | ||
|
|
9105104303 | ||
|
|
540dde135e | ||
|
|
f8936210cf | ||
|
|
1dc6d8de40 | ||
|
|
1069db3c13 | ||
|
|
65122f0144 | ||
|
|
d2c018f7da | ||
|
|
46493c2af6 | ||
|
|
c303e03f76 | ||
|
|
d8b65f62e7 | ||
|
|
854368a8f3 | ||
|
|
7653a34aea | ||
|
|
ee50b58e00 | ||
|
|
1eb60ab100 | ||
|
|
4a20eef351 | ||
|
|
26c9b2c353 | ||
|
|
16374bce9b | ||
|
|
86011d4ea2 | ||
|
|
fcb8b02da9 | ||
|
|
571165c2bb | ||
|
|
c842113610 | ||
|
|
974a8b3b70 | ||
|
|
2e20c99ada | ||
|
|
aa88ff6f2c | ||
|
|
5e6aa63d03 | ||
|
|
ad6700c114 | ||
|
|
f08c6efb00 | ||
|
|
cc807ec132 | ||
|
|
24e7c68243 | ||
|
|
ab25edd37a | ||
|
|
be47fde6c2 | ||
|
|
1ffa8c5e72 | ||
|
|
d855ccb8a7 | ||
|
|
d88919474b | ||
|
|
e139664301 | ||
|
|
5adf5f6e3f | ||
|
|
aef2075c8e | ||
|
|
922b2962a3 | ||
|
|
b4a401700e | ||
|
|
e9601bb9c1 | ||
|
|
58af3dc6ea | ||
|
|
074295df61 | ||
|
|
ec349b31c7 | ||
|
|
5ae236e016 | ||
|
|
d7c5897aba | ||
|
|
5252e7efe7 | ||
|
|
fc7d65629a | ||
|
|
f28fdf8252 | ||
|
|
5d07c4a949 | ||
|
|
fdd9eaab4b | ||
|
|
e0d863a46f | ||
|
|
3aacb6f5f3 | ||
|
|
428e331b3e | ||
|
|
847e05e9a7 | ||
|
|
087eb5dbe6 | ||
|
|
f15932b2ac | ||
|
|
d3a4c3795d | ||
|
|
b15d55e1d9 | ||
|
|
4f5889cc5b | ||
|
|
bf2a104a4e | ||
|
|
0c6dd5cd16 | ||
|
|
5efb06a7aa | ||
|
|
b13acef272 | ||
|
|
cfa67d6c0f | ||
|
|
e70444f19a | ||
|
|
0258982e60 | ||
|
|
70eed5cb5e | ||
|
|
a650fa51f7 | ||
|
|
cb205580d8 | ||
|
|
f9329aac00 | ||
|
|
745f4a7523 | ||
|
|
60254dafd7 | ||
|
|
a8d60388ba | ||
|
|
83ec60254c | ||
|
|
c15c45f765 | ||
|
|
cbe52b5089 | ||
|
|
e4e2921f3e | ||
|
|
4673170531 | ||
|
|
2c2ed26c38 | ||
|
|
94be5244fe | ||
|
|
f137a08493 | ||
|
|
48624d0a34 | ||
|
|
4cceb3ddaa | ||
|
|
f728395603 | ||
|
|
3e82d43807 | ||
|
|
2194c4e0a9 | ||
|
|
c581080f3f | ||
|
|
f6b1ec27e5 | ||
|
|
368b183230 | ||
|
|
9028ad36ad | ||
|
|
6cc041cd39 | ||
|
|
63ff01e78d | ||
|
|
9e5484937e | ||
|
|
b8ed489b14 | ||
|
|
765152d04b | ||
|
|
14934367d8 | ||
|
|
04164500c8 | ||
|
|
5160f2c298 | ||
|
|
124c9303b9 | ||
|
|
cd27f0ad69 | ||
|
|
a7555bcce3 | ||
|
|
6b5c4fd3f4 | ||
|
|
cc55e2acee | ||
|
|
1511f75a80 | ||
|
|
f01bbefc1f | ||
|
|
1d1eb5ffa8 | ||
|
|
a465cb2191 | ||
|
|
42d13e02ef | ||
|
|
d00786c43f | ||
|
|
4b47f99829 | ||
|
|
35aaf40003 | ||
|
|
cc5b4a1e02 | ||
|
|
7079521e8c | ||
|
|
b5025560a5 | ||
|
|
3f4bdd7f0e | ||
|
|
e94bb9b549 | ||
|
|
1ddaacbef5 | ||
|
|
e8b40518e0 | ||
|
|
0f88cbb41b | ||
|
|
780d137b76 | ||
|
|
ad8a9717d1 | ||
|
|
9d6ea6b2f6 | ||
|
|
7559383089 | ||
|
|
f84381c927 | ||
|
|
cb0122a43f | ||
|
|
6776b20989 | ||
|
|
e98d556022 | ||
|
|
5bf18b69d7 | ||
|
|
ea17f045a7 | ||
|
|
526f565ea7 | ||
|
|
4aff9d6e73 | ||
|
|
bf516d4d21 | ||
|
|
ae92e409d9 | ||
|
|
4d017dc8a9 | ||
|
|
707f4e2965 | ||
|
|
1c3bffdc50 | ||
|
|
e54ddcb8b0 | ||
|
|
ddefb0debc | ||
|
|
92d8dde90d | ||
|
|
1bb0508ddf | ||
|
|
a280a326b9 | ||
|
|
683e9b7c2c | ||
|
|
a44e5da421 | ||
|
|
8cd2c90ad7 | ||
|
|
5e57a390a2 | ||
|
|
620848272e | ||
|
|
1e86794416 | ||
|
|
e36717259b | ||
|
|
75b9238b90 | ||
|
|
ce5b20027e | ||
|
|
0de1242c83 | ||
|
|
8bd445ab19 | ||
|
|
fdef0de163 | ||
|
|
b1b03a4325 | ||
|
|
0587d96474 | ||
|
|
c2241567e4 | ||
|
|
7ac24ba418 | ||
|
|
c933ffec66 | ||
|
|
e587d934b1 | ||
|
|
f354e90656 | ||
|
|
1b0bc7ec6e | ||
|
|
ee1acb9c00 | ||
|
|
06862a2812 | ||
|
|
5fa87e18db | ||
|
|
77989e2720 | ||
|
|
3a1102fa4e | ||
|
|
8a9974ce53 | ||
|
|
4be8f1ca03 | ||
|
|
1ec2970ee3 | ||
|
|
81b3a22606 | ||
|
|
f81a475cc9 | ||
|
|
d7ee03d4f9 | ||
|
|
c1c06410c2 | ||
|
|
657d16bb60 | ||
|
|
e65a4c1010 | ||
|
|
e23d3f5661 | ||
|
|
e13611f7af | ||
|
|
596cd09489 | ||
|
|
0be5b27d34 | ||
|
|
a27471ae55 | ||
|
|
e27e3622a8 | ||
|
|
e95273b72b | ||
|
|
583d4f3249 | ||
|
|
d6967c4516 | ||
|
|
40b3097374 | ||
|
|
1a1f127993 | ||
|
|
a0f34a7ce1 | ||
|
|
db020db34b | ||
|
|
681167bc1b | ||
|
|
40e49ffc37 | ||
|
|
834b1afb38 | ||
|
|
62d5a1da87 | ||
|
|
8d8308e557 | ||
|
|
e1aa63487a | ||
|
|
b7fbe110d4 | ||
|
|
58859eb35a | ||
|
|
4b7e1ae1c6 | ||
|
|
3a06a6ac07 | ||
|
|
db0f269dc8 | ||
|
|
3cabe6ca5a | ||
|
|
d483005219 | ||
|
|
fea9bc4e7e | ||
|
|
d579992c98 | ||
|
|
ad1c61d959 | ||
|
|
bb1da31830 | ||
|
|
a50949e554 | ||
|
|
14dce8a10b | ||
|
|
1240c8f685 | ||
|
|
cc7c2e952c | ||
|
|
409ec2e086 | ||
|
|
a7f6848e53 | ||
|
|
4b0b79199d | ||
|
|
d1d6c48d9b | ||
|
|
21631780bb | ||
|
|
b935e32340 | ||
|
|
72dd064932 | ||
|
|
2e75446665 | ||
|
|
be17e4481e | ||
|
|
616c849b1f | ||
|
|
71947c097f | ||
|
|
546787802d | ||
|
|
294d0e388a | ||
|
|
193a1b0325 | ||
|
|
12743217a2 | ||
|
|
b252b9da66 | ||
|
|
cdef9c3c7e | ||
|
|
71dcebb744 | ||
|
|
25f248c60a | ||
|
|
d5cbc17831 | ||
|
|
7a10217511 | ||
|
|
7559efab77 | ||
|
|
8254efbd03 | ||
|
|
4ae24225a5 | ||
|
|
67d9154563 | ||
|
|
ad0319c188 | ||
|
|
eb650ea3ec | ||
|
|
7eba33e805 | ||
|
|
e1cb9d387e | ||
|
|
2ace7c3ca0 | ||
|
|
58014f0592 | ||
|
|
1d4938bb09 | ||
|
|
bbf4007c3e | ||
|
|
4d5124fb4c | ||
|
|
14a7cd05b1 | ||
|
|
946be80eef | ||
|
|
9ad8b1a980 | ||
|
|
f733216fcb | ||
|
|
ffc6139e21 | ||
|
|
571cac6644 | ||
|
|
2738ac5a5c | ||
|
|
7dfde51b84 | ||
|
|
2d2f18e538 | ||
|
|
3af0dd2e3b | ||
|
|
349e077802 | ||
|
|
812aae358f | ||
|
|
c3c59d0627 | ||
|
|
89518b412d | ||
|
|
f43b026162 | ||
|
|
b806c70f52 | ||
|
|
10bff3c0b8 | ||
|
|
65c12fd0b2 | ||
|
|
50f71c4130 | ||
|
|
8e401a53dc | ||
|
|
64a289a47c | ||
|
|
8f2c37061b | ||
|
|
39f2de6b90 | ||
|
|
855ba8d4f3 | ||
|
|
74f098e718 | ||
|
|
56c8a84691 | ||
|
|
8bbf319032 | ||
|
|
afbca4ae65 | ||
|
|
0ef6d2f91a | ||
|
|
fbe4435599 | ||
|
|
34be565dd1 | ||
|
|
af838e4ed1 | ||
|
|
60fe8ce011 | ||
|
|
8ece341467 | ||
|
|
dfa6bdbcb8 | ||
|
|
fb2481ebaa | ||
|
|
4874c116cf | ||
|
|
e19c44efbd | ||
|
|
4b687b9bdc | ||
|
|
6af79ef601 | ||
|
|
352b996ad2 | ||
|
|
4a93bb35f8 | ||
|
|
8e1f493daf | ||
|
|
59ee153375 | ||
|
|
60f7f1fc16 | ||
|
|
b7433683d8 | ||
|
|
42799b9273 | ||
|
|
860a0f790e | ||
|
|
8daccbfbb4 | ||
|
|
285b77dcb7 | ||
|
|
6e48827d3f | ||
|
|
f0c20cc706 | ||
|
|
8916c0a3de | ||
|
|
7193a77840 | ||
|
|
61930b5b51 | ||
|
|
11a494cacf | ||
|
|
17f9bf0339 | ||
|
|
3d9755ca8c | ||
|
|
b5cf2d03e6 | ||
|
|
e3b35b8f35 | ||
|
|
1c1fe672bd | ||
|
|
6c71f68ed8 | ||
|
|
8f2f912cdf | ||
|
|
bf6ea16acb | ||
|
|
288546c2b9 | ||
|
|
724db6c34c | ||
|
|
067c451c1d | ||
|
|
11e3696191 | ||
|
|
41e20664de | ||
|
|
d8de90d6f3 | ||
|
|
b01e8299d3 | ||
|
|
1ec11e3e2e | ||
|
|
422f429725 | ||
|
|
5c55fa5fbb | ||
|
|
80d845fdf2 | ||
|
|
601fe68346 | ||
|
|
9e050fb059 | ||
|
|
99d4adf5e6 | ||
|
|
85f8d1e8e9 | ||
|
|
8334d3d99f | ||
|
|
cff08d19eb | ||
|
|
2d86390bc1 | ||
|
|
7a20835571 | ||
|
|
ff3c9676b5 | ||
|
|
055f97dab1 | ||
|
|
8a867e71a1 | ||
|
|
b8275b4734 | ||
|
|
36b951b146 | ||
|
|
c5a5f17643 | ||
|
|
16b909c4df | ||
|
|
92b7648e03 | ||
|
|
ca46ebe3b2 | ||
|
|
676e48254a | ||
|
|
b15b55227d | ||
|
|
3c3b723913 | ||
|
|
f05002c729 | ||
|
|
1c2cbd5b40 | ||
|
|
54c6ca9f45 | ||
|
|
c10efbb170 | ||
|
|
a496ad5814 | ||
|
|
50cf7f6a3b | ||
|
|
f946f10afd | ||
|
|
eecb4db34c | ||
|
|
1f865d3ea4 | ||
|
|
623bb4b350 | ||
|
|
dc8ad673a6 | ||
|
|
4914ad821e | ||
|
|
f099cbadc3 | ||
|
|
42cda384c8 | ||
|
|
23c91b9990 | ||
|
|
ff0379182e | ||
|
|
e08a23948f | ||
|
|
bd56de6d36 | ||
|
|
42970aea80 | ||
|
|
003a05ee8d | ||
|
|
ffb11b01a6 | ||
|
|
e426f5d5da | ||
|
|
6989f61e1b | ||
|
|
0e6677ccb3 | ||
|
|
8f104d555a | ||
|
|
b1d3158db1 | ||
|
|
7645005d5a | ||
|
|
411f77fd29 | ||
|
|
568ab26db1 | ||
|
|
29652108f0 | ||
|
|
f07e4dc711 | ||
|
|
8a2ac457c2 | ||
|
|
9e54eecfaa | ||
|
|
95ef691077 | ||
|
|
7a0ad5a587 | ||
|
|
42b49d0e4b | ||
|
|
9217c2f003 | ||
|
|
fbdf66998d | ||
|
|
deda9d3c54 | ||
|
|
a5d78f20ae | ||
|
|
5ed09e3f38 | ||
|
|
3e9774cd66 | ||
|
|
54387c8fdf | ||
|
|
7eec949a13 | ||
|
|
4113c4ff40 | ||
|
|
1bf0968bfe | ||
|
|
374b90fb00 | ||
|
|
064e60e9d5 | ||
|
|
b637455970 | ||
|
|
68158937d1 | ||
|
|
adb1356b7a | ||
|
|
d880ccb8e0 | ||
|
|
050fb1d1ef | ||
|
|
6580752bde | ||
|
|
c9df265c9b | ||
|
|
098e5bc162 | ||
|
|
4b2dcc74d4 | ||
|
|
a9254c5c9a | ||
|
|
7ce57e6ccb | ||
|
|
0fcb32a66f | ||
|
|
9946535f01 | ||
|
|
2b17396d6b | ||
|
|
b01d5bc237 | ||
|
|
b123860304 | ||
|
|
033f5b67db | ||
|
|
6280448dfb | ||
|
|
01cd3333e4 | ||
|
|
63050907b9 | ||
|
|
beedf7d780 | ||
|
|
6b8194261f | ||
|
|
dbb1c4d534 | ||
|
|
e6263f9ff5 | ||
|
|
6ca119c4db | ||
|
|
c483a1ab3a | ||
|
|
2e7edd033c | ||
|
|
c576902501 | ||
|
|
66c2951594 | ||
|
|
b812881cdb | ||
|
|
cdeac2c6db | ||
|
|
bca2ddd529 | ||
|
|
e7285c6499 | ||
|
|
bdff275672 | ||
|
|
bec58a1ee6 | ||
|
|
f64616748c | ||
|
|
512ce15973 | ||
|
|
ed8b301574 | ||
|
|
d22a6c019c | ||
|
|
a0cb1b9d9e | ||
|
|
a5294c62ea | ||
|
|
e155d3311c | ||
|
|
0a372b0daf | ||
|
|
69143399d1 | ||
|
|
3270d3bf96 | ||
|
|
3896a66122 | ||
|
|
b94781aef1 | ||
|
|
bed1adc367 | ||
|
|
ae54497efa | ||
|
|
06b747c221 | ||
|
|
f159beee0d | ||
|
|
49d7dea086 | ||
|
|
3e65733dc5 | ||
|
|
cc375d58bb | ||
|
|
911c7c662a | ||
|
|
aae003be33 | ||
|
|
aede03d8b2 | ||
|
|
f0f5ada7de | ||
|
|
58365121a3 | ||
|
|
d5a154d2e6 | ||
|
|
b20f369aef | ||
|
|
abb8aa0b29 | ||
|
|
5368a0f1d7 | ||
|
|
d3897eece7 | ||
|
|
a82b829da9 | ||
|
|
9f5058e81a | ||
|
|
5b19263720 | ||
|
|
9d5a0db0d9 | ||
|
|
4bd8a7014f | ||
|
|
353e96d951 | ||
|
|
149a6f92b0 | ||
|
|
d66426c137 | ||
|
|
4fc9966392 | ||
|
|
417766f0db | ||
|
|
de9ac97887 | ||
|
|
6be42f112a | ||
|
|
3895ae63c7 | ||
|
|
607d416d54 | ||
|
|
038693dc86 | ||
|
|
cc9be13544 | ||
|
|
9c1474087f | ||
|
|
831de2bcf4 | ||
|
|
eb687333bb | ||
|
|
5cc7966d54 | ||
|
|
d1a34e7a6f | ||
|
|
d63d791717 | ||
|
|
015570c741 | ||
|
|
8400ebc9c6 | ||
|
|
9f99e7c0af | ||
|
|
392c1fc399 | ||
|
|
d3cea7a89c | ||
|
|
1dcf7407e6 | ||
|
|
d543c033a3 | ||
|
|
aaa186be5e | ||
|
|
2054b5b3dd | ||
|
|
98ae5b0ca0 | ||
|
|
36c8171d0f | ||
|
|
3603eb94cc | ||
|
|
0e068d4ccf | ||
|
|
199f348ff4 | ||
|
|
b22655fb7c | ||
|
|
06cc9618ba | ||
|
|
b9019c8c7f | ||
|
|
77e133e67c | ||
|
|
571e7df807 | ||
|
|
22f4d2979a | ||
|
|
e46e366694 | ||
|
|
7b4bc23815 | ||
|
|
9ca79f767c | ||
|
|
274dba7408 | ||
|
|
31708ca29e | ||
|
|
671b025588 | ||
|
|
a7956e4856 | ||
|
|
355862025a | ||
|
|
a2a39ee0f8 | ||
|
|
ec8e39c16f | ||
|
|
88f714999e | ||
|
|
c0c2aa3be0 | ||
|
|
822044820e | ||
|
|
6ffc182142 | ||
|
|
3d54a78573 | ||
|
|
8ddf7d953a | ||
|
|
8b9e9ad103 | ||
|
|
5737224c40 | ||
|
|
ec9aacbcae | ||
|
|
9395454997 | ||
|
|
66198a8d98 | ||
|
|
96ed9a4256 | ||
|
|
10e54b2263 | ||
|
|
cf00922ad2 | ||
|
|
84e8e007a5 | ||
|
|
d07b2e773b | ||
|
|
506ef7b0b9 | ||
|
|
2cd5dae8e2 | ||
|
|
a1cd49c111 | ||
|
|
aca2973aef | ||
|
|
0a7a691c95 | ||
|
|
72906a7afd | ||
|
|
d1a4a83570 | ||
|
|
e0396b29e8 | ||
|
|
536833cfe0 | ||
|
|
317b02d1b9 | ||
|
|
75e279ea0d | ||
|
|
dc2ad21f4c | ||
|
|
484d49aae1 | ||
|
|
ca39438ad4 | ||
|
|
49a65ebff4 | ||
|
|
befdc05084 | ||
|
|
1fbffe761b | ||
|
|
36aad379ff | ||
|
|
540cfa072e | ||
|
|
3b049c15cc | ||
|
|
3e93ed0a17 | ||
|
|
d7d9358136 | ||
|
|
5cf0939ff9 | ||
|
|
8dc6f91d3c | ||
|
|
a3a25db230 | ||
|
|
c06f18c815 | ||
|
|
6802f04036 | ||
|
|
beeccdf345 | ||
|
|
58241ed39d | ||
|
|
31128020f0 | ||
|
|
6c48afc37b | ||
|
|
7a2f169dfc | ||
|
|
ed910b99a7 | ||
|
|
54195c0826 | ||
|
|
cefbbcd1df | ||
|
|
cc01592085 | ||
|
|
5a98a5252d | ||
|
|
184e8b1132 | ||
|
|
2b6b896c2e | ||
|
|
96d06b7a93 | ||
|
|
f54f1611b5 | ||
|
|
69ad757e8b | ||
|
|
e0beb796ad | ||
|
|
f331e7d820 | ||
|
|
cbb62d3d78 | ||
|
|
c85bc59c1d | ||
|
|
8081eeb007 | ||
|
|
56f91bd10d | ||
|
|
8e20b78731 | ||
|
|
23a09b7081 | ||
|
|
67fdd27499 | ||
|
|
e1941daedd | ||
|
|
f28bc568a4 | ||
|
|
f24cfe39aa | ||
|
|
59d2bf3f79 | ||
|
|
3176e54614 | ||
|
|
eb090f7265 | ||
|
|
6d3a9bfd18 | ||
|
|
76f08b7acb | ||
|
|
1ff99346aa | ||
|
|
369695ab32 | ||
|
|
7e23dd1d66 | ||
|
|
0205d3fc5c | ||
|
|
4660cf2ad5 | ||
|
|
e26d08d674 | ||
|
|
0932bf2797 | ||
|
|
f560fc6d76 | ||
|
|
aa6209af00 | ||
|
|
4a51176193 | ||
|
|
bb84f7a434 | ||
|
|
48168b1ef0 | ||
|
|
8281c7c83e | ||
|
|
a07c1e3c71 | ||
|
|
0766bb31fe | ||
|
|
ff4472c1a5 | ||
|
|
17424740e5 | ||
|
|
dad0b2fcd3 | ||
|
|
c48dbf030f | ||
|
|
617808d603 | ||
|
|
845149deee | ||
|
|
1a9e009327 | ||
|
|
ae90815708 | ||
|
|
1c460343b7 | ||
|
|
26079622f9 | ||
|
|
06c6d6096f | ||
|
|
c8183aea51 | ||
|
|
7531134ad2 | ||
|
|
cd0c6439c2 | ||
|
|
c918c93f51 | ||
|
|
97d3bd68ed | ||
|
|
75f1e034ae | ||
|
|
8e72f218f1 | ||
|
|
d643e05c5a | ||
|
|
fea44834d0 | ||
|
|
195a2d7523 | ||
|
|
b2fd346ef8 | ||
|
|
1ff8b62cb7 | ||
|
|
c7ae15a41a | ||
|
|
efe6f59f79 | ||
|
|
8e31d0491d | ||
|
|
73d4a10351 | ||
|
|
81d9d4dbc7 | ||
|
|
184ec13f99 | ||
|
|
dee0422eff | ||
|
|
81a2975f1a | ||
|
|
5cd2ef4a5e | ||
|
|
466c2a68c2 | ||
|
|
0fb9fba531 | ||
|
|
356dd2c6cd | ||
|
|
32c4661233 | ||
|
|
a245b504ec | ||
|
|
a2f5fbdfd3 | ||
|
|
04be26adc5 | ||
|
|
be0adb7cf2 | ||
|
|
84fd92bf5a | ||
|
|
ee4e061739 | ||
|
|
4a2b01cd9a | ||
|
|
efdd6460fb | ||
|
|
803827e05c | ||
|
|
a41d5e9ab3 | ||
|
|
e94333f877 | ||
|
|
6dbb80d687 | ||
|
|
31a1031624 | ||
|
|
f8e9ce0d52 | ||
|
|
11baa968cd | ||
|
|
3e5e5b376f | ||
|
|
bda18f296d | ||
|
|
287d110c84 | ||
|
|
c98275e73a | ||
|
|
aa573cc951 | ||
|
|
883d3d86e3 | ||
|
|
fb047bd5f4 | ||
|
|
66a04d8365 | ||
|
|
a0e501f9fd | ||
|
|
8b40d3346d | ||
|
|
eddf5cd250 | ||
|
|
ee5c534ca3 | ||
|
|
fd4e77ae0f | ||
|
|
b747c50aa3 | ||
|
|
deebdd86a6 | ||
|
|
b2db79cc10 | ||
|
|
bbab370b1e | ||
|
|
db4adf399d | ||
|
|
0683c87e52 | ||
|
|
3f62b647fc | ||
|
|
2562a5b30d | ||
|
|
e232f2e223 | ||
|
|
21644ff4dd | ||
|
|
be96a4fce5 | ||
|
|
a1cabcbed3 | ||
|
|
ce933b1f06 | ||
|
|
51ae130922 | ||
|
|
da1bc18a47 | ||
|
|
e7165a526b | ||
|
|
6081cc399f | ||
|
|
7c5c24e15d | ||
|
|
e3e55b4347 | ||
|
|
40d8e7d1ad | ||
|
|
59e23b89f2 | ||
|
|
cc33af8193 | ||
|
|
0e1e7eb2a9 | ||
|
|
52a28a7758 | ||
|
|
3f752d6832 | ||
|
|
fe714e9989 | ||
|
|
0d3213a379 | ||
|
|
44056629e8 | ||
|
|
54125c05d3 | ||
|
|
7d32e50f25 | ||
|
|
4af9ff49a0 | ||
|
|
23faef845c | ||
|
|
41dad4091a | ||
|
|
daf859b977 | ||
|
|
f47ba6c977 | ||
|
|
4b9b207d92 | ||
|
|
875cbf66af | ||
|
|
412d6d4fd7 | ||
|
|
91c6deeb1f | ||
|
|
6d1978fd9a | ||
|
|
52d23b6ef5 | ||
|
|
0656ccbdd9 | ||
|
|
e774ebd0a3 | ||
|
|
edbda32a84 | ||
|
|
721fa04e3c | ||
|
|
9fd8d8915d | ||
|
|
7a9f5ebdd1 | ||
|
|
a3423f0321 | ||
|
|
c37e9a4467 | ||
|
|
6157d82a0b | ||
|
|
38b27fec92 | ||
|
|
c43439bb68 | ||
|
|
8d9561d7a5 | ||
|
|
2a72cce3b7 | ||
|
|
d3bbb6fb1f | ||
|
|
7b6723e9a2 | ||
|
|
1ee741460d | ||
|
|
967b0b493b | ||
|
|
b53aaf7dde | ||
|
|
714c13bdbf | ||
|
|
651a4fd3cc | ||
|
|
505aee22bb | ||
|
|
ca3d59dc33 | ||
|
|
13890e32a1 | ||
|
|
df8976eabe | ||
|
|
3e01daa172 | ||
|
|
f6999f355b | ||
|
|
5c06b45eb1 | ||
|
|
1802bb967a | ||
|
|
d8d92f147f | ||
|
|
4a95bdd8ba | ||
|
|
db5d94d956 | ||
|
|
06f2e34bb5 | ||
|
|
2ef7a01945 | ||
|
|
d3f642551d | ||
|
|
490997157e | ||
|
|
2432af7883 | ||
|
|
7abeece3f0 | ||
|
|
3851652821 | ||
|
|
1d67d2250a | ||
|
|
37f40d8637 | ||
|
|
0224d1d59b | ||
|
|
587b94153d | ||
|
|
5124ce0302 | ||
|
|
950c4045b0 | ||
|
|
9c8ba66873 | ||
|
|
25d1d1be1b | ||
|
|
339c352505 | ||
|
|
c587081fe4 | ||
|
|
8f9b1b866b | ||
|
|
ff5ecf6182 | ||
|
|
05670cf393 | ||
|
|
f10f5d30bf | ||
|
|
0d336727e8 | ||
|
|
bf354275b3 | ||
|
|
1932bf277a | ||
|
|
192db4bb6e | ||
|
|
701efb943d | ||
|
|
b2674971f1 | ||
|
|
c907cb4cf1 | ||
|
|
bb1462b4d9 | ||
|
|
1035f0e139 | ||
|
|
8475757716 | ||
|
|
20ffd8d61b | ||
|
|
8601428c4c | ||
|
|
59998573ee | ||
|
|
293cd72001 | ||
|
|
22ab9ebb2f | ||
|
|
9136e592d3 | ||
|
|
ed8e392616 | ||
|
|
e57ce6e644 | ||
|
|
8204e46086 | ||
|
|
9e365d9f80 | ||
|
|
6b40a933e9 | ||
|
|
8520a5002f | ||
|
|
b38ed06f6e | ||
|
|
48427b1143 | ||
|
|
5d505f4ed0 | ||
|
|
0c02a08954 | ||
|
|
0221bd0f80 | ||
|
|
28216cbcb5 | ||
|
|
3e08a8cd6b | ||
|
|
67fafdeef7 | ||
|
|
cc82505b66 | ||
|
|
fd5f075f63 | ||
|
|
a115960411 | ||
|
|
e9fa4ca816 | ||
|
|
75d92bc7f0 | ||
|
|
74aa6e911e | ||
|
|
9d000c1898 | ||
|
|
b2254875b2 | ||
|
|
78d04230d5 | ||
|
|
1c04477393 | ||
|
|
2a9679c94e | ||
|
|
6009f8ecde | ||
|
|
35cdbec70a | ||
|
|
0faef542c1 | ||
|
|
8b085af6a1 | ||
|
|
e3b11a9eb2 | ||
|
|
72fc88f3c6 | ||
|
|
a5b759f268 | ||
|
|
6a9ffae25d | ||
|
|
64c031c7fe | ||
|
|
720926c50d | ||
|
|
36ec974284 | ||
|
|
cff77c39e2 | ||
|
|
056a0a1736 | ||
|
|
f301da83c6 | ||
|
|
5362231efa | ||
|
|
4bb14cad73 | ||
|
|
7f5188f5a4 | ||
|
|
a80b7aac6c | ||
|
|
83f32478fa | ||
|
|
7578ec6801 | ||
|
|
e14a32f76f | ||
|
|
58faa189ac | ||
|
|
9b4f87d44a | ||
|
|
7830a64170 | ||
|
|
8f0fa02107 | ||
|
|
e000fb5d80 | ||
|
|
566fadad15 | ||
|
|
cf23858c10 | ||
|
|
216a223617 | ||
|
|
638f38d823 | ||
|
|
98ee70f04e | ||
|
|
b365db5a4b | ||
|
|
02e55496df | ||
|
|
e11e53913a | ||
|
|
f8a5fb4225 | ||
|
|
3a49450461 | ||
|
|
3c375fb955 | ||
|
|
011fea2cc6 | ||
|
|
a079eec2cb | ||
|
|
776b37f4ea | ||
|
|
a4d8bbe3da | ||
|
|
72166743fa | ||
|
|
c4312c0b11 | ||
|
|
38ba645415 | ||
|
|
ac12a47071 | ||
|
|
bea321939e | ||
|
|
210f84b6ea | ||
|
|
d4c642741f | ||
|
|
e6b1b58379 | ||
|
|
8d982c1a90 | ||
|
|
be10e836dc | ||
|
|
9be05e7e17 | ||
|
|
bd96b2819f | ||
|
|
f601af3da0 | ||
|
|
70a01c082b | ||
|
|
bcf066ead7 | ||
|
|
24dd3578ed | ||
|
|
d5c39d54d8 | ||
|
|
6e3f6abc67 | ||
|
|
463201df2c | ||
|
|
a7501c396f | ||
|
|
4c8bc49a7e | ||
|
|
f254674f88 | ||
|
|
a97c267378 | ||
|
|
eaa947894b | ||
|
|
fb3a26510b | ||
|
|
bfe8b14e49 | ||
|
|
afb47eb742 | ||
|
|
ba97f96288 | ||
|
|
ecbafb8f4b | ||
|
|
12d3800d67 | ||
|
|
d586b82372 | ||
|
|
ba9877c9b4 | ||
|
|
907933f3df | ||
|
|
2e91d21a88 | ||
|
|
dca395a018 | ||
|
|
625070a5b9 | ||
|
|
f875c7cbf5 | ||
|
|
eed88fc103 | ||
|
|
6fc54d1d27 | ||
|
|
0ef52bf909 | ||
|
|
95e34af6ef | ||
|
|
650b99ead2 | ||
|
|
57941a19de | ||
|
|
6157db0b6a | ||
|
|
c9358acf5d | ||
|
|
ed29eb8a5d | ||
|
|
bcf7452312 | ||
|
|
b9000519e4 | ||
|
|
2694297466 | ||
|
|
53ff5c1490 | ||
|
|
32a036b076 | ||
|
|
aae2ab2693 | ||
|
|
4ef23e28a3 | ||
|
|
38beb7f8d2 | ||
|
|
0129a66906 | ||
|
|
45bced7b34 | ||
|
|
7f5a55dffb | ||
|
|
80289df6d3 | ||
|
|
2115c3ced8 | ||
|
|
1c2d0e6618 | ||
|
|
ea4f7f79ce | ||
|
|
b17f8ac311 | ||
|
|
6870d13b72 | ||
|
|
81fa304fd2 | ||
|
|
a663ddb0d9 | ||
|
|
f7969afc22 | ||
|
|
52ce4cd313 | ||
|
|
247175881f | ||
|
|
fcf5009338 | ||
|
|
12542d8f63 | ||
|
|
e94194e28b | ||
|
|
115499f3b2 | ||
|
|
12af05b94f | ||
|
|
88b83d8164 | ||
|
|
fcc22c692a | ||
|
|
a876e8005c | ||
|
|
2288e3705a | ||
|
|
e8b58154e0 | ||
|
|
4393475af3 | ||
|
|
3fc560597c | ||
|
|
9f9af0b693 | ||
|
|
a83fe2caea | ||
|
|
30c5376217 | ||
|
|
6dddd6629d | ||
|
|
f80de12cb5 | ||
|
|
544ffca3a5 | ||
|
|
e0730c7b39 | ||
|
|
3d59d141c4 | ||
|
|
ccddc2623d | ||
|
|
fcc47b58b4 | ||
|
|
f67ac2e25e | ||
|
|
26127c9ccf | ||
|
|
d624efa799 | ||
|
|
ed3d40a4e0 | ||
|
|
6e90ce5496 | ||
|
|
ab851b1be4 | ||
|
|
340de53825 | ||
|
|
a867b60af0 | ||
|
|
26eafb0bd2 | ||
|
|
0dbe44764b | ||
|
|
2159df6802 | ||
|
|
23389b9f17 | ||
|
|
6d3e3b894a | ||
|
|
55340aefa3 | ||
|
|
94bc751e41 | ||
|
|
89259c11e2 | ||
|
|
6bbaf1523c | ||
|
|
fa3f18b60f | ||
|
|
3dc794002f | ||
|
|
f50b133f2e | ||
|
|
1d6f6d28c9 | ||
|
|
c913de3c8b | ||
|
|
bbc7b54a38 | ||
|
|
05fa1b40d1 | ||
|
|
6a88c8634d | ||
|
|
8ae1efa230 | ||
|
|
ab8df4c8ab | ||
|
|
c259a46ed3 | ||
|
|
5dc0677599 | ||
|
|
7ed662ecc2 | ||
|
|
4361cc69d4 | ||
|
|
461cbcbc28 | ||
|
|
09ae6c488b | ||
|
|
271a0ade26 | ||
|
|
4b076d227a | ||
|
|
3c4e7158a1 | ||
|
|
0b16765f37 | ||
|
|
20c2ff3443 | ||
|
|
96f2e598f4 | ||
|
|
7374f0f9dd | ||
|
|
ccc44a74a0 | ||
|
|
66cd63a68d | ||
|
|
decf7e5485 | ||
|
|
063c8025aa | ||
|
|
2fa7d2bd56 | ||
|
|
e55e7bce74 | ||
|
|
f572445a65 | ||
|
|
8b4f656d90 | ||
|
|
6c1e093ebd | ||
|
|
6dd1b4537a | ||
|
|
39ca9bea72 | ||
|
|
999103277e | ||
|
|
3cc57ab89c | ||
|
|
4e830079de | ||
|
|
ea7231e9fe | ||
|
|
ddfaca6d0c | ||
|
|
3b11bd0593 | ||
|
|
3f89057528 | ||
|
|
2f1073712f | ||
|
|
678e504c48 | ||
|
|
e69545fd95 | ||
|
|
a7ed025cd6 | ||
|
|
3d8e1f2484 | ||
|
|
c38c9608da | ||
|
|
2396b75e3c | ||
|
|
e5c19b7562 | ||
|
|
114788567d | ||
|
|
17ae4b7d2a | ||
|
|
12d652bd0e | ||
|
|
ff1bed97b8 | ||
|
|
56490f0e84 | ||
|
|
0b028a8923 | ||
|
|
43ac541cb8 | ||
|
|
5218443678 | ||
|
|
d9028ed9b7 | ||
|
|
9b9bd11ebb | ||
|
|
07011ec5f7 | ||
|
|
c3990ac32f | ||
|
|
081ff3ff55 | ||
|
|
93996ada96 | ||
|
|
a60671d4c1 | ||
|
|
3f716cc369 | ||
|
|
9f23cffd1b | ||
|
|
07c416cfbf | ||
|
|
389fcc1c8d | ||
|
|
fa07811375 | ||
|
|
fc91a50979 | ||
|
|
f9c518f321 | ||
|
|
015b439f0d | ||
|
|
0675622508 | ||
|
|
e9969bdd5f | ||
|
|
1139f950ed | ||
|
|
284732c7a6 | ||
|
|
0cb0720d8b | ||
|
|
8ad1ede0c5 | ||
|
|
a349aac8a4 | ||
|
|
83b169c6ef | ||
|
|
49c37baac5 | ||
|
|
8d62088576 | ||
|
|
f5437a17f8 | ||
|
|
92af4e5c96 | ||
|
|
0619adb0cd | ||
|
|
65a5107854 | ||
|
|
0fcc1e252b | ||
|
|
4b7f817475 | ||
|
|
0439599971 | ||
|
|
fa344d5308 | ||
|
|
f0316f09ed | ||
|
|
b1af6bab28 | ||
|
|
ad2aebb54d | ||
|
|
23fc652092 | ||
|
|
f0a5756f25 | ||
|
|
14c7ad201a | ||
|
|
08ca3c89d3 | ||
|
|
edda470bf8 | ||
|
|
7183d72e5c | ||
|
|
5e38ebfce5 | ||
|
|
56e36847a5 | ||
|
|
6dba916d02 | ||
|
|
fd57086ffd | ||
|
|
d98d366eea | ||
|
|
b261b0b447 | ||
|
|
c6042a9053 | ||
|
|
ae1245abec | ||
|
|
e26d2376fc | ||
|
|
bb36ad64a7 | ||
|
|
262d4f92d4 | ||
|
|
675530458c | ||
|
|
19e34b460f | ||
|
|
dddb8cdbc0 | ||
|
|
0d0df5f143 | ||
|
|
d1cf683f57 | ||
|
|
d099c33e5b | ||
|
|
21fb41545b | ||
|
|
4c56814785 | ||
|
|
cffe05e22b | ||
|
|
4237850299 | ||
|
|
b5fc36a8e1 | ||
|
|
4586c1ef52 | ||
|
|
2722f0b749 | ||
|
|
d6cf7c4872 | ||
|
|
3aedfed432 | ||
|
|
edeaf13259 | ||
|
|
c1290c4e9b | ||
|
|
b2c1527b17 | ||
|
|
a4a65ea56e | ||
|
|
ccf0e1875e | ||
|
|
9c009aceaf | ||
|
|
da4b1c7276 | ||
|
|
add098d5c0 | ||
|
|
aca096548c | ||
|
|
75aa3abcae | ||
|
|
e685d262cc | ||
|
|
d2599d6ef9 | ||
|
|
cd5223d98d | ||
|
|
7b68d9047d | ||
|
|
9a7ea06d66 | ||
|
|
c69ef34ac9 | ||
|
|
021999d05f | ||
|
|
6054430a5e | ||
|
|
8f578ed95a | ||
|
|
fc5c339e27 | ||
|
|
defad3d820 | ||
|
|
c0f96aa948 | ||
|
|
f2eae2fc98 | ||
|
|
0e4f786978 | ||
|
|
d36b2318fd | ||
|
|
d83b508bbc | ||
|
|
5b9c2cdc13 | ||
|
|
49066c282a | ||
|
|
3e28c0c00a | ||
|
|
220d689f69 | ||
|
|
5a0a28a04c | ||
|
|
b44e69e09b | ||
|
|
fcbe10f5ec | ||
|
|
182fe170fd | ||
|
|
184e9bdaf6 | ||
|
|
0096f50cde | ||
|
|
460f14deca | ||
|
|
910ad45bee | ||
|
|
74e319855d | ||
|
|
9af0fb4cd5 | ||
|
|
185f5cce29 | ||
|
|
aca1174566 | ||
|
|
e791f7fde2 | ||
|
|
7bba7fcf66 | ||
|
|
1d78f98ec8 | ||
|
|
7ed2e03654 | ||
|
|
549e0f3477 | ||
|
|
7785ec0222 | ||
|
|
ca504965f9 | ||
|
|
3841259779 | ||
|
|
169d1065cc | ||
|
|
e864f5507a | ||
|
|
434b4ded4a | ||
|
|
3d01669cea | ||
|
|
1499b2cd40 | ||
|
|
6b54ef8398 | ||
|
|
07ad43f7a2 | ||
|
|
72e72c60c2 | ||
|
|
f9a242d33e | ||
|
|
b0f43eaa07 | ||
|
|
b02046b884 | ||
|
|
864c931ee9 | ||
|
|
5cd8da6d91 | ||
|
|
4a4671c2ae | ||
|
|
8de142cd9a | ||
|
|
1a42bec51c | ||
|
|
577e38759e | ||
|
|
9cdf43a2c9 | ||
|
|
62d43c2cb2 | ||
|
|
2dc67d1674 | ||
|
|
fb1c78c657 | ||
|
|
37e58ac13a | ||
|
|
de715c14be | ||
|
|
c4d8a0da05 | ||
|
|
1b54b14671 | ||
|
|
a92efbc55f | ||
|
|
1bd02529e0 | ||
|
|
bc16298b6e | ||
|
|
804a97cad7 | ||
|
|
ab52bdec15 | ||
|
|
ddc3e82c14 | ||
|
|
57691471bb | ||
|
|
c502dd445b | ||
|
|
ed475b1b9c | ||
|
|
df165a817c | ||
|
|
d16015d625 | ||
|
|
a4b3bf3ef4 | ||
|
|
6b006853e6 | ||
|
|
6001180e29 | ||
|
|
662fbed1d0 | ||
|
|
19c7e08c5d | ||
|
|
72f04aaedc | ||
|
|
2998382969 | ||
|
|
37fe79944f | ||
|
|
536735519a | ||
|
|
6b0a711395 | ||
|
|
13d3d40376 | ||
|
|
6873336aca | ||
|
|
c2d2eb53e8 | ||
|
|
210d597a48 | ||
|
|
e41ede0a6b | ||
|
|
4a8b17ac7c | ||
|
|
1f5f515d72 | ||
|
|
1e6242b89f | ||
|
|
dde09f9f89 | ||
|
|
916d85c3fe | ||
|
|
b243ed93aa | ||
|
|
70d28bbf6e | ||
|
|
3c76da7132 | ||
|
|
3254565c09 | ||
|
|
cd0033791f | ||
|
|
2427ee44a5 | ||
|
|
ff0e617b2a | ||
|
|
e6cfe040b5 | ||
|
|
94e2f9b6dc | ||
|
|
512b81ad93 | ||
|
|
fc0e76f431 | ||
|
|
9da69358e2 | ||
|
|
aa246b0b2b | ||
|
|
6ed649bc8a | ||
|
|
725f5b7110 | ||
|
|
5a890c5c3a | ||
|
|
7752329b94 | ||
|
|
5f48f13890 | ||
|
|
a734e04561 | ||
|
|
5aa1db293f | ||
|
|
f89aee37f5 | ||
|
|
538018fed1 | ||
|
|
a2327c50ec | ||
|
|
c2711023e2 | ||
|
|
cac30f0b4c | ||
|
|
4bb17019a4 | ||
|
|
ba2a40bdf3 | ||
|
|
f3460cca49 | ||
|
|
64ce53ac30 | ||
|
|
a43238360c | ||
|
|
b2cbadf5d8 | ||
|
|
81640ba06d | ||
|
|
9327430484 | ||
|
|
a24c90eae8 | ||
|
|
1d3987ece6 | ||
|
|
83f5b5e293 | ||
|
|
f231263085 | ||
|
|
4ad67a87f1 | ||
|
|
b766d93d9a | ||
|
|
0905ceb1d5 | ||
|
|
2fbf837354 | ||
|
|
4bd79c880c | ||
|
|
4af041e015 | ||
|
|
8dc3e3ec93 | ||
|
|
f4b68d26d6 | ||
|
|
1887977b92 | ||
|
|
8eb84acf4f | ||
|
|
1b685da3e3 | ||
|
|
406b658801 | ||
|
|
bba1ee1264 | ||
|
|
02b6191d47 | ||
|
|
c5a3de09cd | ||
|
|
0afe2a680e | ||
|
|
03e0510c4f | ||
|
|
1068dcb8a4 | ||
|
|
10a93df653 | ||
|
|
79ff67852f | ||
|
|
a36cab969f | ||
|
|
45447646fa | ||
|
|
8a0f76ab68 | ||
|
|
037135e764 | ||
|
|
21e89c3b64 | ||
|
|
bd11ec69fa | ||
|
|
9e2b34bc12 | ||
|
|
0faebc290f | ||
|
|
fc0ef4b79d | ||
|
|
027b954b50 | ||
|
|
33c830a432 | ||
|
|
a7887f1e25 | ||
|
|
fa7a59572a | ||
|
|
d9c2df5b0d | ||
|
|
a854b2c17e | ||
|
|
7d4006b205 | ||
|
|
86ecca6011 | ||
|
|
0ea5cf2caa | ||
|
|
a950e02e9b | ||
|
|
9eec6641dd | ||
|
|
18f46676fd | ||
|
|
9735ef6d41 | ||
|
|
d3e8ceee00 | ||
|
|
144e329eca | ||
|
|
21d5420b2a | ||
|
|
3011b5074d | ||
|
|
83190572c7 | ||
|
|
9cf9e5f865 | ||
|
|
5bdef7f1c7 | ||
|
|
ba285a2d2d | ||
|
|
0dff371e62 | ||
|
|
ce4a2a5851 | ||
|
|
2c978dc89a | ||
|
|
4b8b819109 | ||
|
|
c230b3a806 | ||
|
|
df08b9c5c6 | ||
|
|
eca65376a3 | ||
|
|
88e3705636 | ||
|
|
5476509ef5 | ||
|
|
0bd6636453 | ||
|
|
105894e00d | ||
|
|
230a319510 | ||
|
|
ae16a2b14f | ||
|
|
da0c0742bf | ||
|
|
61d60a9048 | ||
|
|
3e28e9a016 | ||
|
|
423f9fefa9 | ||
|
|
5707dc7579 | ||
|
|
3be1cdb249 | ||
|
|
426d3d948c | ||
|
|
9a3aed8038 | ||
|
|
fb58bf1bf5 | ||
|
|
a6dbd912c6 | ||
|
|
65ce277a20 | ||
|
|
0b2d423c87 | ||
|
|
da056092fb | ||
|
|
45aa85d690 | ||
|
|
5c35fee0c2 | ||
|
|
24bdc319dd | ||
|
|
f1dcc41e42 | ||
|
|
550f301ba2 | ||
|
|
d9bf4d1c0d | ||
|
|
c3c1a6eb22 | ||
|
|
2c4454418e | ||
|
|
e44de572f5 | ||
|
|
f27919f91b | ||
|
|
ba9968bde0 | ||
|
|
05ea8216ff | ||
|
|
fa1695672a | ||
|
|
ac6f98fc47 | ||
|
|
1a1f89f555 | ||
|
|
6c3262e176 | ||
|
|
b4bdb48f1e | ||
|
|
823afe877b | ||
|
|
cb8e082414 | ||
|
|
98c1fcc68f | ||
|
|
8c439a2852 | ||
|
|
50c6109be7 | ||
|
|
6e362663b5 | ||
|
|
74c9feb53f | ||
|
|
402e8588cf | ||
|
|
778a42bcc0 | ||
|
|
584f7ced84 | ||
|
|
8e892e7ea5 | ||
|
|
3386c8b455 | ||
|
|
6fa73ee28d | ||
|
|
8ec8042045 | ||
|
|
cddc123539 | ||
|
|
4c2938c5cd | ||
|
|
6d03ddadcc | ||
|
|
64311da4b4 | ||
|
|
0cbb50ae9d | ||
|
|
7e96054dc2 | ||
|
|
578298580e | ||
|
|
ee5afaa6bc | ||
|
|
15b023d116 | ||
|
|
1ef96c0b4d | ||
|
|
8c3ae40de1 | ||
|
|
94fcfacec4 | ||
|
|
ba7c01c6bc | ||
|
|
9f92e1b7bd | ||
|
|
1f0e692ee2 | ||
|
|
0acd75a24f | ||
|
|
eedf27f8a5 | ||
|
|
b451e207e2 | ||
|
|
c0c37eec7b | ||
|
|
89363ecfa3 | ||
|
|
593e799ca1 | ||
|
|
8fc055cad9 | ||
|
|
75f86462e2 | ||
|
|
40892ccfa7 | ||
|
|
87fbf9c1a5 | ||
|
|
4944b233b6 | ||
|
|
9f23462c42 | ||
|
|
84a24f0333 | ||
|
|
7a885bfc3c | ||
|
|
3ba0cf1454 | ||
|
|
2d67a3159d | ||
|
|
290f25f1a0 | ||
|
|
1659904f81 | ||
|
|
230bd6e40a | ||
|
|
ce27e97b92 | ||
|
|
18c1223c7b | ||
|
|
8ef659f5de | ||
|
|
037452e525 | ||
|
|
e3482011d5 | ||
|
|
62748fa255 | ||
|
|
7a9df05f6b | ||
|
|
335279e728 | ||
|
|
0332104738 | ||
|
|
9f04854902 | ||
|
|
73008a35fe | ||
|
|
eae96cd2af | ||
|
|
cb670bb27d | ||
|
|
fe1fb23e5b | ||
|
|
c2dd61e96b | ||
|
|
80f5e61b6b | ||
|
|
dbcae16b75 | ||
|
|
886dcae822 | ||
|
|
ed495ec600 | ||
|
|
ddb60ccdc5 | ||
|
|
335e2083af | ||
|
|
7b1d9d4962 | ||
|
|
da6ff9f90a | ||
|
|
48f26c7bf1 | ||
|
|
3ce317b170 | ||
|
|
b741565f57 | ||
|
|
d8fea44968 | ||
|
|
778300b67e | ||
|
|
cb2b44fef3 | ||
|
|
cdb5875d6b | ||
|
|
01c5e15bcd | ||
|
|
f0babb4be7 | ||
|
|
10b00da874 | ||
|
|
e9f391b2eb | ||
|
|
50be39b054 | ||
|
|
a94e0bb3da | ||
|
|
3f65d5d760 | ||
|
|
48cb528ae4 | ||
|
|
e62e0345df | ||
|
|
441f011fba | ||
|
|
af1349160a | ||
|
|
2072607889 | ||
|
|
073fd5aa0d | ||
|
|
7b4703e4ff | ||
|
|
1484621300 | ||
|
|
40709c8367 | ||
|
|
b6ab5770a2 | ||
|
|
83b7cb4ff9 | ||
|
|
256dba66b2 | ||
|
|
6ac12f8ffa | ||
|
|
82e438d29b | ||
|
|
e86547645c | ||
|
|
8b901084fe | ||
|
|
1a0cbbdb31 | ||
|
|
30ac62ffb7 | ||
|
|
8ab294e90b | ||
|
|
f5edb15f43 | ||
|
|
eed6107ce7 | ||
|
|
d49dc599a2 | ||
|
|
3c5179f145 | ||
|
|
067d17c09c | ||
|
|
4c88c9af86 | ||
|
|
1c84afe186 | ||
|
|
9d4c4be468 | ||
|
|
de7db8db78 | ||
|
|
407ba4dd6d | ||
|
|
5135be3000 | ||
|
|
b8ab7d1a14 | ||
|
|
6211fd8496 | ||
|
|
a4f273b48b | ||
|
|
68d820a97c | ||
|
|
ebadfd6358 | ||
|
|
cf93a88adc | ||
|
|
bd2c4252bb | ||
|
|
84e7af04b9 | ||
|
|
37c63bc6b5 | ||
|
|
e7d3716549 | ||
|
|
73bc5372c0 | ||
|
|
b999a8f0fb | ||
|
|
16678aa5e1 | ||
|
|
f9ab49911d | ||
|
|
3fabe2e9fb | ||
|
|
9a8a3e94d6 | ||
|
|
32ef2ef801 | ||
|
|
76cd3d35e2 | ||
|
|
900dea2c66 | ||
|
|
8e6ca0dd05 | ||
|
|
4b4ad7f1a8 | ||
|
|
21a0a5d573 | ||
|
|
3cfa3f3b27 | ||
|
|
c77b43458e | ||
|
|
9ab0a83f7c | ||
|
|
d3cda8811d | ||
|
|
55740c0d97 | ||
|
|
c0a524c8a3 | ||
|
|
e51c2d10f0 | ||
|
|
7af55b7268 | ||
|
|
7350b1da1b | ||
|
|
136adbe723 | ||
|
|
cb2863eaf3 | ||
|
|
d054f9b92f | ||
|
|
9c83c18137 | ||
|
|
999a7481e4 | ||
|
|
a3b684b4ed | ||
|
|
cb312ca025 | ||
|
|
1335a52db3 | ||
|
|
727221e2cb | ||
|
|
360f286ed3 | ||
|
|
6aecd77b77 |
@@ -1,2 +0,0 @@
|
||||
src_dir: .
|
||||
coverage_clover: tests/_output/coverage.xml
|
||||
53
.env.example
Executable file
53
.env.example
Executable file
@@ -0,0 +1,53 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_FORCE_SSL=false
|
||||
APP_FORCE_ROOT=
|
||||
APP_KEY=SomeRandomStringOf32CharsExactly
|
||||
APP_LOG_LEVEL=warning
|
||||
APP_URL=http://localhost
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=homestead
|
||||
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
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
GOOGLE_MAPS_API_KEY=
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
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=
|
||||
2
.gitattributes
vendored
Normal file → Executable file
2
.gitattributes
vendored
Normal file → Executable file
@@ -1 +1,3 @@
|
||||
* text=auto
|
||||
*.css linguist-vendored
|
||||
*.scss linguist-vendored
|
||||
|
||||
1
.github/CONTRIBUTING
vendored
Normal file
1
.github/CONTRIBUTING
vendored
Normal file
@@ -0,0 +1 @@
|
||||
If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/) first. Thanks!
|
||||
32
.gitignore
vendored
Normal file → Executable file
32
.gitignore
vendored
Normal file → Executable file
@@ -1,20 +1,14 @@
|
||||
/bootstrap/compiled.php
|
||||
/node_modules
|
||||
/public/storage
|
||||
/vendor
|
||||
composer.phar
|
||||
composer.lock
|
||||
.env.*.php
|
||||
.env.php
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.idea/
|
||||
tests/_output/*
|
||||
_ide_helper.php
|
||||
/build/logs/clover.xml
|
||||
index.html*
|
||||
app/storage/firefly-export*
|
||||
.vagrant
|
||||
firefly-iii-import-*.json
|
||||
|
||||
tests/_output/*
|
||||
testing.sqlite
|
||||
c3.php
|
||||
/.idea
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
.env
|
||||
_development
|
||||
.env.local
|
||||
result.html
|
||||
test-import.sh
|
||||
test-import-report.txt
|
||||
public/google*.html
|
||||
.env.backup
|
||||
|
||||
51
.scrutinizer.yml
Normal file
51
.scrutinizer.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
# .scrutinizer.yml
|
||||
tools:
|
||||
external_code_coverage: false
|
||||
filter:
|
||||
paths:
|
||||
- app/*
|
||||
- public/js/ff/*
|
||||
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
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,15 +1,17 @@
|
||||
language: php
|
||||
|
||||
sudo: false
|
||||
php:
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
- '7.0'
|
||||
|
||||
install:
|
||||
- composer install
|
||||
- phpenv config-rm xdebug.ini
|
||||
- rm composer.lock
|
||||
- composer update --no-scripts
|
||||
- php artisan clear-compiled
|
||||
- php artisan optimize
|
||||
- php artisan env
|
||||
- cp .env.testing .env
|
||||
- mv storage/database/databasecopy.sqlite storage/database/database.sqlite
|
||||
|
||||
script:
|
||||
- php vendor/bin/codecept run
|
||||
|
||||
after_script:
|
||||
- php vendor/bin/coveralls
|
||||
- phpunit
|
||||
495
CHANGELOG.md
Normal file
495
CHANGELOG.md
Normal file
@@ -0,0 +1,495 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
### Added
|
||||
- Option to show deposit accounts on the front page.
|
||||
- Script to upgrade split transactions
|
||||
- Can now save notes on piggy banks.
|
||||
- Extend user admin options.
|
||||
- Run import jobs from the command line
|
||||
|
||||
|
||||
### Changed
|
||||
- New preferences screen layout.
|
||||
|
||||
### Deprecated
|
||||
- ``firefly:import`` is now ``firefly:start-import``
|
||||
|
||||
### Removed
|
||||
- Lots of old code
|
||||
|
||||
### Fixed
|
||||
- #357, where non utf-8 files would break Firefly.
|
||||
- Tab delimiter is not properly loaded from import configuration (@roberthorlings)
|
||||
- System response to yearly bills
|
||||
|
||||
## [4.0.2] - 2016-10-14
|
||||
### Added
|
||||
- Added ``intl`` dependency to composer file to ease installation (thanks @telyn)
|
||||
- Added support for Croatian.
|
||||
|
||||
### Changed
|
||||
- Updated all copyright notices to refer to the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||
- Fixed #344
|
||||
- Fixed #346, thanks to @SanderKleykens
|
||||
- #351
|
||||
- Did some internal remodelling.
|
||||
|
||||
### Fixed
|
||||
- PostgreSQL compatibility thanks to @SanderKleykens
|
||||
- @RobertHorlings fixed a bug in the ABN Amro import specific.
|
||||
|
||||
|
||||
## [4.0.1] - 2016-10-04
|
||||
### Added
|
||||
- New ING import specific by @tomwerf
|
||||
- New Presidents Choice specific to fix #307
|
||||
- Added some trimming (#335)
|
||||
|
||||
### Changed
|
||||
- Initial release.
|
||||
|
||||
### Deprecated
|
||||
- Initial release.
|
||||
|
||||
### Removed
|
||||
- Initial release.
|
||||
|
||||
### Fixed
|
||||
- Fixed a bug where incoming transactions would not be properly filtered in several reports.
|
||||
- #334 by @cyberkov
|
||||
- #337
|
||||
- #336
|
||||
- #338 found by @roberthorlings
|
||||
|
||||
### Security
|
||||
- Initial release.
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.0.0] - 2015-09-26
|
||||
### Added
|
||||
- Upgraded to Laravel 5.3, most other libraries upgraded as well.
|
||||
- Added GBP as currency, thanks to @Mortalife
|
||||
|
||||
### Changed
|
||||
- Jump to version 4.0.0.
|
||||
- Firefly III is now subject to a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/) license. Previous versions of this software are still MIT licensed.
|
||||
|
||||
### Fixed
|
||||
- Support for specific decimal places, thanks to @Mortalife
|
||||
- Various CSS fixes
|
||||
- Various bugs, thanks to @fuf, @sandermulders and @vissert
|
||||
- Various queries optimized for MySQL 5.7
|
||||
|
||||
## [3.10.4] - 2015-09-14
|
||||
### Fixed
|
||||
- Migration fix by @sandermulders
|
||||
- Tricky import bug fix thanks to @vissert
|
||||
- Currency preference will be correctly pulled from user settings, thanks to @fuf
|
||||
- Simplified code for upgrade instructions.
|
||||
|
||||
|
||||
## [3.10.3] - 2016-08-29
|
||||
### Added
|
||||
- More fields for mass-edit, thanks to @Vissert (#282)
|
||||
- First start of German translation
|
||||
|
||||
### Changed
|
||||
- More optional fields for transactions and the ability to filter them.
|
||||
|
||||
### Removed
|
||||
- Preference for budget maximum.
|
||||
|
||||
### Fixed
|
||||
- A bug in the translation routine broke the import.
|
||||
- It was possible to destroy your Firefly installation by removing all currencies. Thanks @mondjef
|
||||
- Translation bugs.
|
||||
- Import bug.
|
||||
|
||||
### Security
|
||||
- Firefly will not accept registrations beyond the first one, by default.
|
||||
|
||||
|
||||
## [3.10.2] - 2016-08-29
|
||||
### Added
|
||||
- New Chinese translations. Set Firefly III to show incomplete translations to follow the progress. Want to translate Firefly III in Chinese, or in any other language? Then check out [the Crowdin project](https://crowdin.com/project/firefly-iii).
|
||||
- Added more admin pages. They do nothing yet.
|
||||
|
||||
### Changed
|
||||
- Import routine will now also apply user rules.
|
||||
- Various code cleanup.
|
||||
- Some small HTML changes.
|
||||
|
||||
### Fixed
|
||||
- Bug in the mass edit routines.
|
||||
- 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
|
||||
|
||||
## [3.10.1] - 2016-08-25
|
||||
### Added
|
||||
- More feedback in the import procedure.
|
||||
- Extended model for import job.
|
||||
- Web bases import procedure.
|
||||
|
||||
|
||||
### Changed
|
||||
- Scrutinizer configuration
|
||||
- Various code clean up.
|
||||
|
||||
### Removed
|
||||
- Code climate YAML file.
|
||||
|
||||
### Fixed
|
||||
- Fixed a bug where a migration would check an empty table name.
|
||||
- Fixed various bugs in the import routine.
|
||||
- Fixed various bugs in the piggy banks pages.
|
||||
- Fixed a bug in the `firefly:verify` routine
|
||||
|
||||
## [3.10] - 2015-05-25
|
||||
### Added
|
||||
- New charts in year report
|
||||
- Can add / remove money from piggy bank on mobile device.
|
||||
- Bill overview shows some useful things.
|
||||
- Firefly will track registration / activation IP addresses.
|
||||
|
||||
|
||||
### Changed
|
||||
- Rewrote the import routine.
|
||||
- The date picker now supports more ranges and periods.
|
||||
- Rewrote all migrations. #272
|
||||
|
||||
### Fixed
|
||||
- Issue #264
|
||||
- Issue #265
|
||||
- Fixed amount calculation problems, #266, thanks @xzaz
|
||||
- Issue #271
|
||||
- Issue #278, #273, thanks @StevenReitsma and @rubella
|
||||
- Bug in attachment download routine would report the wrong size to the user's browser.
|
||||
- Various NULL errors fixed.
|
||||
- Various strict typing errors fixed.
|
||||
- Fixed pagination problems, #276, thanks @xzaz
|
||||
- Fixed a bug where an expense would be assigned to a piggy bank if you created a transfer first.
|
||||
- Bulk update problems, #280, thanks @stickgrinder
|
||||
- Fixed various problems with amount reporting of split transactions.
|
||||
|
||||
## [3.9.1]
|
||||
### Fixed
|
||||
- Fixed a bug where removing money from a piggy bank would not work. See issue #265 and #269
|
||||
|
||||
## [3.9.0]
|
||||
### Added
|
||||
- @zjean has added code that allows you to force "https://"-URL's.
|
||||
- @tonicospinelli has added Portuguese (Brazil) translations.
|
||||
- Firefly III supports the *splitting* of transactions:
|
||||
- A withdrawal (expense) can be split into multiple sub-transactions (with multiple destinations)
|
||||
- Likewise for deposits (incomes). You can set multiple sources.
|
||||
- Likewise for transfers.
|
||||
|
||||
### Changed
|
||||
- Update a lot of libraries.
|
||||
- Big improvement to test data generation.
|
||||
- Cleaned up many repositories.
|
||||
|
||||
### Removed
|
||||
- Front page boxes will no longer respond to credit card bills.
|
||||
|
||||
### Fixed
|
||||
- Many bugs
|
||||
|
||||
## [3.8.4] - 2016-04-24
|
||||
### Added
|
||||
- Lots of new translations.
|
||||
- Can now set page size.
|
||||
- Can now mass edit transactions.
|
||||
- Can now mass delete transactions.
|
||||
- Firefly will now attempt to verify the integrity of your database when updating.
|
||||
|
||||
### Changed
|
||||
- New version of Charts library.
|
||||
|
||||
### Fixed
|
||||
- Several CSV related bugs.
|
||||
- Several other bugs.
|
||||
- Bugs fixed by @Bonno.
|
||||
|
||||
## [3.8.3] - 2016-04-17
|
||||
### Added
|
||||
- New audit report to see what happened.
|
||||
|
||||
### Changed
|
||||
- New Chart JS release used.
|
||||
- Help function is more reliable.
|
||||
|
||||
### Fixed
|
||||
- Expected bill amount is now correct.
|
||||
- Upgrade will now invalidate cache.
|
||||
- Search was broken.
|
||||
- Queries run better
|
||||
|
||||
## [3.8.2] - 2016-04-03
|
||||
### Added
|
||||
- Small user administration at /admin.
|
||||
- Informational popups are working in reports.
|
||||
|
||||
### Changed
|
||||
- User activation emails are better
|
||||
|
||||
### Fixed
|
||||
- Some bugs related to accounts and rules.
|
||||
|
||||
|
||||
## [3.8.1] - 2016-03-29
|
||||
### Added
|
||||
- More translations
|
||||
- Extended cookie control.
|
||||
- User accounts can now be activated (disabled by default).
|
||||
- Bills can now take the source and destination account name into account.
|
||||
|
||||
### Changed
|
||||
- The pages related to rules have new URL's.
|
||||
|
||||
### Fixed
|
||||
- Spelling errors.
|
||||
- Problems related to the "account repository".
|
||||
- Some views showed empty (0.0) amounts.
|
||||
|
||||
## [3.8.0] - 2016-03-20
|
||||
### Added
|
||||
- Two factor authentication, thanks to the excellent work of [zjean](https://github.com/zjean).
|
||||
- A new chart showing your net worth in year and multi-year reports.
|
||||
- You can now see if your current or future rules actually match any transactions, thanks to the excellent work of @roberthorlings.
|
||||
- New date fields for transactions. They are not used yet in reports or anything, but they can be filled in.
|
||||
- New routine to export your data.
|
||||
- Firefly III will mail the site owner when blocked users try to login, or when blocked domains are used in registrations.
|
||||
|
||||
|
||||
### Changed
|
||||
- Firefly III now requires PHP 7.0 minimum.
|
||||
|
||||
|
||||
### Fixed
|
||||
- HTML fixes, thanks to [roberthorlings](https://github.com/roberthorlings) and [zjean](https://github.com/zjean)..
|
||||
- A bug fix in the ABN Amro importer, thanks to [roberthorlings](https://github.com/roberthorlings)
|
||||
- It was not possible to change the opening balance, once it had been set. Thanks to [xnyhps](https://github.com/xnyhps) and [marcoveeneman](https://github.com/marcoveeneman) for spotting this.
|
||||
- Various other bug fixes.
|
||||
|
||||
|
||||
|
||||
## [3.4.2] - 2015-05-25
|
||||
### Added
|
||||
- Initial release.
|
||||
|
||||
### Changed
|
||||
- Initial release.
|
||||
|
||||
### Deprecated
|
||||
- Initial release.
|
||||
|
||||
### Removed
|
||||
- Initial release.
|
||||
|
||||
### Fixed
|
||||
- Initial release.
|
||||
|
||||
### Security
|
||||
- Initial release.
|
||||
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
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 && \
|
||||
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
|
||||
|
||||
# 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
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
80
README.md
80
README.md
@@ -1,76 +1,36 @@
|
||||
Firefly III
|
||||
===========
|
||||
# Firefly III: A personal finances manager
|
||||
|
||||
[](https://travis-ci.org/JC5/firefly-iii)
|
||||

|
||||
[](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://packagist.org/packages/grumpydictator/firefly-iii)
|
||||
[](https://packagist.org/packages/grumpydictator/firefly-iii)
|
||||
[](https://packagist.org/packages/grumpydictator/firefly-iii)
|
||||
[](https://packagist.org/packages/grumpydictator/firefly-iii)
|
||||
[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
|
||||
|
||||
Firefly II is a tool to help you manage your finances. Please read the full description [in the wiki](https://github.com/JC5/firefly-iii/wiki/full-description).
|
||||
[](https://i.nder.be/ccn0u2mp) [](https://i.nder.be/gm8hbh7z)
|
||||
|
||||
Firefly Mark III is a new version of Firefly built upon best practices and lessons learned
|
||||
from building [Firefly](https://github.com/JC5/Firefly). It's Mark III since the original Firefly never made it outside of my
|
||||
laptop and [Firefly II](https://github.com/JC5/Firefly) is live.
|
||||
"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.
|
||||
|
||||
## Current features
|
||||
## Try it out!
|
||||
|
||||
- [A double-entry bookkeeping system](http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system).
|
||||
- You can store, edit and remove withdrawals, deposits and transfers. This allows you full financial management;
|
||||
- It's possible to create, change and manage money using _budgets_;
|
||||
- Organize transactions using categories;
|
||||
- Save towards a goal using piggy banks;
|
||||
- Predict and anticipate large expenses using "repeated expenses" (ie. yearly taxes);
|
||||
- Predict and anticipate bills using "recurring transactions" (rent for example).
|
||||
Try out Firefly III on the [demo site](https://firefly-iii.nder.be/).
|
||||
|
||||
Everything is organised:
|
||||
## Installation
|
||||
|
||||
- Clear views that should show you how you're doing;
|
||||
- Easy navigation through your records;
|
||||
- Browse back and forth to see previous months or even years;
|
||||
- Lots of charts because we all love them.
|
||||
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/).
|
||||
|
||||
## Changes
|
||||
## More about Firefly III
|
||||
|
||||
Firefly III will feature, but does not feature yet:
|
||||
Personal financial management is pretty difficult, and everybody has their own approach to it. Some people make budgets, other people limit their cashflow by throwing away their credit cards, others try to increase their current cashflow. There are tons of ways to save and earn money.
|
||||
|
||||
- Financial reporting showing you how well you are doing;
|
||||
- Lots of help text in case you don't get it;
|
||||
- More control over other resources outside of personal finance
|
||||
- Accounts shared with a partner (household accounts)
|
||||
- Debts
|
||||
- Credit cards
|
||||
- More test-coverage (aka: actual test coverage);
|
||||
- Firefly will be able to split transactions; a single purchase can be split in multiple entries, for more fine-grained control.
|
||||
- Firefly will be able to join transactions.
|
||||
- Transfers and transactions are combined into one internal datatype which is more consistent with what you're actually doing: moving money from A to B. The fact that A or B or both are yours should not matter.
|
||||
- Any other features I might not have thought of.
|
||||
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
|
||||
|
||||
Some stuff has been removed:
|
||||
#### Some advantages of using Firefly
|
||||
|
||||
- The nesting of budgets, categories and beneficiaries is removed because it was pretty pointless.
|
||||
- Firefly will not encrypt the content of the (MySQL) tables. Old versions of Firefly had this capability but it sucks when searching, sorting and organizing entries.
|
||||
- Firefly can import any CSV file, so migrating from other systems is easy.
|
||||
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", it’s just somebody else’s computer!
|
||||
- Firefly 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!
|
||||
|
||||
## Screenshots
|
||||
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!)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Current state
|
||||
I have the basics up and running. Test coverage is currently non-existent.
|
||||
|
||||
Although I have not checked extensively, some forms and views have CSRF vulnerabilities. This is because not all
|
||||
views escape all characters by default. Will be fixed.
|
||||
|
||||
The current layout / look & feel is a pretty basic Bootstrap3 template. I am currently working on a more consistent,
|
||||
expanded layout which will feature shiny AJAX things and data tables and all the Web 3.0 goodies you've come to expect
|
||||
from social media sites.
|
||||
|
||||
Questions, ideas or other things to contribute? [Let me know](https://github.com/JC5/firefly-iii/issues/new)!
|
||||
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
|
||||
|
||||
63
app/Bootstrap/ConfigureLogging.php
Normal file
63
app/Bootstrap/ConfigureLogging.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* ConfigureLogging.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Bootstrap;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Foundation\Bootstrap\ConfigureLogging as IlluminateConfigureLogging;
|
||||
use Illuminate\Log\Writer;
|
||||
|
||||
/**
|
||||
* Class ConfigureLogging
|
||||
*
|
||||
* @package FireflyIII\Bootstrap
|
||||
*/
|
||||
class ConfigureLogging extends IlluminateConfigureLogging
|
||||
{
|
||||
|
||||
/**
|
||||
* Configure the Monolog handlers for the application.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @param \Illuminate\Log\Writer $log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureDailyHandler(Application $app, Writer $log)
|
||||
{
|
||||
$config = $app->make('config');
|
||||
|
||||
$maxFiles = $config->get('app.log_max_files');
|
||||
|
||||
$log->useDailyFiles(
|
||||
$app->storagePath() . '/logs/firefly-iii.log', is_null($maxFiles) ? 5 : $maxFiles,
|
||||
$config->get('app.log_level', 'debug')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the Monolog handlers for the application.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @param \Illuminate\Log\Writer $log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureSingleHandler(Application $app, Writer $log)
|
||||
{
|
||||
$log->useFiles(
|
||||
$app->storagePath() . '/logs/firefly-iii.log',
|
||||
$app->make('config')->get('app.log_level', 'debug')
|
||||
);
|
||||
}
|
||||
}
|
||||
144
app/Console/Commands/CreateImport.php
Normal file
144
app/Console/Commands/CreateImport.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* CreateImport.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Artisan;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class CreateImport
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class CreateImport extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Use this command to create a new import. Your user ID can be found on the /profile page.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:create-import {file} {configuration} {--user=1} {--type=csv} {--start}';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
$file = $this->argument('file');
|
||||
$configuration = $this->argument('configuration');
|
||||
$user = $userRepository->find(intval($this->option('user')));
|
||||
$cwd = getcwd();
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
if (!$this->validArguments()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configurationData = json_decode(file_get_contents($configuration));
|
||||
if (is_null($configurationData)) {
|
||||
$this->error(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info(sprintf('Going to create a job to import file: %s', $file));
|
||||
$this->info(sprintf('Using configuration file: %s', $configuration));
|
||||
$this->info(sprintf('Import into user: #%d (%s)', $user->id, $user->email));
|
||||
$this->info(sprintf('Type of import: %s', $type));
|
||||
|
||||
/** @var ImportJobRepositoryInterface $jobRepository */
|
||||
$jobRepository = app(ImportJobRepositoryInterface::class, [$user]);
|
||||
$job = $jobRepository->create($type);
|
||||
$this->line(sprintf('Created job "%s"...', $job->key));
|
||||
|
||||
Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]);
|
||||
$this->line('Stored import data...');
|
||||
|
||||
$job->configuration = $configurationData;
|
||||
$job->status = 'settings_complete';
|
||||
$job->save();
|
||||
$this->line('Stored configuration...');
|
||||
|
||||
if ($this->option('start') === true) {
|
||||
$this->line('The import will start in a moment. This process is not visible...');
|
||||
Log::debug('Go for import!');
|
||||
Artisan::call('firefly:start-import', ['key' => $job->key]);
|
||||
$this->line('Done!');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly.
|
||||
*/
|
||||
private function validArguments(): bool
|
||||
{
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
$file = $this->argument('file');
|
||||
$configuration = $this->argument('configuration');
|
||||
$user = $userRepository->find(intval($this->option('user')));
|
||||
$cwd = getcwd();
|
||||
$validTypes = array_keys(config('firefly.import_formats'));
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
if (is_null($user->id)) {
|
||||
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!in_array($type, $validTypes)) {
|
||||
$this->error(sprintf('Cannot import file of type "%s"', $type));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_exists($file)) {
|
||||
$this->error(sprintf('Firefly III cannot find file "%s" (working directory: "%s").', $file, $cwd));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_exists($configuration)) {
|
||||
$this->error(sprintf('Firefly III cannot find configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
68
app/Console/Commands/EncryptFile.php
Normal file
68
app/Console/Commands/EncryptFile.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* EncryptFile.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Crypt;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class EncryptFile
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class EncryptFile extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Encrypts a file and places it in the storage/upload directory.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:encrypt {file} {key}';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$file = e(strval($this->argument('file')));
|
||||
if (!file_exists($file)) {
|
||||
$this->error(sprintf('File "%s" does not seem to exist.', $file));
|
||||
|
||||
return;
|
||||
}
|
||||
$content = file_get_contents($file);
|
||||
$content = Crypt::encrypt($content);
|
||||
$newName = e(strval($this->argument('key'))) . '.upload';
|
||||
|
||||
$path = storage_path('upload') . '/' . $newName;
|
||||
file_put_contents($path, $content);
|
||||
$this->line(sprintf('Encrypted "%s" and put it in "%s"', $file, $path));
|
||||
}
|
||||
}
|
||||
139
app/Console/Commands/Import.php
Normal file
139
app/Console/Commands/Import.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* Import.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use FireflyIII\Import\ImportProcedure;
|
||||
use FireflyIII\Import\Logging\CommandHandler;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class Import
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class Import extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This will start a new import.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:start-import {key}';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Log::debug('Start start-import command');
|
||||
$jobKey = $this->argument('key');
|
||||
$job = ImportJob::whereKey($jobKey)->first();
|
||||
if (!$this->isValid($job)) {
|
||||
Log::error('Job is not valid for some reason. Exit.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line(sprintf('Going to import job with key "%s" of type "%s"', $job->key, $job->file_type));
|
||||
|
||||
$monolog = Log::getMonolog();
|
||||
$handler = new CommandHandler($this);
|
||||
$monolog->pushHandler($handler);
|
||||
$importProcedure = new ImportProcedure;
|
||||
$result = $importProcedure->runImport($job);
|
||||
|
||||
// display result to user:
|
||||
$this->presentResults($result);
|
||||
$this->line('The import has completed.');
|
||||
|
||||
// get any errors from the importer:
|
||||
$this->presentErrors($job);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isValid(ImportJob $job): bool
|
||||
{
|
||||
if (is_null($job)) {
|
||||
$this->error('This job does not seem to exist.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($job->status != 'settings_complete') {
|
||||
$this->error('This job is not ready to be imported.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
85
app/Console/Commands/ScanAttachments.php
Normal file
85
app/Console/Commands/ScanAttachments.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* ScanAttachments.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Crypt;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Storage;
|
||||
|
||||
/**
|
||||
* Class ScanAttachments
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class ScanAttachments extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rescan all attachments and re-set the MD5 hash and mime.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:scan-attachments';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$attachments = Attachment::get();
|
||||
$disk = Storage::disk('upload');
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($attachments as $attachment) {
|
||||
$fileName = $attachment->fileName();
|
||||
try {
|
||||
$content = $disk->get($fileName);
|
||||
} catch (FileNotFoundException $e) {
|
||||
$this->error(sprintf('Could not find data for attachment #%d', $attachment->id));
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$decrypted = Crypt::decrypt($content);
|
||||
} catch (DecryptException $e) {
|
||||
$this->error(sprintf('Could not decrypt data of attachment #%d', $attachment->id));
|
||||
continue;
|
||||
}
|
||||
$tmpfname = tempnam(sys_get_temp_dir(), 'FireflyIII');
|
||||
file_put_contents($tmpfname, $decrypted);
|
||||
$md5 = md5_file($tmpfname);
|
||||
$mime = mime_content_type($tmpfname);
|
||||
$attachment->md5 = $md5;
|
||||
$attachment->mime = $mime;
|
||||
$attachment->save();
|
||||
$this->line(sprintf('Fixed attachment #%d', $attachment->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
159
app/Console/Commands/UpgradeDatabase.php
Normal file
159
app/Console/Commands/UpgradeDatabase.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* UpgradeDatabase.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
|
||||
use DB;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\LimitRepetition;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Log;
|
||||
use Schema;
|
||||
|
||||
/**
|
||||
* Class UpgradeDatabase
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class UpgradeDatabase extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Will run various commands to update database records.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:upgrade-database';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
|
||||
*/
|
||||
private function setTransactionIdentifier()
|
||||
{
|
||||
// if table does not exist, return false
|
||||
if (!Schema::hasTable('transaction_journals')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$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'))
|
||||
->mergeBindings($subQuery->getQuery())
|
||||
->where('t_count', '>', 2)
|
||||
->select(['id', 't_count']);
|
||||
$journalIds = array_unique($result->pluck('id')->toArray());
|
||||
|
||||
foreach ($journalIds as $journalId) {
|
||||
$this->updateJournal(intval($journalId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
|
||||
* which has 0 as an identifier and give it the same identifier.
|
||||
*
|
||||
* @param int $journalId
|
||||
*/
|
||||
private function updateJournal(int $journalId)
|
||||
{
|
||||
$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 */
|
||||
$opposing = Transaction::where('transaction_journal_id', $journalId)
|
||||
->where('amount', $amount)->where('identifier', '=', 0)
|
||||
->whereNotIn('id', $processed)
|
||||
->first();
|
||||
} catch (QueryException $e) {
|
||||
Log::error($e->getMessage());
|
||||
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
|
||||
$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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
83
app/Console/Commands/UpgradeFireflyInstructions.php
Normal file
83
app/Console/Commands/UpgradeFireflyInstructions.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* UpgradeFireflyInstructions.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class UpgradeFireflyInstructions
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class UpgradeFireflyInstructions extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Instructions in case of upgrade trouble.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:upgrade-instructions';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
/** @var string $version */
|
||||
$version = config('firefly.version');
|
||||
$config = config('upgrade.text');
|
||||
$text = null;
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
$len = strlen($compare);
|
||||
if (substr($version, 0, $len) === $compare) {
|
||||
$text = $config[$compare];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (is_null($text)) {
|
||||
$this->line(sprintf('Thank you for installing Firefly III, v%s', $version));
|
||||
$this->info('There are no extra upgrade instructions.');
|
||||
$this->line('Firefly III should be ready for use.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line('+------------------------------------------------------------------------------+');
|
||||
$this->line('');
|
||||
$this->line(sprintf('Thank you for installing Firefly III, v%s', $version));
|
||||
$this->info(wordwrap($text));
|
||||
$this->line('');
|
||||
$this->line('+------------------------------------------------------------------------------+');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
348
app/Console/Commands/VerifyDatabase.php
Normal file
348
app/Console/Commands/VerifyDatabase.php
Normal file
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
/**
|
||||
* VerifyDatabase.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Crypt;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Schema;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class VerifyDatabase
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class VerifyDatabase extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Will verify your database.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:verify';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
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.
|
||||
$this->reportAccounts();
|
||||
// budgets with no limits
|
||||
$this->reportBudgetLimits();
|
||||
// budgets with no transactions
|
||||
|
||||
// sum of transactions is not zero.
|
||||
$this->reportSum();
|
||||
// any deleted transaction journals that have transactions that are NOT deleted:
|
||||
$this->reportJournals();
|
||||
// deleted transactions that are connected to a not deleted journal.
|
||||
$this->reportTransactions();
|
||||
// deleted accounts that still have not deleted transactions or journals attached to them.
|
||||
$this->reportDeletedAccounts();
|
||||
|
||||
// report on journals with no transactions at all.
|
||||
$this->reportNoTransactions();
|
||||
|
||||
// transfers with budgets.
|
||||
$this->reportTransfersBudgets();
|
||||
|
||||
// report on journals with the wrong types of accounts.
|
||||
$this->reportIncorrectJournals();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on accounts with no transactions.
|
||||
*/
|
||||
private function reportAccounts()
|
||||
{
|
||||
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
|
||||
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||
->whereNull('transactions.account_id')
|
||||
->get(
|
||||
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
|
||||
);
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$name = $entry->name;
|
||||
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
|
||||
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $name);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on budgets with no budget limits (which makes them pointless).
|
||||
*/
|
||||
private function reportBudgetLimits()
|
||||
{
|
||||
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
|
||||
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
||||
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
|
||||
->whereNull('budget_limits.id')
|
||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']);
|
||||
|
||||
/** @var Budget $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = sprintf(
|
||||
'Notice: User #%d (%s) has budget #%d ("%s") which has no budget limits.',
|
||||
$entry->user_id, $entry->email, $entry->id, $entry->name
|
||||
);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on deleted accounts that still have not deleted transactions or journals attached to them.
|
||||
*/
|
||||
private function reportDeletedAccounts()
|
||||
{
|
||||
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->whereNotNull('accounts.deleted_at')
|
||||
->whereNotNull('transactions.id')
|
||||
->where(
|
||||
function (Builder $q) {
|
||||
$q->whereNull('transactions.deleted_at');
|
||||
$q->orWhereNull('transaction_journals.deleted_at');
|
||||
}
|
||||
)
|
||||
->get(
|
||||
['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',
|
||||
'transaction_journals.deleted_at as journal_deleted_at']
|
||||
);
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$date = is_null($entry->transaction_deleted_at) ? $entry->journal_deleted_at : $entry->transaction_deleted_at;
|
||||
$this->error(
|
||||
'Error: Account #' . $entry->account_id . ' should have been deleted, but has not.' .
|
||||
' Find it in the table called "accounts" and change the "deleted_at" field to: "' . $date . '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
*/
|
||||
private function reportJournals()
|
||||
{
|
||||
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->whereNotNull('transaction_journals.deleted_at')// USE THIS
|
||||
->whereNull('transactions.deleted_at')
|
||||
->whereNotNull('transactions.id')
|
||||
->get(
|
||||
[
|
||||
'transaction_journals.id as journal_id',
|
||||
'transaction_journals.description',
|
||||
'transaction_journals.deleted_at as journal_deleted',
|
||||
'transactions.id as transaction_id',
|
||||
'transactions.deleted_at as transaction_deleted_at']
|
||||
);
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$this->error(
|
||||
'Error: Transaction #' . $entry->transaction_id . ' should have been deleted, but has not.' .
|
||||
' Find it in the table called "transactions" and change the "deleted_at" field to: "' . $entry->journal_deleted . '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function reportNoTransactions()
|
||||
{
|
||||
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->groupBy('transaction_journals.id')
|
||||
->whereNull('transactions.transaction_journal_id')
|
||||
->get(['transaction_journals.id']);
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->error(
|
||||
'Error: Journal #' . $entry->id . ' has zero transactions. Open table "transaction_journals" and delete the entry with id #' . $entry->id
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
private function reportSum()
|
||||
{
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($userRepository->all() as $user) {
|
||||
$sum = strval($user->transactions()->sum('amount'));
|
||||
if (bccomp($sum, '0') !== 0) {
|
||||
$this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on deleted transactions that are connected to a not deleted journal.
|
||||
*/
|
||||
private function reportTransactions()
|
||||
{
|
||||
$set = Transaction::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->whereNotNull('transactions.deleted_at')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->get(
|
||||
['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id',
|
||||
'transaction_journals.deleted_at']
|
||||
);
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$this->error(
|
||||
'Error: Transaction journal #' . $entry->journal_id . ' should have been deleted, but has not.' .
|
||||
' Find it in the table called "transaction_journals" and change the "deleted_at" field to: "' . $entry->transaction_deleted . '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function reportTransfersBudgets()
|
||||
{
|
||||
$set = TransactionJournal::distinct()
|
||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.id']);
|
||||
|
||||
/** @var TransactionJournal $entry */
|
||||
foreach ($set as $entry) {
|
||||
$this->error(
|
||||
sprintf(
|
||||
'Error: Transaction journal #%d is a transfer, but has a budget. Edit it without changing anything, so the budget will be removed.',
|
||||
$entry->id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
77
app/Console/Kernel.php
Normal file
77
app/Console/Kernel.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Kernel.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console;
|
||||
|
||||
use FireflyIII\Console\Commands\CreateImport;
|
||||
use FireflyIII\Console\Commands\EncryptFile;
|
||||
use FireflyIII\Console\Commands\Import;
|
||||
use FireflyIII\Console\Commands\MoveRepository;
|
||||
use FireflyIII\Console\Commands\ScanAttachments;
|
||||
use FireflyIII\Console\Commands\UpgradeDatabase;
|
||||
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
|
||||
use FireflyIII\Console\Commands\VerifyDatabase;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
/**
|
||||
* Class Kernel
|
||||
*
|
||||
* @package FireflyIII\Console
|
||||
*/
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The bootstrap classes for the application.
|
||||
*
|
||||
* Next upgrade verify these are the same.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bootstrappers
|
||||
= [
|
||||
'Illuminate\Foundation\Bootstrap\DetectEnvironment',
|
||||
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
|
||||
'FireflyIII\Bootstrap\ConfigureLogging',
|
||||
'Illuminate\Foundation\Bootstrap\HandleExceptions',
|
||||
'Illuminate\Foundation\Bootstrap\RegisterFacades',
|
||||
'Illuminate\Foundation\Bootstrap\SetRequestForConsole',
|
||||
'Illuminate\Foundation\Bootstrap\RegisterProviders',
|
||||
'Illuminate\Foundation\Bootstrap\BootProviders',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands
|
||||
= [
|
||||
UpgradeFireflyInstructions::class,
|
||||
VerifyDatabase::class,
|
||||
Import::class,
|
||||
CreateImport::class,
|
||||
EncryptFile::class,
|
||||
ScanAttachments::class,
|
||||
UpgradeDatabase::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the Closure based commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
23
app/Events/Event.php
Normal file
23
app/Events/Event.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* Event.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;
|
||||
|
||||
/**
|
||||
* Class Event
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
abstract class Event
|
||||
{
|
||||
//
|
||||
}
|
||||
42
app/Events/RegisteredUser.php
Normal file
42
app/Events/RegisteredUser.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* RegisteredUser.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 RegisteredUser
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class RegisteredUser extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $ipAddress;
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance. This event is triggered when a new user registers.
|
||||
*
|
||||
* @param User $user
|
||||
* @param string $ipAddress
|
||||
*/
|
||||
public function __construct(User $user, string $ipAddress)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->ipAddress = $ipAddress;
|
||||
}
|
||||
}
|
||||
46
app/Events/RequestedNewPassword.php
Normal file
46
app/Events/RequestedNewPassword.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* RequestedNewPassword.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class RequestedNewPassword
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class RequestedNewPassword extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $ipAddress;
|
||||
public $token;
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance. This event is triggered when a users tries to reset his or her password.
|
||||
*
|
||||
* @param User $user
|
||||
* @param string $token
|
||||
* @param string $ipAddress
|
||||
*/
|
||||
public function __construct(User $user, string $token, string $ipAddress)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->token = $token;
|
||||
$this->ipAddress = $ipAddress;
|
||||
}
|
||||
|
||||
}
|
||||
46
app/Events/StoredTransactionJournal.php
Normal file
46
app/Events/StoredTransactionJournal.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* StoredTransactionJournal.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\Models\TransactionJournal;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class StoredTransactionJournal
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class StoredTransactionJournal extends Event
|
||||
{
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $journal;
|
||||
public $piggyBankId;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
* @param int $piggyBankId
|
||||
*/
|
||||
public function __construct(TransactionJournal $journal, int $piggyBankId)
|
||||
{
|
||||
//
|
||||
$this->journal = $journal;
|
||||
$this->piggyBankId = $piggyBankId;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
42
app/Events/UpdatedTransactionJournal.php
Normal file
42
app/Events/UpdatedTransactionJournal.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* UpdatedTransactionJournal.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\Models\TransactionJournal;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class UpdatedTransactionJournal
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class UpdatedTransactionJournal extends Event
|
||||
{
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $journal;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*/
|
||||
public function __construct(TransactionJournal $journal)
|
||||
{
|
||||
//
|
||||
$this->journal = $journal;
|
||||
}
|
||||
|
||||
}
|
||||
24
app/Exceptions/FireflyException.php
Normal file
24
app/Exceptions/FireflyException.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* FireflyException.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\Exceptions;
|
||||
|
||||
|
||||
/**
|
||||
* Class FireflyException
|
||||
*
|
||||
* @package FireflyIII\Exceptions
|
||||
*/
|
||||
class FireflyException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
126
app/Exceptions/Handler.php
Normal file
126
app/Exceptions/Handler.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Handler.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\Exceptions;
|
||||
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use FireflyIII\Jobs\MailError;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Session\TokenMismatchException;
|
||||
use Illuminate\Validation\ValidationException as ValException;
|
||||
use Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
/**
|
||||
* Class Handler
|
||||
*
|
||||
* @package FireflyIII\Exceptions
|
||||
*/
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of the exception types that should not be reported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dontReport
|
||||
= [
|
||||
AuthenticationException::class,
|
||||
AuthorizationException::class,
|
||||
HttpException::class,
|
||||
ModelNotFoundException::class,
|
||||
TokenMismatchException::class,
|
||||
ValException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Exception $exception
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function render($request, Exception $exception)
|
||||
{
|
||||
if ($exception instanceof FireflyException || $exception instanceof ErrorException) {
|
||||
|
||||
$isDebug = env('APP_DEBUG', false);
|
||||
|
||||
return response()->view('errors.FireflyException', ['exception' => $exception, 'debug' => $isDebug], 500);
|
||||
}
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Report or log an exception.
|
||||
*
|
||||
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function report(Exception $exception)
|
||||
{
|
||||
$doMailError = env('SEND_ERROR_MESSAGE', true);
|
||||
if (($exception instanceof FireflyException || $exception instanceof ErrorException) && $doMailError) {
|
||||
$userData = [
|
||||
'id' => 0,
|
||||
'email' => 'unknown@example.com',
|
||||
];
|
||||
if (auth()->check()) {
|
||||
$userData['id'] = auth()->user()->id;
|
||||
$userData['email'] = auth()->user()->email;
|
||||
}
|
||||
$data = [
|
||||
'class' => get_class($exception),
|
||||
'errorMessage' => $exception->getMessage(),
|
||||
'time' => date('r'),
|
||||
'stackTrace' => $exception->getTraceAsString(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'code' => $exception->getCode(),
|
||||
];
|
||||
|
||||
// create job that will mail.
|
||||
$ipAddress = Request::ip() ?? '0.0.0.0';
|
||||
$job = new MailError($userData, env('SITE_OWNER', ''), $ipAddress, $data);
|
||||
dispatch($job);
|
||||
}
|
||||
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an authentication exception into an unauthenticated response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
protected function unauthenticated($request)
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Unauthenticated.'], 401);
|
||||
}
|
||||
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
}
|
||||
24
app/Exceptions/NotImplementedException.php
Normal file
24
app/Exceptions/NotImplementedException.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* NotImplementedException.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\Exceptions;
|
||||
|
||||
|
||||
/**
|
||||
* Class NotImplementedException
|
||||
*
|
||||
* @package FireflyIII\Exceptions
|
||||
*/
|
||||
class NotImplementedException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
23
app/Exceptions/ValidationException.php
Normal file
23
app/Exceptions/ValidationException.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* ValidationException.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\Exceptions;
|
||||
|
||||
/**
|
||||
* Class ValidationExceptions
|
||||
*
|
||||
* @package FireflyIII\Exception
|
||||
*/
|
||||
class ValidationException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
132
app/Export/Collector/AttachmentCollector.php
Normal file
132
app/Export/Collector/AttachmentCollector.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* AttachmentCollector.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 FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Storage;
|
||||
|
||||
/**
|
||||
* Class AttachmentCollector
|
||||
*
|
||||
* @package FireflyIII\Export\Collector
|
||||
*/
|
||||
class AttachmentCollector extends BasicCollector implements CollectorInterface
|
||||
{
|
||||
/** @var Carbon */
|
||||
private $end;
|
||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||
private $exportDisk;
|
||||
/** @var AttachmentRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var Carbon */
|
||||
private $start;
|
||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||
private $uploadDisk;
|
||||
|
||||
/**
|
||||
* AttachmentCollector constructor.
|
||||
*
|
||||
* @param ExportJob $job
|
||||
*/
|
||||
public function __construct(ExportJob $job)
|
||||
{
|
||||
/** @var AttachmentRepositoryInterface repository */
|
||||
$this->repository = app(AttachmentRepositoryInterface::class);
|
||||
// make storage:
|
||||
$this->uploadDisk = Storage::disk('upload');
|
||||
$this->exportDisk = Storage::disk('export');
|
||||
|
||||
parent::__construct($job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
// grab all the users attachments:
|
||||
$attachments = $this->getAttachments();
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($attachments as $attachment) {
|
||||
$this->exportAttachment($attachment);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*/
|
||||
public function setDates(Carbon $start, Carbon $end)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function exportAttachment(Attachment $attachment): bool
|
||||
{
|
||||
$file = $attachment->fileName();
|
||||
if ($this->uploadDisk->exists($file)) {
|
||||
try {
|
||||
$decrypted = Crypt::decrypt($this->uploadDisk->get($file));
|
||||
$exportFile = $this->exportFileName($attachment);
|
||||
$this->exportDisk->put($exportFile, $decrypted);
|
||||
$this->getEntries()->push($exportFile);
|
||||
|
||||
} catch (DecryptException $e) {
|
||||
Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the new file name for the export file.
|
||||
*
|
||||
* @param $attachment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function exportFileName($attachment): string
|
||||
{
|
||||
|
||||
return sprintf('%s-Attachment nr. %s - %s', $this->job->key, strval($attachment->id), $attachment->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
private function getAttachments(): Collection
|
||||
{
|
||||
$attachments = $this->repository->getBetween($this->start, $this->end);
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
}
|
||||
60
app/Export/Collector/BasicCollector.php
Normal file
60
app/Export/Collector/BasicCollector.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* BasicCollector.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 FireflyIII\Models\ExportJob;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class BasicCollector
|
||||
*
|
||||
* @package FireflyIII\Export\Collector
|
||||
*/
|
||||
class BasicCollector
|
||||
{
|
||||
/** @var ExportJob */
|
||||
protected $job;
|
||||
/** @var Collection */
|
||||
private $entries;
|
||||
|
||||
/**
|
||||
* BasicCollector constructor.
|
||||
*
|
||||
* @param ExportJob $job
|
||||
*/
|
||||
public function __construct(ExportJob $job)
|
||||
{
|
||||
$this->entries = new Collection;
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEntries(): Collection
|
||||
{
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $entries
|
||||
*/
|
||||
public function setEntries(Collection $entries)
|
||||
{
|
||||
$this->entries = $entries;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
43
app/Export/Collector/CollectorInterface.php
Normal file
43
app/Export/Collector/CollectorInterface.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* CollectorInterface.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 Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface CollectorInterface
|
||||
*
|
||||
* @package FireflyIII\Export\Collector
|
||||
*/
|
||||
interface CollectorInterface
|
||||
{
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEntries(): Collection;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function run(): bool;
|
||||
|
||||
/**
|
||||
* @param Collection $entries
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
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',
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
220
app/Export/Collector/UploadCollector.php
Normal file
220
app/Export/Collector/UploadCollector.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* UploadCollector.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 Crypt;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Log;
|
||||
use Storage;
|
||||
|
||||
/**
|
||||
* Class UploadCollector
|
||||
*
|
||||
* @package FireflyIII\Export\Collector
|
||||
*/
|
||||
class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
{
|
||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||
private $exportDisk;
|
||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||
private $uploadDisk;
|
||||
/** @var string */
|
||||
private $vintageFormat;
|
||||
|
||||
/**
|
||||
* AttachmentCollector constructor.
|
||||
*
|
||||
* @param ExportJob $job
|
||||
*/
|
||||
public function __construct(ExportJob $job)
|
||||
{
|
||||
parent::__construct($job);
|
||||
|
||||
Log::debug('Going to collect attachments', ['key' => $job->key]);
|
||||
|
||||
// make storage:
|
||||
$this->uploadDisk = Storage::disk('upload');
|
||||
$this->exportDisk = Storage::disk('export');
|
||||
|
||||
// file names associated with the old import routine.
|
||||
$this->vintageFormat = sprintf('csv-upload-%d-', auth()->user()->id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called from the outside to actually start the export.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
// collect old upload files (names beginning with "csv-upload".
|
||||
$this->collectVintageUploads();
|
||||
|
||||
// then collect current upload files:
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function collectVintageUploads(): bool
|
||||
{
|
||||
// grab upload directory.
|
||||
$files = $this->uploadDisk->files();
|
||||
|
||||
foreach ($files as $entry) {
|
||||
$this->processVintageUpload($entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tells you when the vintage upload file was actually uploaded.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getVintageUploadDate(string $entry): string
|
||||
{
|
||||
// this is an original upload.
|
||||
$parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry));
|
||||
$originalUpload = intval($parts[1]);
|
||||
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells you if a file name is a vintage upload.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isVintageImport(string $entry): bool
|
||||
{
|
||||
$len = strlen($this->vintageFormat);
|
||||
// file is part of the old import routine:
|
||||
if (substr($entry, 0, $len) === $this->vintageFormat) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function processModernUpload(string $key): bool
|
||||
{
|
||||
// find job associated with import file:
|
||||
$job = $this->job->user->importJobs()->where('key', $key)->first();
|
||||
if (is_null($job)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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.
|
||||
$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);
|
||||
$this->exportDisk->put($file, $content);
|
||||
$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
|
||||
*/
|
||||
private function saveVintageImportFile(string $entry)
|
||||
{
|
||||
$content = '';
|
||||
try {
|
||||
$content = Crypt::decrypt($this->uploadDisk->get($entry));
|
||||
} catch (DecryptException $e) {
|
||||
Log::error('Could not decrypt old CSV import file ' . $entry . '. Skipped because ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (strlen($content) > 0) {
|
||||
// add to export disk.
|
||||
$date = $this->getVintageUploadDate($entry);
|
||||
$file = $this->job->key . '-Old import dated ' . $date . '.csv';
|
||||
$this->exportDisk->put($file, $content);
|
||||
$this->getEntries()->push($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
114
app/Export/Entry/Entry.php
Normal file
114
app/Export/Entry/Entry.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Entry.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 Crypt;
|
||||
|
||||
/**
|
||||
* To extend the exported object, in case of new features in Firefly III for example,
|
||||
* do the following:
|
||||
*
|
||||
* - Add the field(s) to this class. If you add more than one related field, add a new object.
|
||||
* - Make sure the "fromJournal"-routine fills these fields.
|
||||
* - Add them to the static function that returns its type (key=value. Remember that the only
|
||||
* valid types can be found in config/csv.php (under "roles").
|
||||
*
|
||||
* These new entries should be should be strings and numbers as much as possible.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Class Entry
|
||||
* @SuppressWarnings(PHPMD.LongVariable)
|
||||
*
|
||||
* @package FireflyIII\Export\Entry
|
||||
*/
|
||||
final class Entry
|
||||
{
|
||||
// @formatter:off
|
||||
public $journal_id;
|
||||
public $date;
|
||||
public $description;
|
||||
|
||||
public $currency_code;
|
||||
public $amount;
|
||||
|
||||
public $transaction_type;
|
||||
|
||||
public $source_account_id;
|
||||
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.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public static function fromObject($object): Entry
|
||||
{
|
||||
$entry = new self;
|
||||
$entry->journal_id = $object->transaction_journal_id;
|
||||
$entry->description = self::decrypt($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($object->account_name_encrypted, $object->account_name);
|
||||
$entry->destination_account_id = $object->opposing_account_id;
|
||||
$entry->destination_account_name = self::decrypt($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 ?? '';
|
||||
|
||||
// update description when transaction description is different:
|
||||
if (!is_null($object->description) && $object->description != $entry->description) {
|
||||
$entry->description = $entry->description . ' (' . $object->description . ')';
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $isEncrypted
|
||||
* @param $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function decrypt(int $isEncrypted, $value)
|
||||
{
|
||||
if ($isEncrypted === 1) {
|
||||
return Crypt::decrypt($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
59
app/Export/Exporter/BasicExporter.php
Normal file
59
app/Export/Exporter/BasicExporter.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* BasicExporter.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\Exporter;
|
||||
|
||||
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class BasicExporter
|
||||
*
|
||||
* @package FireflyIII\Export\Exporter
|
||||
*/
|
||||
class BasicExporter
|
||||
{
|
||||
/** @var ExportJob */
|
||||
protected $job;
|
||||
private $entries;
|
||||
|
||||
/**
|
||||
* BasicExporter constructor.
|
||||
*
|
||||
* @param ExportJob $job
|
||||
*/
|
||||
public function __construct(ExportJob $job)
|
||||
{
|
||||
$this->entries = new Collection;
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEntries(): Collection
|
||||
{
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $entries
|
||||
*/
|
||||
public function setEntries(Collection $entries)
|
||||
{
|
||||
$this->entries = $entries;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
86
app/Export/Exporter/CsvExporter.php
Normal file
86
app/Export/Exporter/CsvExporter.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* CsvExporter.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\Exporter;
|
||||
|
||||
use FireflyIII\Export\Entry\Entry;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use League\Csv\Writer;
|
||||
use SplFileObject;
|
||||
|
||||
/**
|
||||
* Class CsvExporter
|
||||
*
|
||||
* @package FireflyIII\Export\Exporter
|
||||
*/
|
||||
class CsvExporter extends BasicExporter implements ExporterInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $fileName;
|
||||
|
||||
/**
|
||||
* CsvExporter constructor.
|
||||
*
|
||||
* @param ExportJob $job
|
||||
*/
|
||||
public function __construct(ExportJob $job)
|
||||
{
|
||||
parent::__construct($job);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileName(): string
|
||||
{
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
// create temporary file:
|
||||
$this->tempFile();
|
||||
|
||||
// necessary for CSV writer:
|
||||
$fullPath = storage_path('export') . DIRECTORY_SEPARATOR . $this->fileName;
|
||||
$writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
|
||||
$rows = [];
|
||||
|
||||
// get field names for header row:
|
||||
$first = $this->getEntries()->first();
|
||||
$headers = array_keys(get_object_vars($first));
|
||||
$rows[] = $headers;
|
||||
|
||||
/** @var Entry $entry */
|
||||
foreach ($this->getEntries() as $entry) {
|
||||
$line = [];
|
||||
foreach ($headers as $header) {
|
||||
$line[] = $entry->$header;
|
||||
}
|
||||
$rows[] = $line;
|
||||
}
|
||||
$writer->insertAll($rows);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private function tempFile()
|
||||
{
|
||||
$this->fileName = $this->job->key . '-records.csv';
|
||||
}
|
||||
}
|
||||
48
app/Export/Exporter/ExporterInterface.php
Normal file
48
app/Export/Exporter/ExporterInterface.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* ExporterInterface.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\Exporter;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface ExporterInterface
|
||||
*
|
||||
* @package FireflyIII\Export\Exporter
|
||||
*/
|
||||
interface ExporterInterface
|
||||
{
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEntries(): Collection;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileName(): string;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function run(): bool;
|
||||
|
||||
/**
|
||||
* @param Collection $entries
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function setEntries(Collection $entries);
|
||||
|
||||
}
|
||||
195
app/Export/Processor.php
Normal file
195
app/Export/Processor.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Processor.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\Exceptions\FireflyException;
|
||||
use FireflyIII\Export\Collector\AttachmentCollector;
|
||||
use FireflyIII\Export\Collector\JournalExportCollector;
|
||||
use FireflyIII\Export\Collector\UploadCollector;
|
||||
use FireflyIII\Export\Entry\Entry;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Storage;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Class Processor
|
||||
*
|
||||
* @package FireflyIII\Export
|
||||
*/
|
||||
class Processor implements ProcessorInterface
|
||||
{
|
||||
|
||||
/** @var Collection */
|
||||
public $accounts;
|
||||
/** @var string */
|
||||
public $exportFormat;
|
||||
/** @var bool */
|
||||
public $includeAttachments;
|
||||
/** @var bool */
|
||||
public $includeOldUploads;
|
||||
/** @var ExportJob */
|
||||
public $job;
|
||||
/** @var array */
|
||||
public $settings;
|
||||
/** @var Collection */
|
||||
private $exportEntries;
|
||||
/** @var Collection */
|
||||
private $files;
|
||||
/** @var Collection */
|
||||
private $journals;
|
||||
|
||||
/**
|
||||
* Processor constructor.
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
public function __construct(array $settings)
|
||||
{
|
||||
// save settings
|
||||
$this->settings = $settings;
|
||||
$this->accounts = $settings['accounts'];
|
||||
$this->exportFormat = $settings['exportFormat'];
|
||||
$this->includeAttachments = $settings['includeAttachments'];
|
||||
$this->includeOldUploads = $settings['includeOldUploads'];
|
||||
$this->job = $settings['job'];
|
||||
$this->journals = new Collection;
|
||||
$this->exportEntries = new Collection;
|
||||
$this->files = new Collection;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectAttachments(): bool
|
||||
{
|
||||
/** @var AttachmentCollector $attachmentCollector */
|
||||
$attachmentCollector = app(AttachmentCollector::class, [$this->job]);
|
||||
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||
$attachmentCollector->run();
|
||||
$this->files = $this->files->merge($attachmentCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectJournals(): bool
|
||||
{
|
||||
/** @var JournalExportCollector $collector */
|
||||
$collector = app(JournalExportCollector::class, [$this->job]);
|
||||
$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 bool
|
||||
*/
|
||||
public function collectOldUploads(): bool
|
||||
{
|
||||
/** @var UploadCollector $uploadCollector */
|
||||
$uploadCollector = app(UploadCollector::class, [$this->job]);
|
||||
$uploadCollector->run();
|
||||
|
||||
$this->files = $this->files->merge($uploadCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function convertJournals(): bool
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($this->journals as $object) {
|
||||
$this->exportEntries->push(Entry::fromObject($object));
|
||||
$count++;
|
||||
}
|
||||
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createZipFile(): bool
|
||||
{
|
||||
$zip = new ZipArchive;
|
||||
$file = $this->job->key . '.zip';
|
||||
$fullPath = storage_path('export') . '/' . $file;
|
||||
|
||||
if ($zip->open($fullPath, ZipArchive::CREATE) !== true) {
|
||||
throw new FireflyException('Cannot store zip file.');
|
||||
}
|
||||
// for each file in the collection, add it to the zip file.
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $entry) {
|
||||
// is part of this job?
|
||||
$zipFileName = str_replace($this->job->key . '-', '', $entry);
|
||||
$zip->addFromString($zipFileName, $disk->get($entry));
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
// delete the files:
|
||||
$this->deleteFiles();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exportJournals(): bool
|
||||
{
|
||||
$exporterClass = config('firefly.export_formats.' . $this->exportFormat);
|
||||
$exporter = app($exporterClass, [$this->job]);
|
||||
$exporter->setEntries($this->exportEntries);
|
||||
$exporter->run();
|
||||
$this->files->push($exporter->getFileName());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getFiles(): Collection
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function deleteFiles()
|
||||
{
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $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;
|
||||
}
|
||||
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;
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
203
app/Handlers/Events/StoredJournalEventHandler.php
Normal file
203
app/Handlers/Events/StoredJournalEventHandler.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* StoredJournalEventHandler.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\StoredTransactionJournal;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class StoredJournalEventHandler
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class StoredJournalEventHandler
|
||||
{
|
||||
/**
|
||||
* This method connects a new transfer to a piggy bank.
|
||||
*
|
||||
* @param StoredTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connectToPiggyBank(StoredTransactionJournal $event): bool
|
||||
{
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->journal;
|
||||
$piggyBankId = $event->piggyBankId;
|
||||
Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId));
|
||||
|
||||
/*
|
||||
* 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')));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get relevant data:
|
||||
*/
|
||||
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||
$amount = $this->getExactAmount($journal, $piggyBank, $repetition);
|
||||
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
|
||||
$repetition->save();
|
||||
|
||||
/** @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method grabs all the users rules and processes them.
|
||||
*
|
||||
* @param StoredTransactionJournal $storedJournalEvent
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function processRules(StoredTransactionJournal $storedJournalEvent): bool
|
||||
{
|
||||
// get all the user's rule groups, with the rules, order by 'order'.
|
||||
$journal = $storedJournalEvent->journal;
|
||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||
//
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
$rules = $group->rules()
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', 1)
|
||||
->get(['rules.*']);
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($journal);
|
||||
|
||||
if ($rule->stop_processing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calls a special bill scanner that will check if the stored journal is part of a bill.
|
||||
*
|
||||
* @param StoredTransactionJournal $storedJournalEvent
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function scanBills(StoredTransactionJournal $storedJournalEvent): bool
|
||||
{
|
||||
$journal = $storedJournalEvent->journal;
|
||||
BillScanner::scan($journal);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
81
app/Handlers/Events/UpdatedJournalEventHandler.php
Normal file
81
app/Handlers/Events/UpdatedJournalEventHandler.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* UpdatedJournalEventHandler.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\UpdatedTransactionJournal;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
|
||||
/**
|
||||
* Class UpdatedJournalEventHandler
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UpdatedJournalEventHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* This method will check all the rules when a journal is updated.
|
||||
*
|
||||
* @param UpdatedTransactionJournal $updatedJournalEvent
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function processRules(UpdatedTransactionJournal $updatedJournalEvent): bool
|
||||
{
|
||||
// get all the user's rule groups, with the rules, order by 'order'.
|
||||
$journal = $updatedJournalEvent->journal;
|
||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||
//
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
$rules = $group->rules()
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'update-journal')
|
||||
->where('rules.active', 1)
|
||||
->get(['rules.*']);
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($journal);
|
||||
|
||||
if ($rule->stop_processing) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calls a special bill scanner that will check if the updated journal is part of a bill.
|
||||
*
|
||||
* @param UpdatedTransactionJournal $updatedJournalEvent
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function scanBills(UpdatedTransactionJournal $updatedJournalEvent): bool
|
||||
{
|
||||
$journal = $updatedJournalEvent->journal;
|
||||
BillScanner::scan($journal);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
130
app/Handlers/Events/UserEventHandler.php
Normal file
130
app/Handlers/Events/UserEventHandler.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* UserEventHandler.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\RegisteredUser;
|
||||
use FireflyIII\Events\RequestedNewPassword;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Mail\Message;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Session;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
* Class UserEventHandler
|
||||
*
|
||||
* This class responds to any events that have anything to do with the User object.
|
||||
*
|
||||
* The method name reflects what is being done. This is in the present tense.
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UserEventHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* This method will bestow upon a user the "owner" role if he is the first user in the system.
|
||||
*
|
||||
* @param RegisteredUser $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function attachUserRole(RegisteredUser $event): bool
|
||||
{
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
// first user ever?
|
||||
if ($repository->count() === 1) {
|
||||
$repository->attachRole($event->user, 'owner');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle user logout events.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function logoutUser(): bool
|
||||
{
|
||||
// dump stuff from the session:
|
||||
Session::forget('twofactor-authenticated');
|
||||
Session::forget('twofactor-authenticated-date');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestedNewPassword $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendNewPassword(RequestedNewPassword $event): bool
|
||||
{
|
||||
$email = $event->user->email;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$token = $event->token;
|
||||
|
||||
$url = route('password.reset', [$token]);
|
||||
|
||||
// send email.
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.password-html', 'emails.password-text'], ['url' => $url, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
||||
$message->to($email, $email)->subject('Your password reset request');
|
||||
}
|
||||
);
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param RegisteredUser $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendRegistrationMail(RegisteredUser $event)
|
||||
{
|
||||
|
||||
$sendMail = env('SEND_REGISTRATION_MAIL', true);
|
||||
if (!$sendMail) {
|
||||
return true;
|
||||
}
|
||||
// get the email address
|
||||
$email = $event->user->email;
|
||||
$address = route('index');
|
||||
$ipAddress = $event->ipAddress;
|
||||
// send email.
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.registered-html', 'emails.registered-text'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
||||
$message->to($email, $email)->subject('Welcome to Firefly III!');
|
||||
}
|
||||
);
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
228
app/Helpers/Attachments/AttachmentHelper.php
Normal file
228
app/Helpers/Attachments/AttachmentHelper.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/**
|
||||
* AttachmentHelper.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\Attachments;
|
||||
|
||||
use Crypt;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\MessageBag;
|
||||
use Storage;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
/**
|
||||
* Class AttachmentHelper
|
||||
*
|
||||
* @package FireflyIII\Helpers\Attachments
|
||||
*/
|
||||
class AttachmentHelper implements AttachmentHelperInterface
|
||||
{
|
||||
|
||||
/** @var MessageBag */
|
||||
public $errors;
|
||||
/** @var MessageBag */
|
||||
public $messages;
|
||||
/** @var array */
|
||||
protected $allowedMimes;
|
||||
/** @var int */
|
||||
protected $maxUploadSize;
|
||||
|
||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||
protected $uploadDisk;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->maxUploadSize = config('firefly.maxUploadSize');
|
||||
$this->allowedMimes = config('firefly.allowedMimes');
|
||||
$this->errors = new MessageBag;
|
||||
$this->messages = new MessageBag;
|
||||
$this->uploadDisk = Storage::disk('upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAttachmentLocation(Attachment $attachment): string
|
||||
{
|
||||
$path = sprintf('%s%sat-%d.data', storage_path('upload'), DIRECTORY_SEPARATOR, intval($attachment->id));
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function getErrors(): MessageBag
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function getMessages(): MessageBag
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Model $model
|
||||
* @param array|null $files
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveAttachmentsForModel(Model $model, array $files = null): bool
|
||||
{
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $entry) {
|
||||
if (!is_null($entry)) {
|
||||
$this->processFile($entry, $model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UploadedFile $file
|
||||
* @param Model $model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasFile(UploadedFile $file, Model $model): bool
|
||||
{
|
||||
$md5 = md5_file($file->getRealPath());
|
||||
$name = $file->getClientOriginalName();
|
||||
$class = get_class($model);
|
||||
$count = auth()->user()->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count();
|
||||
|
||||
if ($count > 0) {
|
||||
$msg = (string)trans('validation.file_already_attached', ['name' => $name]);
|
||||
$this->errors->add('attachments', $msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param UploadedFile $file
|
||||
* @param Model $model
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
protected function processFile(UploadedFile $file, Model $model): Attachment
|
||||
{
|
||||
$validation = $this->validateUpload($file, $model);
|
||||
if ($validation === false) {
|
||||
return new Attachment;
|
||||
}
|
||||
|
||||
$attachment = new Attachment; // create Attachment object.
|
||||
$attachment->user()->associate(auth()->user());
|
||||
$attachment->attachable()->associate($model);
|
||||
$attachment->md5 = md5_file($file->getRealPath());
|
||||
$attachment->filename = $file->getClientOriginalName();
|
||||
$attachment->mime = $file->getMimeType();
|
||||
$attachment->size = $file->getSize();
|
||||
$attachment->uploaded = 0;
|
||||
$attachment->save();
|
||||
|
||||
$fileObject = $file->openFile('r');
|
||||
$fileObject->rewind();
|
||||
$content = $fileObject->fread($file->getSize());
|
||||
$encrypted = Crypt::encrypt($content);
|
||||
|
||||
// store it:
|
||||
$this->uploadDisk->put($attachment->fileName(), $encrypted);
|
||||
|
||||
$attachment->uploaded = 1; // update attachment
|
||||
$attachment->save();
|
||||
|
||||
$name = e($file->getClientOriginalName()); // add message:
|
||||
$msg = (string)trans('validation.file_attached', ['name' => $name]);
|
||||
$this->messages->add('attachments', $msg);
|
||||
|
||||
// return it.
|
||||
return $attachment;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UploadedFile $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function validMime(UploadedFile $file): bool
|
||||
{
|
||||
$mime = e($file->getMimeType());
|
||||
$name = e($file->getClientOriginalName());
|
||||
|
||||
if (!in_array($mime, $this->allowedMimes)) {
|
||||
$msg = (string)trans('validation.file_invalid_mime', ['name' => $name, 'mime' => $mime]);
|
||||
$this->errors->add('attachments', $msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UploadedFile $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function validSize(UploadedFile $file): bool
|
||||
{
|
||||
$size = $file->getSize();
|
||||
$name = e($file->getClientOriginalName());
|
||||
if ($size > $this->maxUploadSize) {
|
||||
$msg = (string)trans('validation.file_too_large', ['name' => $name]);
|
||||
$this->errors->add('attachments', $msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UploadedFile $file
|
||||
* @param Model $model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateUpload(UploadedFile $file, Model $model): bool
|
||||
{
|
||||
if (!$this->validMime($file)) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->validSize($file)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->hasFile($file, $model)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
51
app/Helpers/Attachments/AttachmentHelperInterface.php
Normal file
51
app/Helpers/Attachments/AttachmentHelperInterface.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* AttachmentHelperInterface.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\Attachments;
|
||||
|
||||
use FireflyIII\Models\Attachment;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\MessageBag;
|
||||
|
||||
/**
|
||||
* Interface AttachmentHelperInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Attachments
|
||||
*/
|
||||
interface AttachmentHelperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAttachmentLocation(Attachment $attachment): string;
|
||||
|
||||
/**
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function getErrors(): MessageBag;
|
||||
|
||||
/**
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function getMessages(): MessageBag;
|
||||
|
||||
/**
|
||||
* @param Model $model
|
||||
*
|
||||
* @return 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;
|
||||
|
||||
}
|
||||
107
app/Helpers/Collection/Account.php
Normal file
107
app/Helpers/Collection/Account.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
81
app/Helpers/Collection/Balance.php
Normal file
81
app/Helpers/Collection/Balance.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Balance.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 Balance
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class Balance
|
||||
{
|
||||
|
||||
/** @var BalanceHeader */
|
||||
protected $balanceHeader;
|
||||
|
||||
/** @var Collection */
|
||||
protected $balanceLines;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->balanceLines = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BalanceLine $line
|
||||
*/
|
||||
public function addBalanceLine(BalanceLine $line)
|
||||
{
|
||||
$this->balanceLines->push($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BalanceHeader
|
||||
*/
|
||||
public function getBalanceHeader(): BalanceHeader
|
||||
{
|
||||
return $this->balanceHeader ?? new BalanceHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BalanceHeader $balanceHeader
|
||||
*/
|
||||
public function setBalanceHeader(BalanceHeader $balanceHeader)
|
||||
{
|
||||
$this->balanceHeader = $balanceHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getBalanceLines(): Collection
|
||||
{
|
||||
return $this->balanceLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $balanceLines
|
||||
*/
|
||||
public function setBalanceLines(Collection $balanceLines)
|
||||
{
|
||||
$this->balanceLines = $balanceLines;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
83
app/Helpers/Collection/BalanceEntry.php
Normal file
83
app/Helpers/Collection/BalanceEntry.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* BalanceEntry.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 FireflyIII\Models\Account as AccountModel;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class BalanceEntry
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class BalanceEntry
|
||||
{
|
||||
|
||||
|
||||
/** @var AccountModel */
|
||||
protected $account;
|
||||
/** @var string */
|
||||
protected $left = '0';
|
||||
/** @var string */
|
||||
protected $spent = '0';
|
||||
|
||||
/**
|
||||
* @return AccountModel
|
||||
*/
|
||||
public function getAccount(): AccountModel
|
||||
{
|
||||
return $this->account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountModel $account
|
||||
*/
|
||||
public function setAccount(AccountModel $account)
|
||||
{
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLeft(): string
|
||||
{
|
||||
return $this->left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $left
|
||||
*/
|
||||
public function setLeft(string $left)
|
||||
{
|
||||
$this->left = $left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSpent(): string
|
||||
{
|
||||
return $this->spent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $spent
|
||||
*/
|
||||
public function setSpent(string $spent)
|
||||
{
|
||||
$this->spent = $spent;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
55
app/Helpers/Collection/BalanceHeader.php
Normal file
55
app/Helpers/Collection/BalanceHeader.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* BalanceHeader.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 FireflyIII\Models\Account as AccountModel;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class BalanceHeader
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class BalanceHeader
|
||||
{
|
||||
|
||||
/** @var Collection */
|
||||
protected $accounts;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->accounts = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountModel $account
|
||||
*/
|
||||
public function addAccount(AccountModel $account)
|
||||
{
|
||||
$this->accounts->push($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAccounts(): Collection
|
||||
{
|
||||
return $this->accounts;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
180
app/Helpers/Collection/BalanceLine.php
Normal file
180
app/Helpers/Collection/BalanceLine.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
/**
|
||||
* BalanceLine.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 Carbon\Carbon;
|
||||
use FireflyIII\Models\Budget as BudgetModel;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class BalanceLine
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class BalanceLine
|
||||
{
|
||||
|
||||
const ROLE_DEFAULTROLE = 1;
|
||||
const ROLE_TAGROLE = 2;
|
||||
const ROLE_DIFFROLE = 3;
|
||||
|
||||
/** @var Collection */
|
||||
protected $balanceEntries;
|
||||
|
||||
/** @var BudgetModel */
|
||||
protected $budget;
|
||||
/** @var BudgetLimit */
|
||||
protected $budgetLimit;
|
||||
/** @var int */
|
||||
protected $role = self::ROLE_DEFAULTROLE;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->balanceEntries = new Collection;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BalanceEntry $balanceEntry
|
||||
*/
|
||||
public function addBalanceEntry(BalanceEntry $balanceEntry)
|
||||
{
|
||||
$this->balanceEntries->push($balanceEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getBalanceEntries(): Collection
|
||||
{
|
||||
return $this->balanceEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $balanceEntries
|
||||
*/
|
||||
public function setBalanceEntries(Collection $balanceEntries)
|
||||
{
|
||||
$this->balanceEntries = $balanceEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BudgetModel
|
||||
*/
|
||||
public function getBudget(): BudgetModel
|
||||
{
|
||||
return $this->budget ?? new BudgetModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetModel $budget
|
||||
*/
|
||||
public function setBudget(BudgetModel $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
|
||||
*/
|
||||
public function getEndDate()
|
||||
{
|
||||
return $this->budgetLimit->end_date ?? new Carbon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRole(): int
|
||||
{
|
||||
return $this->role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $role
|
||||
*/
|
||||
public function setRole(int $role)
|
||||
{
|
||||
$this->role = $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Carbon
|
||||
*/
|
||||
public function getStartDate()
|
||||
{
|
||||
return $this->budgetLimit->start_date ?? new Carbon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
if ($this->getBudget() instanceof BudgetModel && !is_null($this->getBudget()->id)) {
|
||||
return $this->getBudget()->name;
|
||||
}
|
||||
if ($this->getRole() == self::ROLE_DEFAULTROLE) {
|
||||
return strval(trans('firefly.no_budget'));
|
||||
}
|
||||
if ($this->getRole() == self::ROLE_TAGROLE) {
|
||||
return strval(trans('firefly.coveredWithTags'));
|
||||
}
|
||||
if ($this->getRole() == self::ROLE_DIFFROLE) {
|
||||
return strval(trans('firefly.leftUnbalanced'));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* If a BalanceLine has a budget/repetition, each BalanceEntry in this BalanceLine
|
||||
* should have a "spent" value, which is the amount of money that has been spent
|
||||
* on the given budget/repetition. If you subtract all those amounts from the budget/repetition's
|
||||
* total amount, this is returned:
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function leftOfRepetition(): string
|
||||
{
|
||||
$start = $this->budgetLimit->amount ?? '0';
|
||||
/** @var BalanceEntry $balanceEntry */
|
||||
foreach ($this->getBalanceEntries() as $balanceEntry) {
|
||||
$start = bcadd($balanceEntry->getSpent(), $start);
|
||||
}
|
||||
|
||||
return $start;
|
||||
}
|
||||
}
|
||||
125
app/Helpers/Collection/Bill.php
Normal file
125
app/Helpers/Collection/Bill.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* Bill.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 Carbon\Carbon;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class Bill
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class Bill
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
*/
|
||||
private $bills;
|
||||
/** @var Carbon */
|
||||
private $endDate;
|
||||
/** @var Carbon */
|
||||
private $startDate;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->bills = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillLine $bill
|
||||
*/
|
||||
public function addBill(BillLine $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
|
||||
*/
|
||||
public function getBills(): Collection
|
||||
{
|
||||
$set = $this->bills->sortBy(
|
||||
function (BillLine $bill) {
|
||||
$active = intval($bill->getBill()->active) == 0 ? 1 : 0;
|
||||
$name = $bill->getBill()->name;
|
||||
|
||||
return $active . $name;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $endDate
|
||||
*/
|
||||
public function setEndDate(Carbon $endDate)
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $startDate
|
||||
*/
|
||||
public function setStartDate(Carbon $startDate)
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
}
|
||||
|
||||
}
|
||||
171
app/Helpers/Collection/BillLine.php
Normal file
171
app/Helpers/Collection/BillLine.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* BillLine.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 Carbon\Carbon;
|
||||
use FireflyIII\Models\Bill as BillModel;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class BillLine
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class BillLine
|
||||
{
|
||||
|
||||
/** @var string */
|
||||
protected $amount;
|
||||
/** @var BillModel */
|
||||
protected $bill;
|
||||
/** @var bool */
|
||||
protected $hit;
|
||||
/** @var string */
|
||||
protected $max;
|
||||
/** @var string */
|
||||
protected $min;
|
||||
/** @var Carbon */
|
||||
private $lastHitDate;
|
||||
/** @var int */
|
||||
private $transactionJournalId;
|
||||
|
||||
/**
|
||||
* BillLine constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->lastHitDate = new Carbon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAmount(): string
|
||||
{
|
||||
return $this->amount ?? '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $amount
|
||||
*/
|
||||
public function setAmount(string $amount)
|
||||
{
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BillModel
|
||||
*/
|
||||
public function getBill(): BillModel
|
||||
{
|
||||
return $this->bill;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillModel $bill
|
||||
*/
|
||||
public function setBill(BillModel $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
|
||||
*/
|
||||
public function getMax(): string
|
||||
{
|
||||
return $this->max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $max
|
||||
*/
|
||||
public function setMax(string $max)
|
||||
{
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMin(): string
|
||||
{
|
||||
return $this->min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $min
|
||||
*/
|
||||
public function setMin(string $min)
|
||||
{
|
||||
$this->min = $min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTransactionJournalId(): int
|
||||
{
|
||||
return $this->transactionJournalId ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $transactionJournalId
|
||||
*/
|
||||
public function setTransactionJournalId(int $transactionJournalId)
|
||||
{
|
||||
$this->transactionJournalId = $transactionJournalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isActive(): bool
|
||||
{
|
||||
return intval($this->bill->active) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isHit(): bool
|
||||
{
|
||||
return $this->hit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $hit
|
||||
*/
|
||||
public function setHit(bool $hit)
|
||||
{
|
||||
$this->hit = $hit;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
193
app/Helpers/Collection/Budget.php
Normal file
193
app/Helpers/Collection/Budget.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* Budget.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 Budget
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class Budget
|
||||
{
|
||||
/** @var Collection */
|
||||
protected $budgetLines;
|
||||
/** @var string */
|
||||
protected $budgeted = '0';
|
||||
/** @var string */
|
||||
protected $left = '0';
|
||||
/** @var string */
|
||||
protected $overspent = '0';
|
||||
/** @var string */
|
||||
protected $spent = '0';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->budgetLines = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetLine $budgetLine
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function addBudgetLine(BudgetLine $budgetLine): Budget
|
||||
{
|
||||
$this->budgetLines->push($budgetLine);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $add
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function addBudgeted(string $add): Budget
|
||||
{
|
||||
$this->budgeted = bcadd($this->budgeted, $add);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $add
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function addLeft(string $add): Budget
|
||||
{
|
||||
$this->left = bcadd($this->left, $add);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $add
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function addOverspent(string $add): Budget
|
||||
{
|
||||
$this->overspent = bcadd($this->overspent, $add);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $add
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function addSpent(string $add): Budget
|
||||
{
|
||||
$this->spent = bcadd($this->spent, $add);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getBudgetLines(): Collection
|
||||
{
|
||||
return $this->budgetLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBudgeted(): string
|
||||
{
|
||||
return $this->budgeted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $budgeted
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function setBudgeted(string $budgeted): Budget
|
||||
{
|
||||
$this->budgeted = $budgeted;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLeft(): string
|
||||
{
|
||||
return $this->left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $left
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function setLeft(string $left): Budget
|
||||
{
|
||||
$this->left = $left;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOverspent(): string
|
||||
{
|
||||
return $this->overspent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $overspent
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function setOverspent(string $overspent): Budget
|
||||
{
|
||||
$this->overspent = $overspent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSpent(): string
|
||||
{
|
||||
return $this->spent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $spent
|
||||
*
|
||||
* @return Budget
|
||||
*/
|
||||
public function setSpent(string $spent): Budget
|
||||
{
|
||||
$this->spent = $spent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
161
app/Helpers/Collection/BudgetLine.php
Normal file
161
app/Helpers/Collection/BudgetLine.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/**
|
||||
* BudgetLine.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 FireflyIII\Models\Budget as BudgetModel;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class BudgetLine
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class BudgetLine
|
||||
{
|
||||
|
||||
/** @var BudgetModel */
|
||||
protected $budget;
|
||||
/** @var BudgetLimit */
|
||||
protected $budgetLimit;
|
||||
/** @var string */
|
||||
protected $budgeted = '0';
|
||||
/** @var string */
|
||||
protected $left = '0';
|
||||
/** @var string */
|
||||
protected $overspent = '0';
|
||||
/** @var string */
|
||||
protected $spent = '0';
|
||||
|
||||
/**
|
||||
* @return BudgetModel
|
||||
*/
|
||||
public function getBudget(): BudgetModel
|
||||
{
|
||||
return $this->budget ?? new BudgetModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetModel $budget
|
||||
*
|
||||
* @return BudgetLine
|
||||
*/
|
||||
public function setBudget(BudgetModel $budget): BudgetLine
|
||||
{
|
||||
$this->budget = $budget;
|
||||
|
||||
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
|
||||
*/
|
||||
public function getBudgeted(): string
|
||||
{
|
||||
return $this->budgeted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $budgeted
|
||||
*
|
||||
* @return BudgetLine
|
||||
*/
|
||||
public function setBudgeted(string $budgeted): BudgetLine
|
||||
{
|
||||
$this->budgeted = $budgeted;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLeft(): string
|
||||
{
|
||||
return $this->left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $left
|
||||
*
|
||||
* @return BudgetLine
|
||||
*/
|
||||
public function setLeft(string $left): BudgetLine
|
||||
{
|
||||
$this->left = $left;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOverspent(): string
|
||||
{
|
||||
return $this->overspent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $overspent
|
||||
*
|
||||
* @return BudgetLine
|
||||
*/
|
||||
public function setOverspent(string $overspent): BudgetLine
|
||||
{
|
||||
$this->overspent = $overspent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSpent(): string
|
||||
{
|
||||
return $this->spent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $spent
|
||||
*
|
||||
* @return BudgetLine
|
||||
*/
|
||||
public function setSpent(string $spent): BudgetLine
|
||||
{
|
||||
$this->spent = $spent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
85
app/Helpers/Collection/Category.php
Normal file
85
app/Helpers/Collection/Category.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Category.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 FireflyIII\Models\Category as CategoryModel;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Class Category
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collection
|
||||
*/
|
||||
class Category
|
||||
{
|
||||
|
||||
/** @var Collection */
|
||||
protected $categories;
|
||||
/** @var string */
|
||||
protected $total = '0';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->categories = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CategoryModel $category
|
||||
*/
|
||||
public function addCategory(CategoryModel $category)
|
||||
{
|
||||
// spent is minus zero for an expense report:
|
||||
if ($category->spent < 0) {
|
||||
$this->categories->push($category);
|
||||
$this->addTotal($category->spent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $add
|
||||
*/
|
||||
public function addTotal(string $add)
|
||||
{
|
||||
$this->total = bcadd($this->total, $add);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getCategories(): Collection
|
||||
{
|
||||
$set = $this->categories->sortBy(
|
||||
function (CategoryModel $category) {
|
||||
return $category->spent;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTotal(): string
|
||||
{
|
||||
return $this->total;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
754
app/Helpers/Collector/JournalCollector.php
Normal file
754
app/Helpers/Collector/JournalCollector.php
Normal file
@@ -0,0 +1,754 @@
|
||||
<?php
|
||||
/**
|
||||
* JournalCollector.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collector;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crypt;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Maybe this is a good idea after all...
|
||||
*
|
||||
* Class JournalCollector
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collector
|
||||
*/
|
||||
class JournalCollector implements JournalCollectorInterface
|
||||
{
|
||||
|
||||
/** @var array */
|
||||
private $accountIds = [];
|
||||
/** @var int */
|
||||
private $count = 0;
|
||||
/** @var array */
|
||||
private $fields
|
||||
= [
|
||||
'transaction_journals.id as journal_id',
|
||||
'transaction_journals.description',
|
||||
'transaction_journals.date',
|
||||
'transaction_journals.encrypted',
|
||||
//'transaction_journals.transaction_currency_id',
|
||||
'transaction_currencies.code as transaction_currency_code',
|
||||
//'transaction_currencies.symbol as transaction_currency_symbol',
|
||||
'transaction_types.type as transaction_type_type',
|
||||
'transaction_journals.bill_id',
|
||||
'bills.name as bill_name',
|
||||
'bills.name_encrypted as bill_name_encrypted',
|
||||
'transactions.id as id',
|
||||
'transactions.amount as transaction_amount',
|
||||
'transactions.description as transaction_description',
|
||||
'transactions.account_id',
|
||||
'transactions.identifier',
|
||||
'transactions.transaction_journal_id',
|
||||
'accounts.name as account_name',
|
||||
'accounts.encrypted as account_encrypted',
|
||||
'account_types.type as account_type',
|
||||
];
|
||||
/** @var bool */
|
||||
private $filterInternalTransfers;
|
||||
/** @var bool */
|
||||
private $filterTransfers = false;
|
||||
/** @var bool */
|
||||
private $joinedBudget = false;
|
||||
/** @var bool */
|
||||
private $joinedCategory = false;
|
||||
/** @var bool */
|
||||
private $joinedOpposing = false;
|
||||
/** @var bool */
|
||||
private $joinedTag = false;
|
||||
/** @var int */
|
||||
private $limit;
|
||||
/** @var int */
|
||||
private $offset;
|
||||
/** @var int */
|
||||
private $page = 1;
|
||||
/** @var EloquentBuilder */
|
||||
private $query;
|
||||
/** @var bool */
|
||||
private $run = false;
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* JournalCollector constructor.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->query = $this->startQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
if ($this->run === true) {
|
||||
throw new FireflyException('Cannot count after run in JournalCollector.');
|
||||
}
|
||||
|
||||
$countQuery = clone $this->query;
|
||||
|
||||
// dont need some fields:
|
||||
$countQuery->getQuery()->limit = null;
|
||||
$countQuery->getQuery()->offset = null;
|
||||
$countQuery->getQuery()->unionLimit = null;
|
||||
$countQuery->getQuery()->groups = null;
|
||||
$countQuery->getQuery()->orders = null;
|
||||
$countQuery->groupBy('accounts.user_id');
|
||||
$this->count = $countQuery->count();
|
||||
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function disableFilter(): JournalCollectorInterface
|
||||
{
|
||||
$this->filterTransfers = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function disableInternalFilter(): JournalCollectorInterface
|
||||
{
|
||||
$this->filterInternalTransfers = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function enableInternalFilter(): JournalCollectorInterface
|
||||
{
|
||||
$this->filterInternalTransfers = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getJournals(): Collection
|
||||
{
|
||||
$this->run = true;
|
||||
$set = $this->query->get(array_values($this->fields));
|
||||
Log::debug(sprintf('Count of set is %d', $set->count()));
|
||||
$set = $this->filterTransfers($set);
|
||||
Log::debug(sprintf('Count of set after filterTransfers() is %d', $set->count()));
|
||||
|
||||
// possibly filter "internal" transfers:
|
||||
$set = $this->filterInternalTransfers($set);
|
||||
Log::debug(sprintf('Count of set after filterInternalTransfers() is %d', $set->count()));
|
||||
|
||||
|
||||
// loop for decryption.
|
||||
$set->each(
|
||||
function (Transaction $transaction) {
|
||||
$transaction->date = new Carbon($transaction->date);
|
||||
$transaction->description = $transaction->encrypted ? Crypt::decrypt($transaction->description) : $transaction->description;
|
||||
|
||||
if (!is_null($transaction->bill_name)) {
|
||||
$transaction->bill_name = $transaction->bill_name_encrypted ? Crypt::decrypt($transaction->bill_name) : $transaction->bill_name;
|
||||
}
|
||||
|
||||
try {
|
||||
$transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name);
|
||||
} catch (DecryptException $e) {
|
||||
// if this fails its already decrypted.
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LengthAwarePaginator
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getPaginatedJournals(): LengthAwarePaginator
|
||||
{
|
||||
if ($this->run === true) {
|
||||
throw new FireflyException('Cannot getPaginatedJournals after run in JournalCollector.');
|
||||
}
|
||||
$this->count();
|
||||
$set = $this->getJournals();
|
||||
$journals = new LengthAwarePaginator($set, $this->count, $this->limit, $this->page);
|
||||
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setAccounts(Collection $accounts): JournalCollectorInterface
|
||||
{
|
||||
if ($accounts->count() > 0) {
|
||||
$accountIds = $accounts->pluck('id')->toArray();
|
||||
$this->query->whereIn('transactions.account_id', $accountIds);
|
||||
Log::debug(sprintf('setAccounts: %s', join(', ', $accountIds)));
|
||||
$this->accountIds = $accountIds;
|
||||
}
|
||||
|
||||
if ($accounts->count() > 1) {
|
||||
$this->filterTransfers = true;
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setAllAssetAccounts(): JournalCollectorInterface
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class, [$this->user]);
|
||||
$accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
||||
if ($accounts->count() > 0) {
|
||||
$accountIds = $accounts->pluck('id')->toArray();
|
||||
$this->query->whereIn('transactions.account_id', $accountIds);
|
||||
$this->accountIds = $accountIds;
|
||||
}
|
||||
|
||||
if ($accounts->count() > 1) {
|
||||
$this->filterTransfers = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $bills
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setBills(Collection $bills): JournalCollectorInterface
|
||||
{
|
||||
if ($bills->count() > 0) {
|
||||
$billIds = $bills->pluck('id')->toArray();
|
||||
$this->query->whereIn('transaction_journals.bill_id', $billIds);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setBudget(Budget $budget): JournalCollectorInterface
|
||||
{
|
||||
$this->joinBudgetTables();
|
||||
|
||||
$this->query->where(
|
||||
function (EloquentBuilder $q) use ($budget) {
|
||||
$q->where('budget_transaction.budget_id', $budget->id);
|
||||
$q->orWhere('budget_transaction_journal.budget_id', $budget->id);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $budgets
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setBudgets(Collection $budgets): JournalCollectorInterface
|
||||
{
|
||||
$budgetIds = $budgets->pluck('id')->toArray();
|
||||
if (count($budgetIds) === 0) {
|
||||
return $this;
|
||||
}
|
||||
$this->joinBudgetTables();
|
||||
|
||||
$this->query->where(
|
||||
function (EloquentBuilder $q) use ($budgetIds) {
|
||||
$q->whereIn('budget_transaction.budget_id', $budgetIds);
|
||||
$q->orWhereIn('budget_transaction_journal.budget_id', $budgetIds);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $categories
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setCategories(Collection $categories): JournalCollectorInterface
|
||||
{
|
||||
$categoryIds = $categories->pluck('id')->toArray();
|
||||
if (count($categoryIds) === 0) {
|
||||
return $this;
|
||||
}
|
||||
$this->joinCategoryTables();
|
||||
|
||||
$this->query->where(
|
||||
function (EloquentBuilder $q) use ($categoryIds) {
|
||||
$q->whereIn('category_transaction.category_id', $categoryIds);
|
||||
$q->orWhereIn('category_transaction_journal.category_id', $categoryIds);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Category $category
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setCategory(Category $category): JournalCollectorInterface
|
||||
{
|
||||
$this->joinCategoryTables();
|
||||
|
||||
$this->query->where(
|
||||
function (EloquentBuilder $q) use ($category) {
|
||||
$q->where('category_transaction.category_id', $category->id);
|
||||
$q->orWhere('category_transaction_journal.category_id', $category->id);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setLimit(int $limit): JournalCollectorInterface
|
||||
{
|
||||
$this->limit = $limit;
|
||||
$this->query->limit($limit);
|
||||
Log::debug(sprintf('Set limit to %d', $limit));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setOffset(int $offset): JournalCollectorInterface
|
||||
{
|
||||
$this->offset = $offset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setPage(int $page): JournalCollectorInterface
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
if ($page > 0) {
|
||||
$page--;
|
||||
}
|
||||
Log::debug(sprintf('Page is %d', $page));
|
||||
|
||||
if (!is_null($this->limit)) {
|
||||
$offset = ($this->limit * $page);
|
||||
$this->offset = $offset;
|
||||
$this->query->skip($offset);
|
||||
Log::debug(sprintf('Changed offset to %d', $offset));
|
||||
}
|
||||
if (is_null($this->limit)) {
|
||||
Log::debug('The limit is zero, cannot set the page.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setRange(Carbon $start, Carbon $end): JournalCollectorInterface
|
||||
{
|
||||
if ($start <= $end) {
|
||||
$this->query->where('transaction_journals.date', '>=', $start->format('Y-m-d'));
|
||||
$this->query->where('transaction_journals.date', '<=', $end->format('Y-m-d'));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setTag(Tag $tag): JournalCollectorInterface
|
||||
{
|
||||
$this->joinTagTables();
|
||||
$this->query->where('tag_transaction_journal.tag_id', $tag->id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $types
|
||||
*
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function setTypes(array $types): JournalCollectorInterface
|
||||
{
|
||||
if (count($types) > 0) {
|
||||
Log::debug('Set query collector types', $types);
|
||||
$this->query->whereIn('transaction_types.type', $types);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function withBudgetInformation(): JournalCollectorInterface
|
||||
{
|
||||
$this->joinBudgetTables();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function withCategoryInformation(): JournalCollectorInterface
|
||||
{
|
||||
|
||||
$this->joinCategoryTables();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function withOpposingAccount(): JournalCollectorInterface
|
||||
{
|
||||
$this->joinOpposingTables();
|
||||
|
||||
$accountIds = $this->accountIds;
|
||||
$this->query->where(
|
||||
function (EloquentBuilder $q1) use ($accountIds) {
|
||||
// set 1:
|
||||
// where source is in the set of $accounts
|
||||
// but destination is not.
|
||||
$q1->where(
|
||||
function (EloquentBuilder $q2) use ($accountIds) {
|
||||
// transactions.account_id in set
|
||||
$q2->whereIn('transactions.account_id', $accountIds);
|
||||
// opposing.account_id not in set
|
||||
$q2->whereNotIn('opposing.account_id', $accountIds);
|
||||
|
||||
}
|
||||
);
|
||||
// set 1:
|
||||
// where source is not in the set of $accounts
|
||||
// but destination is.
|
||||
$q1->orWhere(
|
||||
function (EloquentBuilder $q3) use ($accountIds) {
|
||||
// transactions.account_id not in set
|
||||
$q3->whereNotIn('transactions.account_id', $accountIds);
|
||||
// B in set
|
||||
// opposing.account_id not in set
|
||||
$q3->whereIn('opposing.account_id', $accountIds);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function withoutBudget(): JournalCollectorInterface
|
||||
{
|
||||
$this->joinBudgetTables();
|
||||
|
||||
$this->query->where(
|
||||
function (EloquentBuilder $q) {
|
||||
$q->whereNull('budget_transaction.budget_id');
|
||||
$q->whereNull('budget_transaction_journal.budget_id');
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JournalCollectorInterface
|
||||
*/
|
||||
public function withoutCategory(): JournalCollectorInterface
|
||||
{
|
||||
$this->joinCategoryTables();
|
||||
|
||||
$this->query->where(
|
||||
function (EloquentBuilder $q) {
|
||||
$q->whereNull('category_transaction.category_id');
|
||||
$q->whereNull('category_transaction_journal.category_id');
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $set
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function filterInternalTransfers(Collection $set): Collection
|
||||
{
|
||||
if ($this->filterInternalTransfers === false) {
|
||||
Log::debug('Did NO filtering for internal transfers on given set.');
|
||||
|
||||
return $set;
|
||||
}
|
||||
if ($this->joinedOpposing === false) {
|
||||
Log::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;
|
||||
}
|
||||
87
app/Helpers/FiscalHelper.php
Normal file
87
app/Helpers/FiscalHelper.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* FiscalHelper.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;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
* Class FiscalHelper
|
||||
*
|
||||
* @package FireflyIII\Helpers
|
||||
*/
|
||||
class FiscalHelper implements FiscalHelperInterface
|
||||
{
|
||||
|
||||
/** @var bool */
|
||||
protected $useCustomFiscalYear;
|
||||
|
||||
/**
|
||||
* FiscalHelper constructor.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->useCustomFiscalYear = Preferences::get('customFiscalYear', false)->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Carbon date object
|
||||
*/
|
||||
public function endOfFiscalYear(Carbon $date): Carbon
|
||||
{
|
||||
// get start of fiscal year for passed date
|
||||
$endDate = $this->startOfFiscalYear($date);
|
||||
if ($this->useCustomFiscalYear === true) {
|
||||
// add 1 year and sub 1 day
|
||||
$endDate->addYear();
|
||||
$endDate->subDay();
|
||||
|
||||
return $endDate;
|
||||
}
|
||||
$endDate->endOfYear();
|
||||
|
||||
|
||||
return $endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Carbon date object
|
||||
*/
|
||||
public function startOfFiscalYear(Carbon $date): Carbon
|
||||
{
|
||||
// get start mm-dd. Then create a start date in the year passed.
|
||||
$startDate = clone $date;
|
||||
if ($this->useCustomFiscalYear === true) {
|
||||
$prefStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
|
||||
list($mth, $day) = explode('-', $prefStartStr);
|
||||
$startDate->month(intval($mth))->day(intval($day));
|
||||
|
||||
// if start date is after passed date, sub 1 year.
|
||||
if ($startDate > $date) {
|
||||
$startDate->subYear();
|
||||
}
|
||||
|
||||
return $startDate;
|
||||
}
|
||||
$startDate->startOfYear();
|
||||
|
||||
return $startDate;
|
||||
}
|
||||
}
|
||||
46
app/Helpers/FiscalHelperInterface.php
Normal file
46
app/Helpers/FiscalHelperInterface.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* FiscalHelperInterface.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;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Interface FiscalHelperInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers
|
||||
*/
|
||||
interface FiscalHelperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* This method produces a clone of the Carbon date object passed, checks preferences
|
||||
* and calculates the last day of the fiscal year.
|
||||
*
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Carbon date object
|
||||
*/
|
||||
public function endOfFiscalYear(Carbon $date): Carbon;
|
||||
|
||||
/**
|
||||
* This method produces a clone of the Carbon date object passed, checks preferences
|
||||
* and calculates the first day of the fiscal year.
|
||||
*
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Carbon date object
|
||||
*/
|
||||
public function startOfFiscalYear(Carbon $date): Carbon;
|
||||
|
||||
}
|
||||
130
app/Helpers/Help/Help.php
Normal file
130
app/Helpers/Help/Help.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Help.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\Help;
|
||||
|
||||
use Cache;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use Log;
|
||||
use Requests;
|
||||
use Requests_Exception;
|
||||
use Route;
|
||||
|
||||
/**
|
||||
* Class Help
|
||||
*
|
||||
* @package FireflyIII\Helpers\Help
|
||||
*/
|
||||
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 $language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFromCache(string $route, string $language): string
|
||||
{
|
||||
$line = sprintf('help.%s.%s', $route, $language);
|
||||
|
||||
return Cache::get($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $language
|
||||
* @param string $route
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFromGithub(string $language, string $route): string
|
||||
{
|
||||
|
||||
$uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route);
|
||||
Log::debug(sprintf('Trying to get %s...', $uri));
|
||||
$opt = ['useragent' => $this->userAgent];
|
||||
$content = '';
|
||||
try {
|
||||
$result = Requests::get($uri, [], $opt);
|
||||
} catch (Requests_Exception $e) {
|
||||
Log::error($e);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
Log::debug(sprintf('Status code is %d', $result->status_code));
|
||||
|
||||
if ($result->status_code === 200) {
|
||||
$content = trim($result->body);
|
||||
}
|
||||
if (strlen($content) > 0) {
|
||||
Log::debug('Content is longer than zero. Expect something.');
|
||||
$converter = new CommonMarkConverter();
|
||||
$content = $converter->convertToHtml($content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $route
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRoute(string $route): bool
|
||||
{
|
||||
return Route::has($route);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $language
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function inCache(string $route, string $language): bool
|
||||
{
|
||||
$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 $language
|
||||
* @param string $content
|
||||
*
|
||||
*/
|
||||
public function putInCache(string $route, string $language, string $content)
|
||||
{
|
||||
$key = sprintf('help.%s.%s', $route, $language);
|
||||
if (strlen($content) > 0) {
|
||||
Log::debug(sprintf('Will store entry in cache: %s', $key));
|
||||
Cache::put($key, $content, 10080); // a week.
|
||||
return;
|
||||
}
|
||||
Log::info(sprintf('Will not cache %s because content is empty.', $key));
|
||||
}
|
||||
}
|
||||
60
app/Helpers/Help/HelpInterface.php
Normal file
60
app/Helpers/Help/HelpInterface.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* HelpInterface.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\Help;
|
||||
|
||||
/**
|
||||
* Interface HelpInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Help
|
||||
*/
|
||||
interface HelpInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFromCache(string $route, string $language): string;
|
||||
|
||||
/**
|
||||
* @param string $language
|
||||
* @param string $route
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFromGithub(string $language, string $route): string;
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRoute(string $route): bool;
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $language
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function inCache(string $route, string $language): bool;
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $language
|
||||
* @param string $content
|
||||
*/
|
||||
public function putInCache(string $route, string $language, string $content);
|
||||
}
|
||||
294
app/Helpers/Report/BalanceReportHelper.php
Normal file
294
app/Helpers/Report/BalanceReportHelper.php
Normal file
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
/**
|
||||
* BalanceReportHelper.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\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Helpers\Collection\Balance;
|
||||
use FireflyIII\Helpers\Collection\BalanceEntry;
|
||||
use FireflyIII\Helpers\Collection\BalanceHeader;
|
||||
use FireflyIII\Helpers\Collection\BalanceLine;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class BalanceReportHelper
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // I can't really help it.
|
||||
* @package FireflyIII\Helpers\Report
|
||||
*/
|
||||
class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
{
|
||||
|
||||
/** @var BudgetRepositoryInterface */
|
||||
protected $budgetRepository;
|
||||
|
||||
/**
|
||||
* ReportHelper constructor.
|
||||
*
|
||||
*
|
||||
* @param BudgetRepositoryInterface $budgetRepository
|
||||
*/
|
||||
public function __construct(BudgetRepositoryInterface $budgetRepository)
|
||||
{
|
||||
$this->budgetRepository = $budgetRepository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Balance
|
||||
*/
|
||||
public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): Balance
|
||||
{
|
||||
Log::debug('Start of balance report');
|
||||
$balance = new Balance;
|
||||
$header = new BalanceHeader;
|
||||
$budgetLimits = $this->budgetRepository->getAllBudgetLimits($start, $end);
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Add account %s to headers.', $account->name));
|
||||
$header->addAccount($account);
|
||||
}
|
||||
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($budgetLimits as $budgetLimit) {
|
||||
$line = $this->createBalanceLine($budgetLimit, $accounts);
|
||||
$balance->addBalanceLine($line);
|
||||
}
|
||||
Log::debug('Create rest of the things.');
|
||||
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
|
||||
$coveredByTagLine = $this->createTagsBalanceLine($accounts, $start, $end);
|
||||
$leftUnbalancedLine = $this->createLeftUnbalancedLine($noBudgetLine, $coveredByTagLine);
|
||||
|
||||
$balance->addBalanceLine($noBudgetLine);
|
||||
$balance->addBalanceLine($coveredByTagLine);
|
||||
$balance->addBalanceLine($leftUnbalancedLine);
|
||||
$balance->setBalanceHeader($header);
|
||||
|
||||
Log::debug('Clear unused budgets.');
|
||||
// remove budgets without expenses from balance lines:
|
||||
$balance = $this->removeUnusedBudgets($balance);
|
||||
|
||||
Log::debug('Return report.');
|
||||
|
||||
return $balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method collects all transfers that are part of a "balancing act" tag
|
||||
* and groups the amounts of those transfers by their destination account.
|
||||
*
|
||||
* This is used to indicate which expenses, usually outside of budgets, have been
|
||||
* corrected by transfers from a savings account.
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function allCoveredByBalancingActs(Collection $accounts, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
$set = auth()->user()->tags()
|
||||
->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id')
|
||||
->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
|
||||
->leftJoin(
|
||||
'transactions AS t_source', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 't_source.transaction_journal_id')->where('t_source.amount', '<', 0);
|
||||
}
|
||||
)
|
||||
->leftJoin(
|
||||
'transactions AS t_destination', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 't_destination.transaction_journal_id')->where('t_destination.amount', '>', 0);
|
||||
}
|
||||
)
|
||||
->where('tags.tagMode', 'balancingAct')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereIn('t_source.account_id', $ids)
|
||||
->whereIn('t_destination.account_id', $ids)
|
||||
->groupBy('t_destination.account_id')
|
||||
->get(
|
||||
[
|
||||
't_destination.account_id',
|
||||
DB::raw('SUM(t_destination.amount) AS sum'),
|
||||
]
|
||||
);
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createBalanceLine(BudgetLimit $budgetLimit, Collection $accounts): BalanceLine
|
||||
{
|
||||
$line = new BalanceLine;
|
||||
$line->setBudget($budgetLimit->budget);
|
||||
$line->setBudgetLimit($budgetLimit);
|
||||
|
||||
// loop accounts:
|
||||
foreach ($accounts as $account) {
|
||||
$balanceEntry = new BalanceEntry;
|
||||
$balanceEntry->setAccount($account);
|
||||
$spent = $this->budgetRepository->spentInPeriod(new Collection([$budgetLimit->budget]), new Collection([$account]), $budgetLimit->start_date, $budgetLimit->end_date);
|
||||
$balanceEntry->setSpent($spent);
|
||||
$line->addBalanceEntry($balanceEntry);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BalanceLine $noBudgetLine
|
||||
* @param BalanceLine $coveredByTagLine
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createLeftUnbalancedLine(BalanceLine $noBudgetLine, BalanceLine $coveredByTagLine): BalanceLine
|
||||
{
|
||||
$line = new BalanceLine;
|
||||
$line->setRole(BalanceLine::ROLE_DIFFROLE);
|
||||
$noBudgetEntries = $noBudgetLine->getBalanceEntries();
|
||||
$tagEntries = $coveredByTagLine->getBalanceEntries();
|
||||
|
||||
foreach ($noBudgetEntries as $entry) {
|
||||
$account = $entry->getAccount();
|
||||
$tagEntry = $tagEntries->filter(
|
||||
function (BalanceEntry $current) use ($account) {
|
||||
return $current->getAccount()->id === $account->id;
|
||||
}
|
||||
);
|
||||
if ($tagEntry->first()) {
|
||||
// found corresponding entry. As we should:
|
||||
$newEntry = new BalanceEntry;
|
||||
$newEntry->setAccount($account);
|
||||
$spent = bcadd($tagEntry->first()->getLeft(), $entry->getSpent());
|
||||
$newEntry->setSpent($spent);
|
||||
$line->addBalanceEntry($newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return $line;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createNoBudgetLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
|
||||
{
|
||||
$empty = new BalanceLine;
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$spent = $this->budgetRepository->spentInPeriodWoBudget(new Collection([$account]), $start, $end);
|
||||
// budget
|
||||
$budgetEntry = new BalanceEntry;
|
||||
$budgetEntry->setAccount($account);
|
||||
$budgetEntry->setSpent($spent);
|
||||
$empty->addBalanceEntry($budgetEntry);
|
||||
|
||||
}
|
||||
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createTagsBalanceLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
|
||||
{
|
||||
$tags = new BalanceLine;
|
||||
$tagsLeft = $this->allCoveredByBalancingActs($accounts, $start, $end);
|
||||
|
||||
$tags->setRole(BalanceLine::ROLE_TAGROLE);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$leftEntry = $tagsLeft->filter(
|
||||
function (Tag $tag) use ($account) {
|
||||
return $tag->account_id == $account->id;
|
||||
}
|
||||
);
|
||||
$left = '0';
|
||||
if (!is_null($leftEntry->first())) {
|
||||
$left = $leftEntry->first()->sum;
|
||||
}
|
||||
|
||||
// balanced by tags
|
||||
$tagEntry = new BalanceEntry;
|
||||
$tagEntry->setAccount($account);
|
||||
$tagEntry->setLeft($left);
|
||||
$tags->addBalanceEntry($tagEntry);
|
||||
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Balance $balance
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
|
||||
*
|
||||
* @return Balance
|
||||
*/
|
||||
private function removeUnusedBudgets(Balance $balance): Balance
|
||||
{
|
||||
$set = $balance->getBalanceLines();
|
||||
$newSet = new Collection;
|
||||
foreach ($set as $entry) {
|
||||
if (!is_null($entry->getBudget()->id)) {
|
||||
$sum = '0';
|
||||
foreach ($entry->getBalanceEntries() as $balanceEntry) {
|
||||
$sum = bcadd($sum, $balanceEntry->getSpent());
|
||||
}
|
||||
if (bccomp($sum, '0') === -1) {
|
||||
$newSet->push($entry);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$newSet->push($entry);
|
||||
}
|
||||
|
||||
$balance->setBalanceLines($newSet);
|
||||
|
||||
return $balance;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
36
app/Helpers/Report/BalanceReportHelperInterface.php
Normal file
36
app/Helpers/Report/BalanceReportHelperInterface.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* BalanceReportHelperInterface.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\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collection\Balance;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
||||
/**
|
||||
* Interface BalanceReportHelperInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Report
|
||||
*/
|
||||
interface BalanceReportHelperInterface
|
||||
{
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Balance
|
||||
*/
|
||||
public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): Balance;
|
||||
}
|
||||
140
app/Helpers/Report/BudgetReportHelper.php
Normal file
140
app/Helpers/Report/BudgetReportHelper.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* BudgetReportHelper.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\Report;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collection\Budget as BudgetCollection;
|
||||
use FireflyIII\Helpers\Collection\BudgetLine;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class BudgetReportHelper
|
||||
*
|
||||
* @package FireflyIII\Helpers\Report
|
||||
*/
|
||||
class BudgetReportHelper implements BudgetReportHelperInterface
|
||||
{
|
||||
/** @var BudgetRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* BudgetReportHelper constructor.
|
||||
*
|
||||
* @param BudgetRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(BudgetRepositoryInterface $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return BudgetCollection
|
||||
*/
|
||||
public function getBudgetReport(Carbon $start, Carbon $end, Collection $accounts): BudgetCollection
|
||||
{
|
||||
$object = new BudgetCollection;
|
||||
$set = $this->repository->getBudgets();
|
||||
|
||||
/** @var Budget $budget */
|
||||
foreach ($set as $budget) {
|
||||
$budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
|
||||
if ($budgetLimits->count() == 0) { // no budget limit(s) for this budget
|
||||
$spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);// spent for budget in time range
|
||||
if ($spent > 0) {
|
||||
$budgetLine = new BudgetLine;
|
||||
$budgetLine->setBudget($budget)->setOverspent($spent);
|
||||
$object->addOverspent($spent)->addBudgetLine($budgetLine);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($budgetLimits as $budgetLimit) { // one or more repetitions for budget
|
||||
$data = $this->calculateExpenses($budget, $budgetLimit, $accounts);
|
||||
$budgetLine = new BudgetLine;
|
||||
$budgetLine->setBudget($budget)->setBudgetLimit($budgetLimit)
|
||||
->setLeft($data['left'])->setSpent($data['expenses'])->setOverspent($data['overspent'])
|
||||
->setBudgeted(strval($budgetLimit->amount));
|
||||
|
||||
$object->addBudgeted(strval($budgetLimit->amount))->addSpent($data['spent'])
|
||||
->addLeft($data['left'])->addOverspent($data['overspent'])->addBudgetLine($budgetLine);
|
||||
|
||||
}
|
||||
}
|
||||
$noBudget = $this->repository->spentInPeriodWoBudget($accounts, $start, $end); // stuff outside of budgets
|
||||
$budgetLine = new BudgetLine;
|
||||
$budgetLine->setOverspent($noBudget)->setSpent($noBudget);
|
||||
$object->addOverspent($noBudget)->addBudgetLine($budgetLine);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getBudgetsWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection
|
||||
{
|
||||
/** @var BudgetRepositoryInterface $repository */
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$budgets = $repository->getActiveBudgets();
|
||||
|
||||
$set = new Collection;
|
||||
/** @var Budget $budget */
|
||||
foreach ($budgets as $budget) {
|
||||
$total = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
|
||||
if (bccomp($total, '0') === -1) {
|
||||
$set->push($budget);
|
||||
}
|
||||
}
|
||||
$set = $set->sortBy(
|
||||
function (Budget $budget) {
|
||||
return $budget->name;
|
||||
}
|
||||
);
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param BudgetLimit $budgetLimit
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function calculateExpenses(Budget $budget, BudgetLimit $budgetLimit, Collection $accounts): array
|
||||
{
|
||||
$array = [];
|
||||
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $budgetLimit->start_date, $budgetLimit->end_date);
|
||||
$array['left'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? bcadd($budgetLimit->amount, $expenses) : '0';
|
||||
$array['spent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? $expenses : '0';
|
||||
$array['overspent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $budgetLimit->amount);
|
||||
$array['expenses'] = $expenses;
|
||||
|
||||
return $array;
|
||||
|
||||
}
|
||||
}
|
||||
47
app/Helpers/Report/BudgetReportHelperInterface.php
Normal file
47
app/Helpers/Report/BudgetReportHelperInterface.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* BudgetReportHelperInterface.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\Report;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collection\Budget as BudgetCollection;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface BudgetReportHelperInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Report
|
||||
*/
|
||||
interface BudgetReportHelperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return BudgetCollection
|
||||
*/
|
||||
public function getBudgetReport(Carbon $start, Carbon $end, Collection $accounts): BudgetCollection;
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getBudgetsWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection;
|
||||
|
||||
}
|
||||
150
app/Helpers/Report/ReportHelper.php
Normal file
150
app/Helpers/Report/ReportHelper.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* ReportHelper.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\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collection\Bill as BillCollection;
|
||||
use FireflyIII\Helpers\Collection\BillLine;
|
||||
use FireflyIII\Helpers\Collection\Category as CategoryCollection;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Helpers\FiscalHelperInterface;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class ReportHelper
|
||||
*
|
||||
* @package FireflyIII\Helpers\Report
|
||||
*/
|
||||
class ReportHelper implements ReportHelperInterface
|
||||
{
|
||||
|
||||
/** @var BudgetRepositoryInterface */
|
||||
protected $budgetRepository;
|
||||
|
||||
/**
|
||||
* ReportHelper constructor.
|
||||
*
|
||||
*
|
||||
* @param BudgetRepositoryInterface $budgetRepository
|
||||
*/
|
||||
public function __construct(BudgetRepositoryInterface $budgetRepository)
|
||||
{
|
||||
$this->budgetRepository = $budgetRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method generates a full report for the given period on all
|
||||
* 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.
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return BillCollection
|
||||
*/
|
||||
public function getBillReport(Carbon $start, Carbon $end, Collection $accounts): BillCollection
|
||||
{
|
||||
/** @var BillRepositoryInterface $repository */
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$bills = $repository->getBillsForAccounts($accounts);
|
||||
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||
$collector->setAccounts($accounts)->setRange($start, $end)->setBills($bills);
|
||||
$journals = $collector->getJournals();
|
||||
$collection = new BillCollection;
|
||||
$collection->setStartDate($start);
|
||||
$collection->setEndDate($end);
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
$billLine = new BillLine;
|
||||
$billLine->setBill($bill);
|
||||
$billLine->setMin(strval($bill->amount_min));
|
||||
$billLine->setMax(strval($bill->amount_max));
|
||||
$billLine->setHit(false);
|
||||
$entry = $journals->filter(
|
||||
function (Transaction $transaction) use ($bill) {
|
||||
return $transaction->bill_id === $bill->id;
|
||||
}
|
||||
);
|
||||
$first = $entry->first();
|
||||
if (!is_null($first)) {
|
||||
$billLine->setTransactionJournalId($first->id);
|
||||
$billLine->setAmount($first->transaction_amount);
|
||||
$billLine->setLastHitDate($first->date);
|
||||
$billLine->setHit(true);
|
||||
}
|
||||
if ($billLine->isActive() || $billLine->isHit()) {
|
||||
$collection->addBill($billLine);
|
||||
}
|
||||
}
|
||||
$collection->filterBills();
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listOfMonths(Carbon $date): array
|
||||
{
|
||||
/** @var FiscalHelperInterface $fiscalHelper */
|
||||
$fiscalHelper = app(FiscalHelperInterface::class);
|
||||
$start = clone $date;
|
||||
$start->startOfMonth();
|
||||
$end = Carbon::now();
|
||||
$end->endOfMonth();
|
||||
$months = [];
|
||||
|
||||
while ($start <= $end) {
|
||||
$year = $fiscalHelper->endOfFiscalYear($start)->year; // current year
|
||||
if (!isset($months[$year])) {
|
||||
$months[$year] = [
|
||||
'fiscal_start' => $fiscalHelper->startOfFiscalYear($start)->format('Y-m-d'),
|
||||
'fiscal_end' => $fiscalHelper->endOfFiscalYear($start)->format('Y-m-d'),
|
||||
'start' => Carbon::createFromDate($year, 1, 1)->format('Y-m-d'),
|
||||
'end' => Carbon::createFromDate($year, 12, 31)->format('Y-m-d'),
|
||||
'months' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$currentEnd = clone $start;
|
||||
$currentEnd->endOfMonth();
|
||||
$months[$year]['months'][] = [
|
||||
'formatted' => $start->formatLocalized('%B %Y'),
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $currentEnd->format('Y-m-d'),
|
||||
'month' => $start->month,
|
||||
'year' => $year,
|
||||
];
|
||||
|
||||
$start = clone $currentEnd; // to make the hop to the next month properly
|
||||
$start->addDay();
|
||||
}
|
||||
|
||||
return $months;
|
||||
}
|
||||
|
||||
}
|
||||
52
app/Helpers/Report/ReportHelperInterface.php
Normal file
52
app/Helpers/Report/ReportHelperInterface.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* ReportHelperInterface.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\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collection\Bill as BillCollection;
|
||||
use FireflyIII\Helpers\Collection\Category as CategoryCollection;
|
||||
use FireflyIII\Helpers\Collection\Expense;
|
||||
use FireflyIII\Helpers\Collection\Income;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface ReportHelperInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Report
|
||||
*/
|
||||
interface ReportHelperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* This method generates a full report for the given period on all
|
||||
* the users bills and their payments.
|
||||
*
|
||||
* Excludes bills which have not had a payment on the mentioned accounts.
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return BillCollection
|
||||
*/
|
||||
public function getBillReport(Carbon $start, Carbon $end, Collection $accounts): BillCollection;
|
||||
|
||||
/**
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listOfMonths(Carbon $date): array;
|
||||
|
||||
}
|
||||
480
app/Http/Controllers/AccountController.php
Normal file
480
app/Http/Controllers/AccountController.php
Normal file
@@ -0,0 +1,480 @@
|
||||
<?php
|
||||
/**
|
||||
* AccountController.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;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Requests\AccountFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
|
||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Navigation;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use Steam;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class AccountController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers
|
||||
*/
|
||||
class AccountController extends Controller
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// translations:
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('mainTitleIcon', 'fa-credit-card');
|
||||
View::share('title', trans('firefly.accounts'));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $what
|
||||
*
|
||||
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|View
|
||||
*/
|
||||
public function create(string $what = 'asset')
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currencies = ExpandedForm::makeSelectList($repository->get());
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
$subTitle = trans('firefly.make_new_' . $what . '_account');
|
||||
|
||||
// pre fill some data
|
||||
Session::flash('preFilled', ['currency_id' => $defaultCurrency->id,]);
|
||||
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('accounts.create.fromStore') !== true) {
|
||||
Session::put('accounts.create.url', URL::previous());
|
||||
}
|
||||
Session::forget('accounts.create.fromStore');
|
||||
Session::flash('gaEventCategory', 'accounts');
|
||||
Session::flash('gaEventAction', 'create-' . $what);
|
||||
|
||||
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencies'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ARI $repository
|
||||
* @param Account $account
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function delete(ARI $repository, Account $account)
|
||||
{
|
||||
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
|
||||
$subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
|
||||
$accountList = ExpandedForm::makeSelectListWithEmpty($repository->getAccountsByType([$account->accountType->type]));
|
||||
unset($accountList[$account->id]);
|
||||
|
||||
// put previous url in session
|
||||
Session::put('accounts.delete.url', URL::previous());
|
||||
Session::flash('gaEventCategory', 'accounts');
|
||||
Session::flash('gaEventAction', 'delete-' . $typeName);
|
||||
|
||||
return view('accounts.delete', compact('account', 'subTitle', 'accountList'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param ARI $repository
|
||||
* @param Account $account
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(Request $request, ARI $repository, Account $account)
|
||||
{
|
||||
$type = $account->accountType->type;
|
||||
$typeName = config('firefly.shortNamesByFullName.' . $type);
|
||||
$name = $account->name;
|
||||
$accountId = $account->id;
|
||||
$moveTo = $repository->find(intval($request->get('move_account_before_delete')));
|
||||
|
||||
$repository->destroy($account, $moveTo);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Account $account)
|
||||
{
|
||||
|
||||
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
|
||||
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currencies = ExpandedForm::makeSelectList($repository->get());
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('accounts.edit.fromUpdate') !== true) {
|
||||
Session::put('accounts.edit.url', URL::previous());
|
||||
}
|
||||
Session::forget('accounts.edit.fromUpdate');
|
||||
|
||||
// pre fill some useful values.
|
||||
|
||||
// the opening balance is tricky:
|
||||
$openingBalanceAmount = $account->getOpeningBalanceAmount();
|
||||
$openingBalanceAmount = $account->getOpeningBalanceAmount() === '0' ? '' : $openingBalanceAmount;
|
||||
$openingBalanceDate = $account->getOpeningBalanceDate();
|
||||
$openingBalanceDate = $openingBalanceDate->year === 1900 ? null : $openingBalanceDate->format('Y-m-d');
|
||||
|
||||
$preFilled = [
|
||||
'accountNumber' => $account->getMeta('accountNumber'),
|
||||
'accountRole' => $account->getMeta('accountRole'),
|
||||
'ccType' => $account->getMeta('ccType'),
|
||||
'ccMonthlyPaymentDate' => $account->getMeta('ccMonthlyPaymentDate'),
|
||||
'BIC' => $account->getMeta('BIC'),
|
||||
'openingBalanceDate' => $openingBalanceDate,
|
||||
'openingBalance' => $openingBalanceAmount,
|
||||
'virtualBalance' => $account->virtual_balance,
|
||||
'currency_id' => $account->getMeta('currency_id'),
|
||||
];
|
||||
Session::flash('preFilled', $preFilled);
|
||||
Session::flash('gaEventCategory', 'accounts');
|
||||
Session::flash('gaEventAction', 'edit-' . $what);
|
||||
|
||||
return view('accounts.edit', compact('currencies', 'account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ARI $repository
|
||||
* @param string $what
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(ARI $repository, string $what)
|
||||
{
|
||||
$what = $what ?? 'asset';
|
||||
$subTitle = trans('firefly.' . $what . '_accounts');
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
$types = config('firefly.accountTypesByIdentifier.' . $what);
|
||||
$accounts = $repository->getAccountsByType($types);
|
||||
/** @var Carbon $start */
|
||||
$start = clone session('start', Carbon::now()->startOfMonth());
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', Carbon::now()->endOfMonth());
|
||||
$start->subDay();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
$startBalances = Steam::balancesById($ids, $start);
|
||||
$endBalances = Steam::balancesById($ids, $end);
|
||||
$activities = Steam::getLastActivities($ids);
|
||||
|
||||
$accounts->each(
|
||||
function (Account $account) use ($activities, $startBalances, $endBalances) {
|
||||
$account->lastActivityDate = $this->isInArray($activities, $account->id);
|
||||
$account->startBalance = $this->isInArray($startBalances, $account->id);
|
||||
$account->endBalance = $this->isInArray($endBalances, $account->id);
|
||||
$account->difference = bcsub($account->endBalance, $account->startBalance);
|
||||
}
|
||||
);
|
||||
|
||||
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param JournalCollectorInterface $collector
|
||||
* @param Account $account
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
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:
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||
$subTitle = $account->name;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$chartUri = route('chart.account.single', [$account->id]);
|
||||
$accountType = $account->accountType->type;
|
||||
|
||||
// grab those journals:
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('accounts/show/' . $account->id);
|
||||
|
||||
// generate entries for each period (and cache those)
|
||||
$entries = $this->periodEntries($account);
|
||||
|
||||
return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param ARI $repository
|
||||
* @param Account $account
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function showAll(Request $request, AccountRepositoryInterface $repository, Account $account)
|
||||
{
|
||||
$subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$chartUri = route('chart.account.all', [$account->id]);
|
||||
|
||||
// replace with journal collector:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('accounts/show/' . $account->id . '/all');
|
||||
|
||||
// get oldest and newest journal for account:
|
||||
$start = $repository->oldestJournalDate($account);
|
||||
$end = $repository->newestJournalDate($account);
|
||||
|
||||
// same call, except "entries".
|
||||
return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
// 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountFormRequest $request
|
||||
* @param ARI $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*
|
||||
*/
|
||||
public function store(AccountFormRequest $request, ARI $repository)
|
||||
{
|
||||
$data = $request->getAccountData();
|
||||
$account = $repository->store($data);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
|
||||
Preferences::mark();
|
||||
|
||||
// update preferences if necessary:
|
||||
$frontPage = Preferences::get('frontPageAccounts', [])->data;
|
||||
if (count($frontPage) > 0) {
|
||||
$frontPage[] = $account->id;
|
||||
Preferences::set('frontPageAccounts', $frontPage);
|
||||
}
|
||||
|
||||
if (intval($request->get('create_another')) === 1) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
Session::put('accounts.create.fromStore', true);
|
||||
|
||||
return redirect(route('accounts.create', [$request->input('what')]))->withInput();
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('accounts.create.url'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountFormRequest $request
|
||||
* @param ARI $repository
|
||||
* @param Account $account
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(AccountFormRequest $request, ARI $repository, Account $account)
|
||||
{
|
||||
$data = $request->getAccountData();
|
||||
$repository->update($account, $data);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
Session::put('accounts.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('accounts.edit', [$account->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('accounts.edit.url'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return null|mixed
|
||||
*/
|
||||
protected function isInArray(array $array, int $entryId)
|
||||
{
|
||||
if (isset($array[$entryId])) {
|
||||
return $array[$entryId];
|
||||
}
|
||||
|
||||
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]));
|
||||
}
|
||||
}
|
||||
93
app/Http/Controllers/Admin/ConfigurationController.php
Normal file
93
app/Http/Controllers/Admin/ConfigurationController.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* ConfigurationController.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\Http\Requests\ConfigurationRequest;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Preferences;
|
||||
use Redirect;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class ConfigurationController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Admin
|
||||
*/
|
||||
class ConfigurationController extends Controller
|
||||
{
|
||||
/**
|
||||
* ConfigurationController constructor.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$subTitle = strval(trans('firefly.instance_configuration'));
|
||||
$subTitleIcon = 'fa-wrench';
|
||||
|
||||
// all available configuration and their default value in case
|
||||
// they don't exist yet.
|
||||
$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', 'isDemoSite', 'siteOwner')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConfigurationRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function postIndex(ConfigurationRequest $request)
|
||||
{
|
||||
// get config values:
|
||||
$data = $request->getConfigurationData();
|
||||
|
||||
// store config values
|
||||
FireflyConfig::set('single_user_mode', $data['single_user_mode']);
|
||||
FireflyConfig::set('is_demo_site', $data['is_demo_site']);
|
||||
|
||||
// flash message
|
||||
Session::flash('success', strval(trans('firefly.configuration_updated')));
|
||||
Preferences::mark();
|
||||
|
||||
return Redirect::route('admin.configuration.index');
|
||||
}
|
||||
|
||||
}
|
||||
37
app/Http/Controllers/Admin/HomeController.php
Normal file
37
app/Http/Controllers/Admin/HomeController.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* HomeController.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;
|
||||
|
||||
/**
|
||||
* Class HomeController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Admin
|
||||
*/
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$title = strval(trans('firefly.administration'));
|
||||
$mainTitleIcon = 'fa-hand-spock-o';
|
||||
|
||||
return view('admin.index', compact('title', 'mainTitleIcon'));
|
||||
}
|
||||
|
||||
}
|
||||
164
app/Http/Controllers/Admin/UserController.php
Normal file
164
app/Http/Controllers/Admin/UserController.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* UserController.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\Http\Requests\UserFormRequest;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class UserController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Admin
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(User $user)
|
||||
{
|
||||
// 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
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(UserRepositoryInterface $repository)
|
||||
{
|
||||
$subTitle = strval(trans('firefly.user_administration'));
|
||||
$subTitleIcon = 'fa-users';
|
||||
$users = $repository->all();
|
||||
|
||||
// add meta stuff.
|
||||
$users->each(
|
||||
function (User $user) {
|
||||
$list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret'];
|
||||
$preferences = Preferences::getArrayForUser($user, $list);
|
||||
$user->isAdmin = $user->hasRole('owner');
|
||||
$is2faEnabled = $preferences['twoFactorAuthEnabled'] === true;
|
||||
$has2faSecret = !is_null($preferences['twoFactorAuthSecret']);
|
||||
$user->has2FA = ($is2faEnabled && $has2faSecret) ? true : false;
|
||||
$user->prefs = $preferences;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserRepositoryInterface $repository
|
||||
* @param User $user
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function show(UserRepositoryInterface $repository, User $user)
|
||||
{
|
||||
$title = strval(trans('firefly.administration'));
|
||||
$mainTitleIcon = 'fa-hand-spock-o';
|
||||
$subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email]));
|
||||
$subTitleIcon = 'fa-user';
|
||||
$information = $repository->getUserData($user);
|
||||
|
||||
return view(
|
||||
'admin.users.show',
|
||||
compact(
|
||||
'title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'information', 'user'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
183
app/Http/Controllers/AttachmentController.php
Normal file
183
app/Http/Controllers/AttachmentController.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* AttachmentController.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;
|
||||
|
||||
use File;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Requests\AttachmentFormRequest;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Session;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class AttachmentController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it's 13.
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers
|
||||
*/
|
||||
class AttachmentController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// translations:
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('mainTitleIcon', 'fa-paperclip');
|
||||
View::share('title', trans('firefly.attachments'));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function delete(Attachment $attachment)
|
||||
{
|
||||
$subTitle = trans('firefly.delete_attachment', ['name' => $attachment->filename]);
|
||||
|
||||
// put previous url in session
|
||||
Session::put('attachments.delete.url', URL::previous());
|
||||
Session::flash('gaEventCategory', 'attachments');
|
||||
Session::flash('gaEventAction', 'delete-attachment');
|
||||
|
||||
return view('attachments.delete', compact('attachment', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttachmentRepositoryInterface $repository
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||
{
|
||||
$name = $attachment->filename;
|
||||
|
||||
$repository->destroy($attachment);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.attachment_deleted', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
|
||||
return redirect(session('attachments.delete.url'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttachmentRepositoryInterface $repository
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return mixed
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||
{
|
||||
if ($repository->exists($attachment)) {
|
||||
$content = $repository->getContent($attachment);
|
||||
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
|
||||
|
||||
return response($content, 200)
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Content-Type', 'application/octet-stream')
|
||||
->header('Content-Disposition', 'attachment; filename=' . $quoted)
|
||||
->header('Content-Transfer-Encoding', 'binary')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', strlen($content));
|
||||
}
|
||||
throw new FireflyException('Could not find the indicated attachment. The file is no longer there.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Attachment $attachment)
|
||||
{
|
||||
$subTitleIcon = 'fa-pencil';
|
||||
$subTitle = trans('firefly.edit_attachment', ['name' => $attachment->filename]);
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('attachments.edit.fromUpdate') !== true) {
|
||||
Session::put('attachments.edit.url', URL::previous());
|
||||
}
|
||||
Session::forget('attachments.edit.fromUpdate');
|
||||
|
||||
return view('attachments.edit', compact('attachment', 'subTitleIcon', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function preview(Attachment $attachment)
|
||||
{
|
||||
$image = 'images/page_green.png';
|
||||
|
||||
if ($attachment->mime == 'application/pdf') {
|
||||
$image = 'images/page_white_acrobat.png';
|
||||
}
|
||||
$file = public_path($image);
|
||||
$response = Response::make(File::get($file));
|
||||
$response->header('Content-Type', 'image/png');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param AttachmentFormRequest $request
|
||||
* @param AttachmentRepositoryInterface $repository
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||
{
|
||||
$data = $request->getAttachmentData();
|
||||
$repository->update($attachment, $data);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
Session::put('attachments.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('attachments.edit', [$attachment->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('attachments.edit.url'));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
74
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
74
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* ForgotPasswordController.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\Auth;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Password;
|
||||
|
||||
/**
|
||||
* Class ForgotPasswordController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class ForgotPasswordController extends Controller
|
||||
{
|
||||
use SendsPasswordResetEmails;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reset link to the given user.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request)
|
||||
{
|
||||
$this->validate($request, ['email' => 'required|email']);
|
||||
|
||||
// verify if the user is not a demo user. If so, we give him back an error.
|
||||
$user = User::where('email', $request->get('email'))->first();
|
||||
if (!is_null($user) && $user->hasRole('demo')) {
|
||||
return back()->withErrors(
|
||||
['email' => trans('firefly.cannot_reset_demo_user')]
|
||||
);
|
||||
}
|
||||
|
||||
$response = $this->broker()->sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
if ($response === Password::RESET_LINK_SENT) {
|
||||
return back()->with('status', trans($response));
|
||||
}
|
||||
|
||||
// If an error was returned by the password broker, we will get this message
|
||||
// translated so we can notify a user of the problem. We'll redirect back
|
||||
// to where the users came from so they can attempt this process again.
|
||||
return back()->withErrors(
|
||||
['email' => trans($response)]
|
||||
);
|
||||
}
|
||||
}
|
||||
160
app/Http/Controllers/Auth/LoginController.php
Normal file
160
app/Http/Controllers/Auth/LoginController.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* LoginController.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\Auth;
|
||||
|
||||
use Config;
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Lang;
|
||||
|
||||
/**
|
||||
* Class LoginController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class LoginController extends Controller
|
||||
{
|
||||
|
||||
use AuthenticatesUsers;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login / registration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('guest', ['except' => 'logout']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a login request to the application.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$this->validateLogin($request);
|
||||
$lockedOut = $this->hasTooManyLoginAttempts($request);
|
||||
if ($lockedOut) {
|
||||
$this->fireLockoutEvent($request);
|
||||
|
||||
return $this->sendLockoutResponse($request);
|
||||
}
|
||||
|
||||
$credentials = $this->credentials($request);
|
||||
$credentials['blocked'] = 0; // must not be blocked.
|
||||
|
||||
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
|
||||
return $this->sendLoginResponse($request);
|
||||
}
|
||||
|
||||
$errorMessage = $this->getBlockedError($credentials['email']);
|
||||
|
||||
if (!$lockedOut) {
|
||||
$this->incrementLoginAttempts($request);
|
||||
}
|
||||
|
||||
return $this->sendFailedLoginResponse($request, $errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application login form.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showLoginForm(Request $request)
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
if ($singleUserMode === true && $userCount > 0) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
$email = $request->old('email');
|
||||
$remember = $request->old('remember');
|
||||
|
||||
return view('auth.login', compact('allowRegistration', 'email', 'remember'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failed login message.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFailedLoginMessage(string $message)
|
||||
{
|
||||
if (strlen($message) > 0) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
return Lang::has('auth.failed') ? Lang::get('auth.failed') : 'These credentials do not match our records.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failed login response instance.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $message
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function sendFailedLoginResponse(Request $request, string $message)
|
||||
{
|
||||
return redirect()->back()
|
||||
->withInput($request->only($this->username(), 'remember'))
|
||||
->withErrors(
|
||||
[
|
||||
$this->username() => $this->getFailedLoginMessage($message),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getBlockedError(string $email): string
|
||||
{
|
||||
// check if user is blocked:
|
||||
$errorMessage = '';
|
||||
/** @var User $foundUser */
|
||||
$foundUser = User::where('email', $email)->where('blocked', 1)->first();
|
||||
if (!is_null($foundUser)) {
|
||||
// user exists, but is blocked:
|
||||
$code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked';
|
||||
$errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $email]));
|
||||
}
|
||||
|
||||
return $errorMessage;
|
||||
}
|
||||
}
|
||||
86
app/Http/Controllers/Auth/PasswordController.php
Normal file
86
app/Http/Controllers/Auth/PasswordController.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* PasswordController.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\Auth;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
|
||||
/**
|
||||
* Class PasswordController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
* @method getEmailSubject()
|
||||
* @method getSendResetLinkEmailSuccessResponse(string $response)
|
||||
* @method getSendResetLinkEmailFailureResponse(string $response)
|
||||
*/
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Create a new password controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reset link to the given user.
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 7 but ok
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request)
|
||||
{
|
||||
$this->validate($request, ['email' => 'required|email']);
|
||||
|
||||
$user = User::whereEmail($request->get('email'))->first();
|
||||
$response = 'passwords.blocked';
|
||||
|
||||
if (is_null($user)) {
|
||||
$response = Password::INVALID_USER;
|
||||
}
|
||||
|
||||
if (!is_null($user) && intval($user->blocked) === 0) {
|
||||
$response = Password::sendResetLink(
|
||||
$request->only('email'), function (Message $message) {
|
||||
$message->subject($this->getEmailSubject());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
switch ($response) {
|
||||
case Password::RESET_LINK_SENT:
|
||||
return $this->getSendResetLinkEmailSuccessResponse($response);
|
||||
|
||||
case Password::INVALID_USER:
|
||||
case 'passwords.blocked':
|
||||
default:
|
||||
return $this->getSendResetLinkEmailFailureResponse($response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
149
app/Http/Controllers/Auth/RegisterController.php
Normal file
149
app/Http/Controllers/Auth/RegisterController.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* RegisterController.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\Auth;
|
||||
|
||||
use Auth;
|
||||
use Config;
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Session;
|
||||
use Validator;
|
||||
|
||||
/**
|
||||
* Class RegisterController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
|
||||
use RegistersUsers;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login / registration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/home';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
if ($singleUserMode === true && $userCount > 0) {
|
||||
$message = 'Registration is currently not available.';
|
||||
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
|
||||
$validator = $this->validator($request->all());
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->throwValidationException($request, $validator);
|
||||
}
|
||||
|
||||
$user = $this->create($request->all());
|
||||
|
||||
// trigger user registration event:
|
||||
event(new RegisteredUser($user, $request->ip()));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.registered')));
|
||||
Session::flash('gaEventCategory', 'user');
|
||||
Session::flash('gaEventAction', 'new-registration');
|
||||
|
||||
return redirect($this->redirectPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* OLD
|
||||
* Show the application registration form.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showRegistrationForm(Request $request)
|
||||
{
|
||||
// is demo site?
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', Config::get('firefly.configuration.is_demo_site'))->data;
|
||||
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
if ($singleUserMode === true && $userCount > 0) {
|
||||
$message = 'Registration is currently not available.';
|
||||
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
$email = $request->old('email');
|
||||
|
||||
return view('auth.register', compact('isDemoSite', 'email'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user instance after a valid registration.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
protected function create(array $data)
|
||||
{
|
||||
return User::create(
|
||||
[
|
||||
'email' => $data['email'],
|
||||
'password' => bcrypt($data['password']),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a validator for an incoming registration request.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return \Illuminate\Contracts\Validation\Validator
|
||||
*/
|
||||
protected function validator(array $data)
|
||||
{
|
||||
return Validator::make(
|
||||
$data, [
|
||||
'email' => 'required|email|max:255|unique:users',
|
||||
'password' => 'required|min:6|confirmed',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
38
app/Http/Controllers/Auth/ResetPasswordController.php
Normal file
38
app/Http/Controllers/Auth/ResetPasswordController.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* ResetPasswordController.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\Auth;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Class ResetPasswordController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware('guest');
|
||||
}
|
||||
}
|
||||
91
app/Http/Controllers/Auth/TwoFactorController.php
Normal file
91
app/Http/Controllers/Auth/TwoFactorController.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* TwoFactorController.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\Auth;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\TokenFormRequest;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class TwoFactorController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class TwoFactorController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
// to make sure the validator in the next step gets the secret, we push it in session
|
||||
$secret = Preferences::get('twoFactorAuthSecret', null)->data;
|
||||
$title = strval(trans('firefly.two_factor_title'));
|
||||
|
||||
// make sure the user has two factor configured:
|
||||
$has2FA = Preferences::get('twoFactorAuthEnabled', null)->data;
|
||||
if (is_null($has2FA) || $has2FA === false) {
|
||||
return redirect(route('index'));
|
||||
}
|
||||
|
||||
if (strlen(strval($secret)) === 0) {
|
||||
throw new FireflyException('Your two factor authentication secret is empty, which it cannot be at this point. Please check the log files.');
|
||||
}
|
||||
Session::flash('two-factor-secret', $secret);
|
||||
|
||||
return view('auth.two-factor', compact('user', 'title'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function lostTwoFactor()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$siteOwner = env('SITE_OWNER', '');
|
||||
$title = strval(trans('firefly.two_factor_forgot_title'));
|
||||
|
||||
Log::info(
|
||||
'To reset the two factor authentication for user #' . $user->id .
|
||||
' (' . $user->email . '), simply open the "preferences" table and delete the entries with the names "twoFactorAuthEnabled" and' .
|
||||
' "twoFactorAuthSecret" for user_id ' . $user->id . '. That will take care of it.'
|
||||
);
|
||||
|
||||
return view('auth.lost-two-factor', compact('user', 'siteOwner', 'title'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TokenFormRequest $request
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function postIndex(TokenFormRequest $request)
|
||||
{
|
||||
Session::put('twofactor-authenticated', true);
|
||||
Session::put('twofactor-authenticated-date', new Carbon);
|
||||
|
||||
return redirect(route('home'));
|
||||
}
|
||||
|
||||
}
|
||||
283
app/Http/Controllers/BillController.php
Normal file
283
app/Http/Controllers/BillController.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
/**
|
||||
* BillController.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;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Requests\BillFormRequest;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class BillController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers
|
||||
*/
|
||||
class BillController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('title', trans('firefly.bills'));
|
||||
View::share('mainTitleIcon', 'fa-calendar-o');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$periods = [];
|
||||
foreach (config('firefly.bill_periods') as $current) {
|
||||
$periods[$current] = trans('firefly.' . $current);
|
||||
}
|
||||
$subTitle = trans('firefly.create_new_bill');
|
||||
|
||||
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('bills.create.fromStore') !== true) {
|
||||
Session::put('bills.create.url', URL::previous());
|
||||
}
|
||||
Session::forget('bills.create.fromStore');
|
||||
Session::flash('gaEventCategory', 'bills');
|
||||
Session::flash('gaEventAction', 'create');
|
||||
|
||||
return view('bills.create', compact('periods', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function delete(Bill $bill)
|
||||
{
|
||||
// put previous url in session
|
||||
Session::put('bills.delete.url', URL::previous());
|
||||
Session::flash('gaEventCategory', 'bills');
|
||||
Session::flash('gaEventAction', 'delete');
|
||||
$subTitle = trans('firefly.delete_bill', ['name' => $bill->name]);
|
||||
|
||||
return view('bills.delete', compact('bill', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillRepositoryInterface $repository
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(BillRepositoryInterface $repository, Bill $bill)
|
||||
{
|
||||
$name = $bill->name;
|
||||
$billId = $bill->id;
|
||||
$repository->destroy($bill);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.deleted_bill', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
|
||||
$uri = session('bills.delete.url');
|
||||
if (!(strpos($uri, sprintf('bills/show/%s', $billId)) === false)) {
|
||||
// uri would point back to bill
|
||||
$uri = route('bills.index');
|
||||
}
|
||||
|
||||
return redirect($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Bill $bill)
|
||||
{
|
||||
$periods = [];
|
||||
foreach (config('firefly.bill_periods') as $current) {
|
||||
$periods[$current] = trans('firefly.' . $current);
|
||||
}
|
||||
$subTitle = trans('firefly.edit_bill', ['name' => $bill->name]);
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('bills.edit.fromUpdate') !== true) {
|
||||
Session::put('bills.edit.url', URL::previous());
|
||||
}
|
||||
Session::forget('bills.edit.fromUpdate');
|
||||
Session::flash('gaEventCategory', 'bills');
|
||||
Session::flash('gaEventAction', 'edit');
|
||||
|
||||
return view('bills.edit', compact('subTitle', 'periods', 'bill'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(BillRepositoryInterface $repository)
|
||||
{
|
||||
/** @var Carbon $start */
|
||||
$start = session('start');
|
||||
/** @var Carbon $end */
|
||||
$end = session('end');
|
||||
|
||||
$bills = $repository->getBills();
|
||||
$bills->each(
|
||||
function (Bill $bill) use ($repository, $start, $end) {
|
||||
|
||||
// paid in this period?
|
||||
$bill->paidDates = $repository->getPaidDatesInRange($bill, $start, $end);
|
||||
$bill->payDates = $repository->getPayDatesInRange($bill, $start, $end);
|
||||
$lastDate = clone $start;
|
||||
if ($bill->paidDates->count() >= $bill->payDates->count()) {
|
||||
$lastDate = $end;
|
||||
}
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastDate);
|
||||
}
|
||||
);
|
||||
|
||||
return view('bills.index', compact('bills'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillRepositoryInterface $repository
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function rescan(BillRepositoryInterface $repository, Bill $bill)
|
||||
{
|
||||
if (intval($bill->active) == 0) {
|
||||
Session::flash('warning', strval(trans('firefly.cannot_scan_inactive_bill')));
|
||||
|
||||
return redirect(URL::previous());
|
||||
}
|
||||
|
||||
$journals = $repository->getPossiblyRelatedJournals($bill);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$repository->scan($bill, $journal);
|
||||
}
|
||||
|
||||
|
||||
Session::flash('success', strval(trans('firefly.rescanned_bill')));
|
||||
Preferences::mark();
|
||||
|
||||
return redirect(URL::previous());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param BillRepositoryInterface $repository
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function show(Request $request, BillRepositoryInterface $repository, Bill $bill)
|
||||
{
|
||||
/** @var Carbon $date */
|
||||
$date = session('start');
|
||||
$year = $date->year;
|
||||
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$yearAverage = $repository->getYearAverage($bill, $date);
|
||||
$overallAverage = $repository->getOverallAverage($bill);
|
||||
|
||||
// use collector:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||
$collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setLimit($pageSize)->setPage($page)->withBudgetInformation()
|
||||
->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/bills/show/' . $bill->id);
|
||||
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
|
||||
$hideBill = true;
|
||||
$subTitle = e($bill->name);
|
||||
|
||||
return view('bills.show', compact('journals', 'yearAverage', 'overallAverage', 'year', 'hideBill', 'bill', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillFormRequest $request
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(BillFormRequest $request, BillRepositoryInterface $repository)
|
||||
{
|
||||
$billData = $request->getBillData();
|
||||
$bill = $repository->store($billData);
|
||||
Session::flash('success', strval(trans('firefly.stored_new_bill', ['name' => e($bill->name)])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('create_another')) === 1) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
Session::put('bills.create.fromStore', true);
|
||||
|
||||
return redirect(route('bills.create'))->withInput();
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('bills.create.url'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillFormRequest $request
|
||||
* @param BillRepositoryInterface $repository
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(BillFormRequest $request, BillRepositoryInterface $repository, Bill $bill)
|
||||
{
|
||||
$billData = $request->getBillData();
|
||||
$bill = $repository->update($bill, $billData);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.updated_bill', ['name' => e($bill->name)])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
Session::put('bills.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('bills.edit', [$bill->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('bills.edit.url'));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
456
app/Http/Controllers/BudgetController.php
Normal file
456
app/Http/Controllers/BudgetController.php
Normal file
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
/**
|
||||
* BudgetController.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;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Requests\BudgetFormRequest;
|
||||
use FireflyIII\Http\Requests\BudgetIncomeRequest;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Session;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers
|
||||
*/
|
||||
class BudgetController extends Controller
|
||||
{
|
||||
|
||||
/** @var BudgetRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
View::share('hideBudgets', true);
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('title', trans('firefly.budgets'));
|
||||
View::share('mainTitleIcon', 'fa-tasks');
|
||||
$this->repository = app(BudgetRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function amount(Request $request, Budget $budget)
|
||||
{
|
||||
$amount = intval($request->get('amount'));
|
||||
/** @var Carbon $start */
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
/** @var Carbon $end */
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
|
||||
if ($amount == 0) {
|
||||
$budgetLimit = null;
|
||||
}
|
||||
Preferences::mark();
|
||||
|
||||
return Response::json(['name' => $budget->name, 'limit' => $budgetLimit ? $budgetLimit->id : 0, 'amount' => $amount]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('budgets.create.fromStore') !== true) {
|
||||
Session::put('budgets.create.url', URL::previous());
|
||||
}
|
||||
Session::forget('budgets.create.fromStore');
|
||||
Session::flash('gaEventCategory', 'budgets');
|
||||
Session::flash('gaEventAction', 'create');
|
||||
$subTitle = (string)trans('firefly.create_new_budget');
|
||||
|
||||
return view('budgets.create', compact('subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function delete(Budget $budget)
|
||||
{
|
||||
$subTitle = trans('firefly.delete_budget', ['name' => $budget->name]);
|
||||
|
||||
// put previous url in session
|
||||
Session::put('budgets.delete.url', URL::previous());
|
||||
Session::flash('gaEventCategory', 'budgets');
|
||||
Session::flash('gaEventAction', 'delete');
|
||||
|
||||
return view('budgets.delete', compact('budget', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Budget $budget)
|
||||
{
|
||||
|
||||
$name = $budget->name;
|
||||
$budgetId = $budget->id;
|
||||
$this->repository->destroy($budget);
|
||||
|
||||
|
||||
Session::flash('success', strval(trans('firefly.deleted_budget', ['name' => e($name)])));
|
||||
Preferences::mark();
|
||||
|
||||
$uri = session('budgets.delete.url');
|
||||
if (!(strpos($uri, sprintf('budgets/show/%s', $budgetId)) === false)) {
|
||||
// uri would point back to budget
|
||||
$uri = route('budgets.index');
|
||||
}
|
||||
|
||||
return redirect($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Budget $budget)
|
||||
{
|
||||
$subTitle = trans('firefly.edit_budget', ['name' => $budget->name]);
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('budgets.edit.fromUpdate') !== true) {
|
||||
Session::put('budgets.edit.url', URL::previous());
|
||||
}
|
||||
Session::forget('budgets.edit.fromUpdate');
|
||||
Session::flash('gaEventCategory', 'budgets');
|
||||
Session::flash('gaEventAction', 'edit');
|
||||
|
||||
return view('budgets.edit', compact('budget', 'subTitle'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
$budgets = $this->repository->getActiveBudgets();
|
||||
$inactive = $this->repository->getInactiveBudgets();
|
||||
$start = session('start', new Carbon);
|
||||
$end = session('end', new Carbon);
|
||||
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
|
||||
$spent = array_sum(array_column($budgetInformation, 'spent'));
|
||||
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
|
||||
|
||||
return view(
|
||||
'budgets.index',
|
||||
compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', 'spent', 'budgeted')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function noBudget(Request $request)
|
||||
{
|
||||
/** @var Carbon $start */
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
/** @var Carbon $end */
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$subTitle = trans(
|
||||
'firefly.without_budget_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
|
||||
// collector
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutBudget();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/list/noBudget');
|
||||
|
||||
return view('budgets.no-budget', compact('journals', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function postUpdateIncome(BudgetIncomeRequest $request)
|
||||
{
|
||||
$start = session('start', new Carbon);
|
||||
$end = session('end', new Carbon);
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$amount = $request->get('amount');
|
||||
|
||||
$this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
|
||||
Preferences::mark();
|
||||
|
||||
return redirect(route('budgets.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function show(Request $request, Budget $budget)
|
||||
{
|
||||
/** @var Carbon $start */
|
||||
$start = session('first', Carbon::create()->startOfYear());
|
||||
$end = new Carbon;
|
||||
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$limits = $this->getLimits($budget, $start, $end);
|
||||
$repetition = null;
|
||||
// collector:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/show/' . $budget->id);
|
||||
|
||||
|
||||
$subTitle = e($budget->name);
|
||||
|
||||
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Budget $budget
|
||||
* @param BudgetLimit $budgetLimit
|
||||
*
|
||||
* @return View
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
|
||||
{
|
||||
if ($budgetLimit->budget->id != $budget->id) {
|
||||
throw new FireflyException('This budget limit is not part of this budget.');
|
||||
}
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$subTitle = trans(
|
||||
'firefly.budget_in_period', [
|
||||
'name' => $budget->name,
|
||||
'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat),
|
||||
]
|
||||
);
|
||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||
|
||||
// collector:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||
$collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date)
|
||||
->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/show/' . $budget->id . '/' . $budgetLimit->id);
|
||||
|
||||
|
||||
$start = session('first', Carbon::create()->startOfYear());
|
||||
$end = new Carbon;
|
||||
$limits = $this->getLimits($budget, $start, $end);
|
||||
|
||||
return view('budgets.show', compact('limits', 'budget', 'budgetLimit', 'journals', 'subTitle'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetFormRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(BudgetFormRequest $request)
|
||||
{
|
||||
$data = $request->getBudgetData();
|
||||
$budget = $this->repository->store($data);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.stored_new_budget', ['name' => e($budget->name)])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('create_another')) === 1) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
Session::put('budgets.create.fromStore', true);
|
||||
|
||||
return redirect(route('budgets.create'))->withInput();
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('budgets.create.url'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetFormRequest $request
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(BudgetFormRequest $request, Budget $budget)
|
||||
{
|
||||
$data = $request->getBudgetData();
|
||||
$this->repository->update($budget, $data);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.updated_budget', ['name' => e($budget->name)])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
Session::put('budgets.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('budgets.edit.url'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
public function updateIncome()
|
||||
{
|
||||
$start = session('start', new Carbon);
|
||||
$end = session('end', new Carbon);
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
|
||||
|
||||
|
||||
return view('budgets.income', compact('available', 'start', 'end'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $budgets
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get account information
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||
$return = [];
|
||||
/** @var Budget $budget */
|
||||
foreach ($budgets as $budget) {
|
||||
$budgetId = $budget->id;
|
||||
$return[$budgetId] = [
|
||||
'spent' => $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end),
|
||||
'budgeted' => '0',
|
||||
'currentRep' => false,
|
||||
];
|
||||
$budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
|
||||
$otherLimits = new Collection;
|
||||
|
||||
// get all the budget limits relevant between start and end and examine them:
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($budgetLimits as $limit) {
|
||||
if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)
|
||||
) {
|
||||
$return[$budgetId]['currentLimit'] = $limit;
|
||||
$return[$budgetId]['budgeted'] = $limit->amount;
|
||||
continue;
|
||||
}
|
||||
// otherwise it's just one of the many relevant repetitions:
|
||||
$otherLimits->push($limit);
|
||||
}
|
||||
$return[$budgetId]['otherLimits'] = $otherLimits;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function getLimits(Budget $budget, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
// properties for cache
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($budget->id);
|
||||
$cache->addProperty('get-limits');
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||
$set = $this->repository->getBudgetLimits($budget, $start, $end);
|
||||
$limits = new Collection();
|
||||
|
||||
/** @var BudgetLimit $entry */
|
||||
foreach ($set as $entry) {
|
||||
$entry->spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $entry->start_date, $entry->end_date);
|
||||
$limits->push($entry);
|
||||
}
|
||||
$cache->store($limits);
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user