mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-16 10:54:39 +00:00
Compare commits
135 Commits
develop-20
...
0e29e282df
Author | SHA1 | Date | |
---|---|---|---|
|
0e29e282df | ||
|
0b3fd335ad | ||
|
9b2263c7bb | ||
|
1305fafd38 | ||
|
844b8d48c4 | ||
|
fc9ef290f1 | ||
|
020c8ad933 | ||
|
33ea27b411 | ||
|
b39e3e5965 | ||
|
ba7e504f12 | ||
|
a732ce2a20 | ||
|
2b76c4801f | ||
|
dd09dd59e9 | ||
|
d56088d086 | ||
|
f27f00cfa0 | ||
|
d762136e0b | ||
|
536cd78e29 | ||
|
5ef0889f50 | ||
|
c2ffedb506 | ||
|
fe702a3030 | ||
|
2244d366bd | ||
|
ac16e0294e | ||
|
bf1b0ee3c2 | ||
|
ffec4bbfba | ||
|
ae6e103c09 | ||
|
99d4471f75 | ||
|
7f1fb72298 | ||
|
009c2ed5d9 | ||
|
44fc00299c | ||
|
774fc4281c | ||
|
c47955c069 | ||
|
e7569644f7 | ||
|
c567474043 | ||
|
9f394e92fe | ||
|
66befc7e44 | ||
|
c3a28fc698 | ||
|
ef317d5b3c | ||
|
152301f9ee | ||
|
645e9ba1f7 | ||
|
56487c3a33 | ||
|
b8062a915c | ||
|
5780c9512a | ||
|
71d39707d9 | ||
|
9ccb8ae692 | ||
|
8cd50bb5bd | ||
|
ae9e1278e5 | ||
|
58c03797b2 | ||
|
7db38b4c6c | ||
|
da6b447e64 | ||
|
c19ac2b0f3 | ||
|
d5ca2171b3 | ||
|
20972cb29f | ||
|
7b714d0866 | ||
|
240ae8fa57 | ||
|
5a2f6b2652 | ||
|
4196ce31f0 | ||
|
be8ca5db50 | ||
|
30a417ea3c | ||
|
695ed940e0 | ||
|
1353554cf8 | ||
|
e1ba2732af | ||
|
42b57c0e0e | ||
|
a6072753b2 | ||
|
e92c224c39 | ||
|
a3ed7ec8f6 | ||
|
17a2f99dff | ||
|
c14971543c | ||
|
55f899608d | ||
|
83be63f27e | ||
|
ed48d190e5 | ||
|
3c3b6615e6 | ||
|
e71e5a877b | ||
|
b2a65dc660 | ||
|
d66dccd076 | ||
|
c1128b28f2 | ||
|
da8e78c28d | ||
|
f50aa6b0ce | ||
|
661e4e53e6 | ||
|
3eeda4a6aa | ||
|
4dba9cea21 | ||
|
6aab5fab05 | ||
|
4b0597d19a | ||
|
92f534bcb3 | ||
|
76e91be4dc | ||
|
deca4fed56 | ||
|
73512b0365 | ||
|
aaffc125e7 | ||
|
41a48c39a0 | ||
|
2d96bd84b5 | ||
|
ad1c1d2254 | ||
|
813206766d | ||
|
bb25d4a82a | ||
|
f3b78beecc | ||
|
64073768fe | ||
|
fe6dd0f901 | ||
|
aac8d11ff6 | ||
|
afa99a35b5 | ||
|
e9cb0a51d7 | ||
|
9fbcccfd02 | ||
|
468c9c9d56 | ||
|
f76b27a73d | ||
|
579fe81616 | ||
|
ec9ba53690 | ||
|
85337c53d4 | ||
|
eb6d585bb2 | ||
|
378ffbc609 | ||
|
3b3c8e5bcd | ||
|
75cbdb6a57 | ||
|
cdaff0d983 | ||
|
dda3863889 | ||
|
57dc423b3f | ||
|
c0570bc3b2 | ||
|
65110d1666 | ||
|
5a10b29402 | ||
|
028544ca2e | ||
|
af46729372 | ||
|
4c7789a668 | ||
|
292908048c | ||
|
e300314e05 | ||
|
4f1f360346 | ||
|
bff856aeff | ||
|
7f5a1bda8d | ||
|
b506281bd6 | ||
|
dfe9b3e787 | ||
|
2428a2a7c5 | ||
|
0e8f608e00 | ||
|
70071767ab | ||
|
0ad6beb66c | ||
|
1197f65589 | ||
|
7bbf2dcc6f | ||
|
d4ab69ebe6 | ||
|
c8c552602e | ||
|
1921a8050b | ||
|
f488feda93 | ||
|
d90c033b83 |
20
.github/release-notes/alpha.md
vendored
Normal file
20
.github/release-notes/alpha.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Welcome to release %version of Firefly III. This **alpha** release contains the latest fixes, translations and features. It is probably buggy and may not work as expected. You can download the release below, and adventurous Docker users can find this release under the `alpha` tag.
|
||||
|
||||
:warning: Please be careful with this alpha release, as it may not work as expected.
|
||||
|
||||
Alpha releases are created to test new features and fixes before they are included in a stable release. They are not recommended for production use. This release was created on %date and may contain unexpected bugs. Data loss is rare but possible.
|
||||
|
||||
## Changelog (not final)
|
||||
|
||||
%changelog
|
||||
|
||||
## Installation and upgrade instructions
|
||||
|
||||
* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/).
|
||||
* Alternatively, read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)
|
||||
|
||||
The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
20
.github/release-notes/beta.md
vendored
Normal file
20
.github/release-notes/beta.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Welcome to release %version of Firefly III. This **beta** release contains the latest fixes, translations and features. It may be buggy, nor work as expected. You can download the release below, and adventurous Docker users can find this release under the `beta` tag.
|
||||
|
||||
:warning: Please be careful with this beta release, as it may not work as expected.
|
||||
|
||||
Alpha releases are created to test new features and fixes before they are included in a stable release. They are not recommended for production use. This release was created on %date and may contain unexpected bugs. Data loss is rare but possible.
|
||||
|
||||
## Changelog (not final)
|
||||
|
||||
%changelog
|
||||
|
||||
## Installation and upgrade instructions
|
||||
|
||||
* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/).
|
||||
* Alternatively, read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)
|
||||
|
||||
The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
20
.github/release-notes/branch.md
vendored
Normal file
20
.github/release-notes/branch.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Welcome to release %version of Firefly III. This branch-related release contains the latest fixes, translations and features. It is probably buggy and may not work as expected. You can download the release below, and adventurous Docker users can find this release under the `branch-*` tag.
|
||||
|
||||
:warning: Please be careful with this branch release, as it may not work as expected.
|
||||
|
||||
Branch releases are created to test large new features that are developed alongside the normal release flow. They are not recommended for production use. This release was created on %date and may contain unexpected bugs. Data loss is rare but possible.
|
||||
|
||||
## Changelog
|
||||
|
||||
There is no changelog for this release, as it is not final. However, [changelog.md](https://github.com/firefly-iii/firefly-iii/blob/develop/changelog.md) may already contain entries for the future release that this branch will be a part of.
|
||||
|
||||
## Installation and upgrade instructions
|
||||
|
||||
* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/).
|
||||
* Alternatively, read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)
|
||||
|
||||
The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
20
.github/release-notes/develop.md
vendored
Normal file
20
.github/release-notes/develop.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Welcome to the latest development release of Firefly III. This test release contains the absolute latest fixes, translations and features. It is probably buggy and may not work as expected. You can download the release below, and adventurous Docker users can find this release under the `develop` tag.
|
||||
|
||||
:warning: Please be careful with this pre-release, as it may not work as expected.
|
||||
|
||||
This release was created on %date and may contain unexpected bugs. Data loss is rare but possible.
|
||||
|
||||
## Changelog
|
||||
|
||||
The changelog for this release may not be up-to-date, so it is not included. However, [changelog.md](https://github.com/firefly-iii/firefly-iii/blob/develop/changelog.md) may already contain entries for the future release.
|
||||
|
||||
## Installation and upgrade instructions
|
||||
|
||||
* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/).
|
||||
* Alternatively, read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)
|
||||
|
||||
The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
16
.github/release-notes/release.md
vendored
Normal file
16
.github/release-notes/release.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
Welcome to release %version of Firefly III. It contains the latest fixes, translations and features. Docker users can find this release under the `latest` tag.
|
||||
|
||||
## Changelog
|
||||
|
||||
%changelog
|
||||
|
||||
## Installation and upgrade instructions
|
||||
|
||||
* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/).
|
||||
* Alternatively, read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)
|
||||
|
||||
The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
2
.github/workflows/close-duplicates.yml
vendored
2
.github/workflows/close-duplicates.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
close_duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/command@v2.0.1
|
||||
- uses: github/command@v2.0.2
|
||||
id: command
|
||||
with:
|
||||
allowed_contexts: "issue"
|
||||
|
115
.github/workflows/release.yml
vendored
115
.github/workflows/release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Import GPG key
|
||||
@@ -233,122 +233,15 @@ jobs:
|
||||
git push
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Extract changelog
|
||||
id: extract-changelog
|
||||
- name: Generate release description
|
||||
id: release-description
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
with:
|
||||
action: 'ff3:extract-changelog'
|
||||
action: "ff3:generate-release-notes firefly-iii ${{ github.event.inputs.version }}"
|
||||
output: 'output'
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ""
|
||||
- name: Describe new release
|
||||
run: |
|
||||
|
||||
# describe the development release.
|
||||
if [[ "develop" == "$version" ]]; then
|
||||
echo 'Describe the latest develop release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
echo "Weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo "This release was created on **$(date +'%Y-%m-%d %H:%M')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo "* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo ":warning: Please be careful with this pre-release, as it may not work as expected." >> output.txt
|
||||
|
||||
# donations!
|
||||
echo '' >> output.txt
|
||||
echo '### Support Firefly III' >> output.txt
|
||||
echo 'Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. For more information, please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information.' >> output.txt
|
||||
echo '' >> output.txt
|
||||
fi
|
||||
# describe a branch release
|
||||
if [[ "$version" == branch* ]]; then
|
||||
echo 'Describe a branch release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
echo "Irregular BRANCH release of Firefly III. This release contains specific features or changes. Docker users can find this release under the \`$version\` tag." >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo "This release was created on **$(date +'%Y-%m-%d %H:%M')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo "* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo ":warning: Please be careful with this branch pre-release, as it may not work as expected." >> output.txt
|
||||
fi
|
||||
# describe the main release
|
||||
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]] && [[ "$version" != *alpha* ]] && [[ "$version" != *beta* ]]; then
|
||||
echo 'Describe the latest release'
|
||||
sudo chown -R runner:docker output.txt
|
||||
|
||||
# the changelog is in output.txt
|
||||
mv output.txt output2.txt
|
||||
|
||||
touch output.txt
|
||||
echo '' >> output.txt
|
||||
echo "Welcome to release $version of Firefly III. It contains the the latest fixes, translations and features. Docker users can find this release under the \`latest\` tag." >> output.txt
|
||||
echo '' >> output.txt
|
||||
|
||||
# add changelog to file.
|
||||
cat output2.txt >> output.txt
|
||||
echo '' >> output.txt
|
||||
rm -f output2.txt
|
||||
|
||||
echo '### Instructions' >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
|
||||
# donations!
|
||||
echo '' >> output.txt
|
||||
echo '### Support Firefly III' >> output.txt
|
||||
echo 'Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. For more information, please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information.' >> output.txt
|
||||
echo '' >> output.txt
|
||||
fi
|
||||
|
||||
# describe alpha release
|
||||
if [[ "$version" == *alpha* ]]; then
|
||||
echo 'Describe an ALPHA release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
echo "Very early ALPHA release of Firefly III. This release contains specific features or changes. Docker users can find this release under the \`$version\` tag." >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo "This release was created on **$(date +'%Y-%m-%d %H:%M')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo '### Instructions' >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
|
||||
fi
|
||||
|
||||
# describe beta release
|
||||
if [[ "$version" == *beta* ]]; then
|
||||
echo 'Describe a BETA release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
echo "Very early BETA release of Firefly III. This release contains specific features or changes. Docker users can find this release under the \`$version\` tag." >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo "This release was created on **$(date +'%Y-%m-%d %H:%M')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo '### Instructions' >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
|
||||
fi
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Merge all into working branch
|
||||
run: |
|
||||
MERGE_INTO=develop
|
||||
|
@@ -31,6 +31,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\User;
|
||||
@@ -79,17 +80,20 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function accounts(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->getData();
|
||||
$types = $data['types'];
|
||||
$query = $data['query'];
|
||||
$date = $data['date'] ?? today(config('app.timezone'));
|
||||
$return = [];
|
||||
Timer::start(sprintf('AC accounts "%s"', $query));
|
||||
$result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit'));
|
||||
$data = $request->getData();
|
||||
$types = $data['types'];
|
||||
$query = $data['query'];
|
||||
$date = $data['date'] ?? today(config('app.timezone'));
|
||||
$return = [];
|
||||
$timer = Timer::getInstance();
|
||||
$timer->start(sprintf('AC accounts "%s"', $query));
|
||||
$result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit'));
|
||||
|
||||
// set date to subday + end-of-day for account balance. so it is at $date 23:59:59
|
||||
$date->endOfDay();
|
||||
|
||||
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
$nameWithBalance = $account->name;
|
||||
@@ -98,15 +102,11 @@ class AccountController extends Controller
|
||||
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
|
||||
// this one is correct.
|
||||
Log::debug(sprintf('accounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
|
||||
$balance = Steam::finalAccountBalance($account, $date);
|
||||
$balance = $allBalances[$account->id] ?? [];
|
||||
$key = $this->convertToPrimary && $currency->id !== $this->primaryCurrency->id ? 'pc_balance' : 'balance';
|
||||
$useCurrency = $this->convertToPrimary && $currency->id !== $this->primaryCurrency->id ? $this->primaryCurrency : $currency;
|
||||
$amount = $balance[$key] ?? '0';
|
||||
$nameWithBalance = sprintf(
|
||||
'%s (%s)',
|
||||
$account->name,
|
||||
app('amount')->formatAnything($useCurrency, $amount, false)
|
||||
);
|
||||
$nameWithBalance = sprintf('%s (%s)', $account->name, Amount::formatAnything($useCurrency, $amount, false));
|
||||
}
|
||||
|
||||
$return[] = [
|
||||
@@ -138,7 +138,7 @@ class AccountController extends Controller
|
||||
return $posA - $posB;
|
||||
}
|
||||
);
|
||||
Timer::stop(sprintf('AC accounts "%s"', $query));
|
||||
$timer->stop(sprintf('AC accounts "%s"', $query));
|
||||
|
||||
return response()->api($return);
|
||||
}
|
||||
|
@@ -24,13 +24,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Api\V1\Requests\Data\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
@@ -40,7 +38,6 @@ use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ApiSupport;
|
||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -52,6 +49,8 @@ class AccountController extends Controller
|
||||
use ApiSupport;
|
||||
use CollectsAccountsFromFilter;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
private ChartData $chartData;
|
||||
private AccountRepositoryInterface $repository;
|
||||
|
||||
@@ -63,11 +62,11 @@ class AccountController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$this->chartData = new ChartData();
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->repository->setUser($user);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -75,11 +74,9 @@ class AccountController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO fix documentation
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function dashboard(ChartRequest $request): JsonResponse
|
||||
public function overview(ChartRequest $request): JsonResponse
|
||||
{
|
||||
$queryParameters = $request->getParameters();
|
||||
$accounts = $this->getAccountList($queryParameters);
|
||||
@@ -110,24 +107,30 @@ class AccountController extends Controller
|
||||
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToPrimary);
|
||||
|
||||
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
$pcPrevious = null;
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
$pcPrevious = null;
|
||||
if (!$currency instanceof TransactionCurrency) {
|
||||
$currency = $this->default;
|
||||
}
|
||||
$currentSet = [
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
|
||||
// the currency that belongs to the account.
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
|
||||
// the primary currency
|
||||
'primary_currency_id' => (string)$this->primaryCurrency->id,
|
||||
|
||||
// the default currency of the user (could be the same!)
|
||||
'date' => $params['start']->toAtomString(),
|
||||
'start' => $params['start']->toAtomString(),
|
||||
'end' => $params['end']->toAtomString(),
|
||||
'start_date' => $params['start']->toAtomString(),
|
||||
'end_date' => $params['end']->toAtomString(),
|
||||
'type' => 'line',
|
||||
'yAxisID' => 0,
|
||||
'period' => '1D',
|
||||
'entries' => [],
|
||||
];
|
||||
@@ -150,7 +153,7 @@ class AccountController extends Controller
|
||||
|
||||
|
||||
// do the same for the primary currency balance, if relevant:
|
||||
$pcBalance = null;
|
||||
$pcBalance = null;
|
||||
if ($this->convertToPrimary) {
|
||||
$pcBalance = array_key_exists($format, $range) ? $range[$format]['pc_balance'] : $pcPrevious;
|
||||
$pcPrevious = $pcBalance;
|
||||
@@ -162,97 +165,12 @@ class AccountController extends Controller
|
||||
$this->chartData->add($currentSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/charts/getChartAccountOverview
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function overview(DateRequest $request): JsonResponse
|
||||
{
|
||||
// parameters for chart:
|
||||
$dates = $request->getAll();
|
||||
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = $dates['start'];
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $dates['end'];
|
||||
|
||||
// set dates to end of day + start of day:
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
|
||||
$frontPageIds = $this->getFrontPageAccountIds();
|
||||
$accounts = $this->repository->getAccountsById($frontPageIds);
|
||||
$chartData = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Rendering chart data for account %s (%d)', $account->name, $account->id));
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? $this->primaryCurrency;
|
||||
$currentStart = clone $start;
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToPrimary);
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
$pcPrevious = null;
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'start_date' => $start->toAtomString(),
|
||||
'end_date' => $end->toAtomString(),
|
||||
'type' => 'line', // line, area or bar
|
||||
'yAxisID' => 0, // 0, 1, 2
|
||||
'entries' => [],
|
||||
];
|
||||
|
||||
// add "pc_entries" if convertToPrimary is true:
|
||||
if ($this->convertToPrimary) {
|
||||
$currentSet['pc_entries'] = [];
|
||||
$currentSet['primary_currency_id'] = (string)$this->primaryCurrency->id;
|
||||
$currentSet['primary_currency_code'] = $this->primaryCurrency->code;
|
||||
$currentSet['primary_currency_symbol'] = $this->primaryCurrency->symbol;
|
||||
$currentSet['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
|
||||
$pcPrevious = array_values($range)[0]['pc_balance'];
|
||||
|
||||
}
|
||||
|
||||
// also get the primary balance if convertToPrimary is true:
|
||||
while ($currentStart <= $end) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
|
||||
// balance is based on "balance" from the $range variable.
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
|
||||
// do the same for the primary balance, if relevant:
|
||||
$pcBalance = null;
|
||||
if ($this->convertToPrimary) {
|
||||
$pcBalance = array_key_exists($format, $range) ? $range[$format]['pc_balance'] : $pcPrevious;
|
||||
$pcPrevious = $pcBalance;
|
||||
$currentSet['pc_entries'][$label] = $pcBalance;
|
||||
}
|
||||
|
||||
$currentStart->addDay();
|
||||
|
||||
}
|
||||
$chartData[] = $currentSet;
|
||||
}
|
||||
|
||||
return response()->json($chartData);
|
||||
}
|
||||
|
||||
private function getFrontPageAccountIds(): array
|
||||
{
|
||||
$defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
|
||||
|
||||
/** @var Preference $frontpage */
|
||||
$frontpage = Preferences::get('frontpageAccounts', $defaultSet);
|
||||
$frontpage = Preferences::get('frontpageAccounts', $defaultSet);
|
||||
|
||||
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
|
||||
$frontpage->data = $defaultSet;
|
||||
|
96
app/Api/V1/Controllers/Chart/BalanceController.php
Normal file
96
app/Api/V1/Controllers/Chart/BalanceController.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Chart\ChartData;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Http\Api\AccountBalanceGrouped;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* Class BalanceController
|
||||
*/
|
||||
class BalanceController extends Controller
|
||||
{
|
||||
use CleansChartData;
|
||||
use CollectsAccountsFromFilter;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
private array $chartData;
|
||||
private GroupCollectorInterface $collector;
|
||||
private AccountRepositoryInterface $repository;
|
||||
|
||||
// private TransactionCurrency $default;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->collector = app(GroupCollectorInterface::class);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->collector->setUserGroup($userGroup);
|
||||
$this->chartData = [];
|
||||
// $this->default = app('amount')->getPrimaryCurrency();
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The code is practically a duplicate of ReportController::operations.
|
||||
*
|
||||
* Currency is up to the account/transactions in question, but conversion to the default
|
||||
* currency is possible.
|
||||
*
|
||||
* If the transaction being processed is already in native currency OR if the
|
||||
* foreign amount is in the native currency, the amount will not be converted.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function balance(ChartRequest $request): JsonResponse
|
||||
{
|
||||
$queryParameters = $request->getParameters();
|
||||
$accounts = $this->getAccountList($queryParameters);
|
||||
|
||||
// get journals for entire period:
|
||||
|
||||
$this->collector->setRange($queryParameters['start'], $queryParameters['end'])
|
||||
->withAccountInformation()
|
||||
->setXorAccounts($accounts)
|
||||
->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::RECONCILIATION->value, TransactionTypeEnum::TRANSFER->value])
|
||||
;
|
||||
$journals = $this->collector->getExtractedJournals();
|
||||
|
||||
$object = new AccountBalanceGrouped();
|
||||
$object->setPreferredRange($queryParameters['period']);
|
||||
$object->setPrimary($this->primaryCurrency);
|
||||
$object->setAccounts($accounts);
|
||||
$object->setJournals($journals);
|
||||
$object->setStart($queryParameters['start']);
|
||||
$object->setEnd($queryParameters['end']);
|
||||
$object->groupByCurrencyAndPeriod();
|
||||
$data = $object->convertToChartData();
|
||||
foreach ($data as $entry) {
|
||||
$this->chartData[] = $entry;
|
||||
}
|
||||
$this->chartData= $this->clean($this->chartData);
|
||||
|
||||
return response()->json($this->chartData);
|
||||
}
|
||||
}
|
@@ -50,7 +50,7 @@ class BudgetController extends Controller
|
||||
use CleansChartData;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
protected OperationsRepositoryInterface $opsRepository;
|
||||
private BudgetLimitRepositoryInterface $blRepository;
|
||||
@@ -81,15 +81,15 @@ class BudgetController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse
|
||||
public function overview(DateRequest $request): JsonResponse
|
||||
{
|
||||
$params = $request->getAll();
|
||||
$params = $request->getAll();
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = $params['start'];
|
||||
$start = $params['start'];
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $params['end'];
|
||||
$end = $params['end'];
|
||||
|
||||
// code from FrontpageChartGenerator, but not in separate class
|
||||
$budgets = $this->repository->getActiveBudgets();
|
||||
@@ -110,18 +110,27 @@ class BudgetController extends Controller
|
||||
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get all limits:
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
|
||||
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
|
||||
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
|
||||
$converter = new ExchangeRateConverter();
|
||||
$currencies = [$this->primaryCurrency->id => $this->primaryCurrency,];
|
||||
|
||||
/**
|
||||
* @var int $currencyId
|
||||
* @var int $currencyId
|
||||
* @var array $row
|
||||
*/
|
||||
foreach ($expenses as $currencyId => $row) {
|
||||
// budgeted, left and overspent are now 0.
|
||||
$limit = $this->filterLimit($currencyId, $limits);
|
||||
$limit = $this->filterLimit($currencyId, $limits);
|
||||
|
||||
// primary currency entries
|
||||
$row['pc_budgeted'] = '0';
|
||||
$row['pc_spent'] = '0';
|
||||
$row['pc_left'] = '0';
|
||||
$row['pc_overspent'] = '0';
|
||||
|
||||
if (null !== $limit) {
|
||||
$row['budgeted'] = $limit->amount;
|
||||
$row['left'] = bcsub($row['budgeted'], bcmul($row['spent'], '-1'));
|
||||
@@ -129,6 +138,21 @@ class BudgetController extends Controller
|
||||
$row['left'] = 1 === bccomp($row['left'], '0') ? $row['left'] : '0';
|
||||
$row['overspent'] = 1 === bccomp($row['overspent'], '0') ? $row['overspent'] : '0';
|
||||
}
|
||||
|
||||
// convert data if necessary.
|
||||
if (true === $this->convertToPrimary && $currencyId !== $this->primaryCurrency->id) {
|
||||
$currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
|
||||
$row['pc_budgeted'] = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $row['budgeted']);
|
||||
$row['pc_spent'] = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $row['spent']);
|
||||
$row['pc_left'] = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $row['left']);
|
||||
$row['pc_overspent'] = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $row['overspent']);
|
||||
}
|
||||
if (true === $this->convertToPrimary && $currencyId === $this->primaryCurrency->id) {
|
||||
$row['pc_budgeted'] = $row['budgeted'];
|
||||
$row['pc_spent'] = $row['spent'];
|
||||
$row['pc_left'] = $row['left'];
|
||||
$row['pc_overspent'] = $row['overspent'];
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
@@ -140,23 +164,39 @@ class BudgetController extends Controller
|
||||
// }
|
||||
|
||||
// is always an array
|
||||
$return = [];
|
||||
$return = [];
|
||||
foreach ($rows as $row) {
|
||||
$current = [
|
||||
'label' => $budget->name,
|
||||
'currency_id' => (string)$row['currency_id'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_name' => $row['currency_name'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
'period' => null,
|
||||
'start' => $row['start'],
|
||||
'end' => $row['end'],
|
||||
'entries' => [
|
||||
|
||||
'primary_currency_id' => (string)$this->primaryCurrency->id,
|
||||
'primary_currency_name' => $this->primaryCurrency->name,
|
||||
'primary_currency_code' => $this->primaryCurrency->code,
|
||||
'primary_currency_symbol' => $this->primaryCurrency->symbol,
|
||||
'primary_currency_decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
|
||||
'period' => null,
|
||||
'date' => $row['start'],
|
||||
'start_date' => $row['start'],
|
||||
'end_date' => $row['end'],
|
||||
'yAxisID' => 0,
|
||||
'type' => 'bar',
|
||||
'entries' => [
|
||||
'budgeted' => $row['budgeted'],
|
||||
'spent' => $row['spent'],
|
||||
'left' => $row['left'],
|
||||
'overspent' => $row['overspent'],
|
||||
],
|
||||
'pc_entries' => [
|
||||
'budgeted' => $row['pc_budgeted'],
|
||||
'spent' => '0',
|
||||
'left' => '0',
|
||||
'overspent' => '0',
|
||||
],
|
||||
];
|
||||
$return[] = $current;
|
||||
}
|
||||
@@ -191,7 +231,7 @@ class BudgetController extends Controller
|
||||
* This array contains the expenses in this budget. Grouped per currency.
|
||||
* The grouping is on the main currency only.
|
||||
*
|
||||
* @var int $currencyId
|
||||
* @var int $currencyId
|
||||
* @var array $block
|
||||
*/
|
||||
foreach ($spent as $currencyId => $block) {
|
||||
@@ -209,7 +249,7 @@ class BudgetController extends Controller
|
||||
'left' => '0',
|
||||
'overspent' => '0',
|
||||
];
|
||||
$currentBudgetArray = $block['budgets'][$budgetId];
|
||||
$currentBudgetArray = $block['budgets'][$budgetId];
|
||||
|
||||
// var_dump($return);
|
||||
/** @var array $journal */
|
||||
@@ -250,7 +290,7 @@ class BudgetController extends Controller
|
||||
private function processLimit(Budget $budget, BudgetLimit $limit): array
|
||||
{
|
||||
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
|
||||
$end = clone $limit->end_date;
|
||||
$end = clone $limit->end_date;
|
||||
$end->endOfDay();
|
||||
$spent = $this->opsRepository->listExpenses($limit->start_date, $end, null, new Collection([$budget]));
|
||||
$limitCurrencyId = $limit->transaction_currency_id;
|
||||
@@ -258,8 +298,8 @@ class BudgetController extends Controller
|
||||
/** @var array $entry */
|
||||
// only spent the entry where the entry's currency matches the budget limit's currency
|
||||
// so $filtered will only have 1 or 0 entries
|
||||
$filtered = array_filter($spent, fn ($entry) => $entry['currency_id'] === $limitCurrencyId);
|
||||
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
||||
$filtered = array_filter($spent, fn($entry) => $entry['currency_id'] === $limitCurrencyId);
|
||||
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
||||
if (1 === count($result)) {
|
||||
$compare = bccomp($limit->amount, (string)app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||
$result[$limitCurrencyId]['budgeted'] = $limit->amount;
|
||||
|
@@ -34,6 +34,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
@@ -77,10 +78,10 @@ class CategoryController extends Controller
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse
|
||||
public function overview(DateRequest $request): JsonResponse
|
||||
{
|
||||
/** @var Carbon $start */
|
||||
$start = $this->parameters->get('start');
|
||||
$start = $this->parameters->get('start');
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $this->parameters->get('end');
|
||||
@@ -91,11 +92,11 @@ class CategoryController extends Controller
|
||||
|
||||
// get journals for entire period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->withAccountInformation();
|
||||
$collector->setXorAccounts($accounts)->withCategoryInformation();
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::RECONCILIATION->value]);
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$journals = $collector->getExtractedJournals();
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
@@ -108,44 +109,62 @@ class CategoryController extends Controller
|
||||
$currencyCode = (string)$currency->code;
|
||||
$currencySymbol = (string)$currency->symbol;
|
||||
$currencyDecimalPlaces = (int)$currency->decimal_places;
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
$amount = Steam::positive($journal['amount']);
|
||||
$pcAmount = null;
|
||||
|
||||
// overrule if necessary:
|
||||
if ($this->convertToPrimary && $journalCurrencyId === $this->primaryCurrency->id) {
|
||||
$pcAmount = $amount;
|
||||
}
|
||||
if ($this->convertToPrimary && $journalCurrencyId !== $this->primaryCurrency->id) {
|
||||
$currencyId = (int)$this->primaryCurrency->id;
|
||||
$currencyName = (string)$this->primaryCurrency->name;
|
||||
$currencyCode = (string)$this->primaryCurrency->code;
|
||||
$currencySymbol = (string)$this->primaryCurrency->symbol;
|
||||
$currencyDecimalPlaces = (int)$this->primaryCurrency->decimal_places;
|
||||
$convertedAmount = $converter->convert($currency, $this->primaryCurrency, $journal['date'], $amount);
|
||||
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->primaryCurrency->code, $convertedAmount));
|
||||
$amount = $convertedAmount;
|
||||
$pcAmount = $converter->convert($currency, $this->primaryCurrency, $journal['date'], $amount);
|
||||
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->primaryCurrency->code, $pcAmount));
|
||||
}
|
||||
|
||||
|
||||
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
||||
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
||||
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
||||
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
||||
// create arrays
|
||||
$return[$key] ??= [
|
||||
'label' => $categoryName,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
'period' => null,
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
'amount' => '0',
|
||||
'label' => $categoryName,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
'primary_currency_id' => (string)$this->primaryCurrency->id,
|
||||
'primary_currency_name' => (string)$this->primaryCurrency->name,
|
||||
'primary_currency_code' => (string)$this->primaryCurrency->code,
|
||||
'primary_currency_symbol' => (string)$this->primaryCurrency->symbol,
|
||||
'primary_currency_decimal_places' => (int)$this->primaryCurrency->decimal_places,
|
||||
'period' => null,
|
||||
'start_date' => $start->toAtomString(),
|
||||
'end_date' => $end->toAtomString(),
|
||||
'yAxisID' => 0,
|
||||
'type' => 'bar',
|
||||
'entries' => [
|
||||
'spent' => '0'
|
||||
],
|
||||
'pc_entries' => [
|
||||
'spent' => '0'
|
||||
],
|
||||
];
|
||||
|
||||
// add monies
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string)$amount);
|
||||
$return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], (string)$amount);
|
||||
if (null !== $pcAmount) {
|
||||
$return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], (string)$pcAmount);
|
||||
}
|
||||
}
|
||||
$return = array_values($return);
|
||||
$return = array_values($return);
|
||||
|
||||
// order by amount
|
||||
usort($return, static fn (array $a, array $b) => (float)$a['amount'] < (float)$b['amount'] ? 1 : -1);
|
||||
usort($return, static fn(array $a, array $b) => (float)$a['entries']['spent'] < (float)$b['entries']['spent'] ? 1 : -1);
|
||||
|
||||
return response()->json($this->clean($return));
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\AttachmentTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
@@ -117,6 +118,13 @@ class ListController extends Controller
|
||||
$count = $collection->count();
|
||||
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$piggyBanks = $enrichment->enrich($piggyBanks);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.accounts.piggy-banks', [$account->id]).$this->buildParams());
|
||||
@@ -125,7 +133,7 @@ class ListController extends Controller
|
||||
$transformer = app(PiggyBankTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy_banks');
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy-banks');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Category\StoreRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\CategoryEnrichment;
|
||||
use FireflyIII\Transformers\CategoryTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -72,6 +74,15 @@ class StoreController extends Controller
|
||||
$transformer = app(CategoryTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new CategoryEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$category = $enrichment->enrichSingle($category);
|
||||
|
||||
$resource = new Item($category, $transformer, 'categories');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Category\UpdateRequest;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\CategoryEnrichment;
|
||||
use FireflyIII\Transformers\CategoryTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -71,6 +73,15 @@ class UpdateController extends Controller
|
||||
$transformer = app(CategoryTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new CategoryEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$category = $enrichment->enrichSingle($category);
|
||||
|
||||
$resource = new Item($category, $transformer, 'categories');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
|
@@ -59,23 +59,24 @@ class DestroyController extends Controller
|
||||
|
||||
public function destroy(DestroyRequest $request, TransactionCurrency $from, TransactionCurrency $to): JsonResponse
|
||||
{
|
||||
$date = $request->getDate();
|
||||
if (!$date instanceof Carbon) {
|
||||
throw new ValidationException('Date is required');
|
||||
}
|
||||
$rate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if (!$rate instanceof CurrencyExchangeRate) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$this->repository->deleteRate($rate);
|
||||
$this->repository->deleteRates($from, $to);
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
public function destroySingle(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
public function destroySingleById(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$this->repository->deleteRate($exchangeRate);
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
public function destroySingleByDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if(null !== $exchangeRate) {
|
||||
$this->repository->deleteRate($exchangeRate);
|
||||
}
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
@@ -39,7 +40,7 @@ class IndexController extends Controller
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
public const string RESOURCE_KEY = 'currency_exchange_rates';
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::OWNER];
|
||||
private ExchangeRateRepositoryInterface $repository;
|
||||
|
||||
public function __construct()
|
||||
|
@@ -24,7 +24,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
@@ -32,6 +34,7 @@ use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class ShowController
|
||||
@@ -41,7 +44,7 @@ class ShowController extends Controller
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
public const string RESOURCE_KEY = 'exchange-rates';
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::OWNER];
|
||||
private ExchangeRateRepositoryInterface $repository;
|
||||
|
||||
public function __construct()
|
||||
@@ -75,7 +78,7 @@ class ShowController extends Controller
|
||||
;
|
||||
}
|
||||
|
||||
public function showSingle(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
public function showSingleById(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
@@ -85,4 +88,20 @@ class ShowController extends Controller
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function showSingleByDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if(null === $exchangeRate) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@@ -24,20 +24,27 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
|
||||
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreRequest;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreByCurrenciesRequest;
|
||||
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreByDateRequest;
|
||||
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class StoreController extends Controller
|
||||
{
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
public const string RESOURCE_KEY = 'exchange-rates';
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::OWNER];
|
||||
private ExchangeRateRepositoryInterface $repository;
|
||||
|
||||
public function __construct()
|
||||
@@ -53,15 +60,76 @@ class StoreController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function storeByCurrencies(StoreByCurrenciesRequest $request, TransactionCurrency $from, TransactionCurrency $to): JsonResponse
|
||||
{
|
||||
|
||||
$data = $request->getAll();
|
||||
$collection = new Collection();
|
||||
|
||||
foreach ($data as $date => $rate) {
|
||||
$date = Carbon::createFromFormat('Y-m-d', $date);
|
||||
$existing = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if (null !== $existing) {
|
||||
// update existing rate.
|
||||
$existing = $this->repository->updateExchangeRate($existing, $rate);
|
||||
$collection->push($existing);
|
||||
continue;
|
||||
}
|
||||
$new = $this->repository->storeExchangeRate($from, $to, $rate, $date);
|
||||
$collection->push($new);
|
||||
}
|
||||
|
||||
$count = $collection->count();
|
||||
$paginator = new LengthAwarePaginator($collection, $count, $count, 1);
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function storeByDate(StoreByDateRequest $request, Carbon $date): JsonResponse
|
||||
{
|
||||
|
||||
$data = $request->getAll();
|
||||
$from = $request->getFromCurrency();
|
||||
$collection = new Collection();
|
||||
foreach ($data['rates'] as $key => $rate) {
|
||||
$to = TransactionCurrency::where('code', $key)->first();
|
||||
if (null === $to) {
|
||||
continue; // should not happen.
|
||||
}
|
||||
$existing = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if (null !== $existing) {
|
||||
// update existing rate.
|
||||
$existing = $this->repository->updateExchangeRate($existing, $rate);
|
||||
$collection->push($existing);
|
||||
continue;
|
||||
}
|
||||
$new = $this->repository->storeExchangeRate($from, $to, $rate, $date);
|
||||
$collection->push($new);
|
||||
}
|
||||
|
||||
$count = $collection->count();
|
||||
$paginator = new LengthAwarePaginator($collection, $count, $count, 1);
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function store(StoreRequest $request): JsonResponse
|
||||
{
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
$from = $request->getFromCurrency();
|
||||
$to = $request->getToCurrency();
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
$from = $request->getFromCurrency();
|
||||
$to = $request->getToCurrency();
|
||||
|
||||
// already has rate?
|
||||
$object = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
$object = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if ($object instanceof CurrencyExchangeRate) {
|
||||
// just update it, no matter.
|
||||
$rate = $this->repository->updateExchangeRate($object, $rate, $date);
|
||||
@@ -76,7 +144,6 @@ class StoreController extends Controller
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject(self::RESOURCE_KEY, $rate, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
@@ -24,20 +24,24 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
|
||||
|
||||
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\UpdateRequest;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\UpdateRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class UpdateController extends Controller
|
||||
{
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
public const string RESOURCE_KEY = 'exchange-rates';
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::OWNER];
|
||||
private ExchangeRateRepositoryInterface $repository;
|
||||
|
||||
public function __construct()
|
||||
@@ -53,7 +57,7 @@ class UpdateController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function update(UpdateRequest $request, CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
public function updateById(UpdateRequest $request, CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
@@ -63,7 +67,24 @@ class UpdateController extends Controller
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function updateByDate(UpdateRequest $request, TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if (null === $exchangeRate) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
$exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date);
|
||||
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
@@ -124,6 +125,13 @@ class ListController extends Controller
|
||||
$count = $collection->count();
|
||||
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$piggyBanks = $enrichment->enrich($piggyBanks);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.object-groups.piggy-banks', [$objectGroup->id]).$this->buildParams());
|
||||
@@ -132,7 +140,7 @@ class ListController extends Controller
|
||||
$transformer = app(PiggyBankTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy_banks');
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy-banks');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
|
@@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEventEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use FireflyIII\Transformers\AttachmentTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankEventTransformer;
|
||||
@@ -148,6 +149,13 @@ class ListController extends Controller
|
||||
$count = $collection->count();
|
||||
$events = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEventEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$events = $enrichment->enrich($events);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($events, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.piggy-banks.events', [$piggyBank->id]).$this->buildParams());
|
||||
@@ -156,7 +164,7 @@ class ListController extends Controller
|
||||
$transformer = app(PiggyBankEventTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($events, $transformer, 'piggy_bank_events');
|
||||
$resource = new FractalCollection($events, $transformer, sprintf('piggy-banks/%d/events', $piggyBank->id));
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
@@ -77,6 +79,13 @@ class ShowController extends Controller
|
||||
$count = $collection->count();
|
||||
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$piggyBanks = $enrichment->enrich($piggyBanks);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.piggy-banks.index').$this->buildParams());
|
||||
@@ -85,7 +94,7 @@ class ShowController extends Controller
|
||||
$transformer = app(PiggyBankTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy_banks');
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy-banks');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
@@ -101,11 +110,19 @@ class ShowController extends Controller
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$piggyBank = $enrichment->enrichSingle($piggyBank);
|
||||
|
||||
|
||||
/** @var PiggyBankTransformer $transformer */
|
||||
$transformer = app(PiggyBankTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new Item($piggyBank, $transformer, 'piggy_banks');
|
||||
$resource = new Item($piggyBank, $transformer, 'piggy-banks');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\PiggyBank\StoreRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -68,6 +70,13 @@ class StoreController extends Controller
|
||||
$piggyBank = $this->repository->store($request->getAll());
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$piggyBank = $enrichment->enrichSingle($piggyBank);
|
||||
|
||||
/** @var PiggyBankTransformer $transformer */
|
||||
$transformer = app(PiggyBankTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\PiggyBank\UpdateRequest;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -70,13 +72,20 @@ class UpdateController extends Controller
|
||||
$this->repository->setCurrentAmount($piggyBank, $data['current_amount']);
|
||||
}
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$piggyBank = $enrichment->enrichSingle($piggyBank);
|
||||
|
||||
$manager = $this->getManager();
|
||||
|
||||
/** @var PiggyBankTransformer $transformer */
|
||||
$transformer = app(PiggyBankTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new Item($piggyBank, $transformer, 'piggy_banks');
|
||||
$resource = new Item($piggyBank, $transformer, 'piggy-banks');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
@@ -76,17 +78,24 @@ class ShowController extends Controller
|
||||
// get list of budgets. Count it and split it.
|
||||
$collection = $this->repository->get();
|
||||
$count = $collection->count();
|
||||
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$recurrences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrences = $enrichment->enrich($recurrences);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.recurrences.index').$this->buildParams());
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'recurrences');
|
||||
$resource = new FractalCollection($recurrences, $transformer, 'recurrences');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
@@ -102,6 +111,13 @@ class ShowController extends Controller
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrence = $enrichment->enrichSingle($recurrence);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Recurrence\StoreRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -69,6 +71,13 @@ class StoreController extends Controller
|
||||
$recurrence = $this->repository->store($data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrence = $enrichment->enrichSingle($recurrence);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Recurrence\UpdateRequest;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -67,6 +69,13 @@ class UpdateController extends Controller
|
||||
$recurrence = $this->repository->update($recurrence, $data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrence = $enrichment->enrichSingle($recurrence);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalAPIRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEventEnrichment;
|
||||
use FireflyIII\Transformers\AttachmentTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankEventTransformer;
|
||||
use FireflyIII\Transformers\TransactionLinkTransformer;
|
||||
@@ -113,6 +114,14 @@ class ListController extends Controller
|
||||
}
|
||||
$count = $collection->count();
|
||||
$events = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEventEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$events = $enrichment->enrich($events);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($events, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.transactions.piggy-bank-events', [$transactionGroup->id]).$this->buildParams());
|
||||
|
@@ -44,6 +44,7 @@ use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
@@ -276,17 +277,24 @@ class ListController extends Controller
|
||||
}
|
||||
);
|
||||
$count = $collection->count();
|
||||
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$recurrences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrences = $enrichment->enrich($recurrences);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.currencies.recurrences', [$currency->code]).$this->buildParams());
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($piggyBanks, $transformer, 'recurrences');
|
||||
$resource = new FractalCollection($recurrences, $transformer, 'recurrences');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
|
@@ -248,10 +248,10 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
|
||||
.' + '.app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
'sub_title' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
|
||||
.' + '.Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('spent-in-%s', $currency->code),
|
||||
@@ -261,7 +261,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -273,7 +273,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -289,10 +289,10 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => app('amount')->formatAnything($currency, '0', false)
|
||||
.' + '.app('amount')->formatAnything($currency, '0', false),
|
||||
'sub_title' => Amount::formatAnything($currency, '0', false)
|
||||
.' + '.Amount::formatAnything($currency, '0', false),
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('spent-in-%s', $currency->code),
|
||||
@@ -302,7 +302,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -314,7 +314,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -405,7 +405,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -424,7 +424,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -443,7 +443,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -455,7 +455,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -493,14 +493,9 @@ class BasicController extends Controller
|
||||
'currency_code' => $currencies[$currencyId]->code,
|
||||
'currency_symbol' => $currencies[$currencyId]->symbol,
|
||||
'currency_decimal_places' => $currencies[$currencyId]->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
|
||||
'value_parsed' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$currencies[$currencyId]->symbol,
|
||||
$currencies[$currencyId]->decimal_places,
|
||||
$availableBudget,
|
||||
false
|
||||
),
|
||||
'sub_title' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
|
||||
];
|
||||
}
|
||||
foreach ($spent as $row) {
|
||||
@@ -529,18 +524,14 @@ class BasicController extends Controller
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_symbol' => $row['currency_symbol'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
|
||||
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$row['currency_symbol'],
|
||||
$row['currency_decimal_places'],
|
||||
$perDay,
|
||||
false
|
||||
),
|
||||
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
|
||||
];
|
||||
}
|
||||
unset($leftToSpend);
|
||||
if (0 === count($return)) {
|
||||
$days = (int) $start->diffInDays($end, true) + 1;
|
||||
// a small trick to get every expense in this period, regardless of budget.
|
||||
$spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection());
|
||||
foreach ($spent as $row) {
|
||||
@@ -563,14 +554,9 @@ class BasicController extends Controller
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_symbol' => $row['currency_symbol'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
|
||||
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$row['currency_symbol'],
|
||||
$row['currency_decimal_places'],
|
||||
$perDay,
|
||||
false
|
||||
),
|
||||
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -587,9 +573,9 @@ class BasicController extends Controller
|
||||
// 'currency_code' => $currency->code,
|
||||
// 'currency_symbol' => $currency->symbol,
|
||||
// 'currency_decimal_places' => $currency->decimal_places,
|
||||
// 'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
// 'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
// 'local_icon' => 'money',
|
||||
// 'sub_title' => app('amount')->formatFlat(
|
||||
// 'sub_title' => Amount::formatFlat(
|
||||
// $currency->symbol,
|
||||
// $currency->decimal_places,
|
||||
// '0',
|
||||
@@ -642,7 +628,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $data['currency_code'],
|
||||
'currency_symbol' => $data['currency_symbol'],
|
||||
'currency_decimal_places' => $data['currency_decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
|
||||
'value_parsed' => Amount::formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
|
||||
'local_icon' => 'line-chart',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -656,7 +642,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $this->primaryCurrency->code,
|
||||
'currency_symbol' => $this->primaryCurrency->symbol,
|
||||
'currency_decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
|
||||
'value_parsed' => Amount::formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
|
||||
'local_icon' => 'line-chart',
|
||||
'sub_title' => '',
|
||||
];
|
||||
|
@@ -54,6 +54,7 @@ class CronController extends Controller
|
||||
$return['exchange_rates'] = $this->exchangeRatesCronJob($config['force'], $config['date']);
|
||||
}
|
||||
$return['bill_notifications'] = $this->billWarningCronJob($config['force'], $config['date']);
|
||||
$return['webhooks'] = $this->webhookCronJob($config['force'], $config['date']);
|
||||
|
||||
return response()->api($return);
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ class DestroyRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
// 'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* StoreRequest.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreByCurrenciesRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'*' => 'required|numeric|min:0.0000000001',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
static function (Validator $validator): void {
|
||||
$data = $validator->getData() ?? [];
|
||||
foreach ($data as $date => $rate) {
|
||||
try {
|
||||
$date = Carbon::createFromFormat('Y-m-d', $date);
|
||||
} catch (InvalidFormatException $e) {
|
||||
$validator->errors()->add('date', trans('validation.date',['attribute' => 'date']));
|
||||
return;
|
||||
}
|
||||
if (!is_numeric($rate)) {
|
||||
$validator->errors()->add('rate', trans('validation.number',['attribute' => 'rate']));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* StoreRequest.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate;
|
||||
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreByDateRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
return [
|
||||
'from' => $this->get('from'),
|
||||
'rates' => $this->get('rates', []),
|
||||
];
|
||||
}
|
||||
|
||||
public function getFromCurrency(): TransactionCurrency
|
||||
{
|
||||
return TransactionCurrency::where('code', $this->get('from'))->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'from' => 'required|exists:transaction_currencies,code',
|
||||
'rates' => 'required|array',
|
||||
'rates.*' => 'required|numeric|min:0.0000000001',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$from = $this->getFromCurrency();
|
||||
|
||||
$validator->after(
|
||||
static function (Validator $validator) use ($from): void {
|
||||
$data = $validator->getData();
|
||||
$rates = $data['rates'] ?? [];
|
||||
if (0 === count($rates)) {
|
||||
$validator->errors()->add('rates', 'No rates given.');
|
||||
return;
|
||||
}
|
||||
foreach ($rates as $key => $entry) {
|
||||
if ($key === $from->code) {
|
||||
$validator->errors()->add(sprintf('rates.%s', $key), trans('validation.convert_to_itself', ['code' => $key]));
|
||||
continue;
|
||||
}
|
||||
$to = TransactionCurrency::where('code', $key)->first();
|
||||
if (null === $to) {
|
||||
$validator->errors()->add(sprintf('rates.%s', $key), trans('validation.invalid_currency_code', ['code' => $key]));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -52,6 +52,8 @@ class UpdateRequest extends FormRequest
|
||||
return [
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'rate' => 'required|numeric|gt:0',
|
||||
'from' => 'nullable|exists:transaction_currencies,code',
|
||||
'to' => 'nullable|exists:transaction_currencies,code',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -72,6 +73,8 @@ class CorrectsPrimaryCurrencyAmounts extends Command
|
||||
/** @var UserGroupRepositoryInterface $repository */
|
||||
$repository = app(UserGroupRepositoryInterface::class);
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
/** @var UserGroup $userGroup */
|
||||
foreach ($repository->getAll() as $userGroup) {
|
||||
$this->recalculateForGroup($userGroup);
|
||||
@@ -87,8 +90,7 @@ class CorrectsPrimaryCurrencyAmounts extends Command
|
||||
$this->recalculateAccounts($userGroup);
|
||||
|
||||
// do a check with the group's currency so we can skip some stuff.
|
||||
Preferences::mark();
|
||||
$currency = app('amount')->getPrimaryCurrencyByUserGroup($userGroup);
|
||||
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
|
||||
|
||||
$this->recalculatePiggyBanks($userGroup, $currency);
|
||||
$this->recalculateBudgets($userGroup, $currency);
|
||||
|
@@ -32,6 +32,7 @@ use FireflyIII\Support\Cronjobs\BillWarningCronjob;
|
||||
use FireflyIII\Support\Cronjobs\ExchangeRatesCronjob;
|
||||
use FireflyIII\Support\Cronjobs\RecurringCronjob;
|
||||
use FireflyIII\Support\Cronjobs\UpdateCheckCronjob;
|
||||
use FireflyIII\Support\Cronjobs\WebhookCronjob;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
@@ -50,6 +51,7 @@ class Cron extends Command
|
||||
{--create-recurring : Create recurring transactions. Other tasks will be skipped unless also requested.}
|
||||
{--create-auto-budgets : Create auto budgets. Other tasks will be skipped unless also requested.}
|
||||
{--send-bill-warnings : Send bill warnings. Other tasks will be skipped unless also requested.}
|
||||
{--send-webhook-messages : Sends any stray webhook messages (with a maximum of 5).}
|
||||
';
|
||||
|
||||
public function handle(): int
|
||||
@@ -58,7 +60,8 @@ class Cron extends Command
|
||||
&& !$this->option('create-recurring')
|
||||
&& !$this->option('create-auto-budgets')
|
||||
&& !$this->option('send-bill-warnings')
|
||||
&& !$this->option('check-version');
|
||||
&& !$this->option('check-version')
|
||||
&& !$this->option('send-webhook-messages');
|
||||
$date = null;
|
||||
|
||||
try {
|
||||
@@ -122,6 +125,16 @@ class Cron extends Command
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
// Fire webhook messages cron job.
|
||||
if ($doAll || $this->option('send-webhook-messages')) {
|
||||
try {
|
||||
$this->webhookCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->friendlyInfo('More feedback on the cron jobs can be found in the log files.');
|
||||
|
||||
@@ -239,4 +252,26 @@ class Cron extends Command
|
||||
$this->friendlyPositive(sprintf('"Send bill warnings" cron ran with success: %s', $autoBudget->message));
|
||||
}
|
||||
}
|
||||
|
||||
private function webhookCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$webhook = new WebhookCronjob();
|
||||
$webhook->setForce($force);
|
||||
// set date in cron job:
|
||||
if ($date instanceof Carbon) {
|
||||
$webhook->setDate($date);
|
||||
}
|
||||
|
||||
$webhook->fire();
|
||||
|
||||
if ($webhook->jobErrored) {
|
||||
$this->friendlyError(sprintf('Error in "webhook" cron: %s', $webhook->message));
|
||||
}
|
||||
if ($webhook->jobFired) {
|
||||
$this->friendlyInfo(sprintf('"Webhook" cron fired: %s', $webhook->message));
|
||||
}
|
||||
if ($webhook->jobSucceeded) {
|
||||
$this->friendlyPositive(sprintf('"Webhook" cron ran with success: %s', $webhook->message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
app/Events/Model/Bill/WarnUserAboutBill.php
Normal file
20
app/Events/Model/Bill/WarnUserAboutBill.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\Bill;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class WarnUserAboutBill.
|
||||
*/
|
||||
class WarnUserAboutBill extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public Bill $bill, public string $field, public int $diff) {}
|
||||
}
|
17
app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php
Normal file
17
app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\Bill;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class WarnUserAboutOverdueSubscriptions extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public User $user, public array $overdue) {}
|
||||
|
||||
}
|
@@ -35,6 +35,6 @@ class UserGroupChangedPrimaryCurrency extends Event
|
||||
|
||||
public function __construct(public UserGroup $userGroup)
|
||||
{
|
||||
Log::debug('User group changed default currency.');
|
||||
Log::debug('User group changed primary currency.');
|
||||
}
|
||||
}
|
||||
|
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DestroyedTransactionGroup.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class WarnUserAboutBill.
|
||||
*/
|
||||
class WarnUserAboutBill extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public Bill $bill, public string $field, public int $diff) {}
|
||||
}
|
@@ -24,48 +24,118 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Notifications\User\BillReminder;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Exception;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Notifications\User\BillReminder;
|
||||
use FireflyIII\Notifications\User\SubscriptionsOverdueReminder;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
/**
|
||||
* Class BillEventHandler
|
||||
*/
|
||||
class BillEventHandler
|
||||
{
|
||||
public function warnAboutOverdueSubscriptions(WarnUserAboutOverdueSubscriptions $event): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
// make sure user does not get the warning twice.
|
||||
$overdue = $event->overdue;
|
||||
$user = $event->user;
|
||||
$toBeWarned = [];
|
||||
Log::debug(sprintf('%d bills to warn about.', count($overdue)));
|
||||
foreach ($overdue as $item) {
|
||||
/** @var Bill $bill */
|
||||
$bill = $item['bill'];
|
||||
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($item['dates']['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
|
||||
$pref = Preferences::getForUser($bill->user, $key, false);
|
||||
if (true === $pref->data) {
|
||||
Log::debug(sprintf('User #%d has already been warned about overdue subscription #%d.', $bill->user->id, $bill->id));
|
||||
|
||||
continue;
|
||||
}
|
||||
$toBeWarned[] = $item;
|
||||
}
|
||||
unset($bill);
|
||||
Log::debug(sprintf('Now %d bills to warn about.', count($toBeWarned)));
|
||||
|
||||
/** @var bool $sendNotification */
|
||||
$sendNotification = Preferences::getForUser($user, 'notification_bill_reminder', true)->data;
|
||||
if (false === $sendNotification) {
|
||||
Log::debug('User has disabled bill reminders.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Will warn about %d overdue subscription(s).', count($toBeWarned)));
|
||||
if (0 === count($toBeWarned)) {
|
||||
Log::debug('No overdue subscriptions to warn about.');
|
||||
|
||||
return;
|
||||
}
|
||||
foreach ($toBeWarned as $item) {
|
||||
/** @var Bill $bill */
|
||||
$bill = $item['bill'];
|
||||
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($item['dates']['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
|
||||
Preferences::setForUser($bill->user, $key, true);
|
||||
}
|
||||
Log::warning('should hit this ONCE');
|
||||
|
||||
try {
|
||||
Notification::send($user, new SubscriptionsOverdueReminder($toBeWarned));
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function warnAboutBill(WarnUserAboutBill $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$bill = $event->bill;
|
||||
$bill = $event->bill;
|
||||
|
||||
/** @var bool $preference */
|
||||
$preference = app('preferences')->getForUser($bill->user, 'notification_bill_reminder', true)->data;
|
||||
Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
|
||||
|
||||
if (true === $preference) {
|
||||
app('log')->debug('Bill reminder is true!');
|
||||
Log::debug('Bill reminder is true!');
|
||||
|
||||
try {
|
||||
Notification::send($bill->user, new BillReminder($bill, $event->field, $event->diff));
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (false === $preference) {
|
||||
app('log')->debug('User has disabled bill reminders.');
|
||||
}
|
||||
Log::debug('User has disabled bill reminders.');
|
||||
}
|
||||
}
|
||||
|
@@ -73,6 +73,7 @@ class PreferencesEventHandler
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$repository->setUserGroup($userGroup);
|
||||
$piggyBanks = $repository->getPiggyBanks();
|
||||
Log::debug(sprintf('Resetting %d piggy bank(s).', $piggyBanks->count()));
|
||||
|
||||
/** @var PiggyBank $piggyBank */
|
||||
foreach ($piggyBanks as $piggyBank) {
|
||||
@@ -104,6 +105,8 @@ class PreferencesEventHandler
|
||||
$repository->setUserGroup($userGroup);
|
||||
$set = $repository->getBudgets();
|
||||
|
||||
Log::debug(sprintf('Resetting %d budget(s).', $set->count()));
|
||||
|
||||
/** @var Budget $budget */
|
||||
foreach ($set as $budget) {
|
||||
foreach ($budget->autoBudgets as $autoBudget) {
|
||||
|
@@ -77,8 +77,8 @@ class NetWorth implements NetWorthInterface
|
||||
Log::debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d H:i:s')));
|
||||
$primary = Amount::getPrimaryCurrency();
|
||||
$netWorth = [];
|
||||
Log::debug(sprintf('NetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s')));
|
||||
$balances = Steam::finalAccountsBalance($accounts, $date);
|
||||
Log::debug(sprintf('NetWorth: accountsBalancesOptimized("%s")', $date->format('Y-m-d H:i:s')));
|
||||
$balances = Steam::accountsBalancesOptimized($accounts, $date, null, $convertToPrimary);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
@@ -143,8 +143,8 @@ class NetWorth implements NetWorthInterface
|
||||
*/
|
||||
$accounts = $this->getAccounts();
|
||||
$return = [];
|
||||
Log::debug(sprintf('SumNetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s')));
|
||||
$balances = Steam::finalAccountsBalance($accounts, $date);
|
||||
Log::debug(sprintf('SumNetWorth: accountsBalancesOptimized("%s")', $date->format('Y-m-d H:i:s')));
|
||||
$balances = Steam::accountsBalancesOptimized($accounts, $date);
|
||||
foreach ($accounts as $account) {
|
||||
$currency = $this->accountRepository->getAccountCurrency($account);
|
||||
$balance = $balances[$account->id]['balance'] ?? '0';
|
||||
|
@@ -93,10 +93,10 @@ class IndexController extends Controller
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
Log::debug(sprintf('inactive start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('inactive end: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::finalAccountsBalance($accounts, $start);
|
||||
$endBalances = Steam::finalAccountsBalance($accounts, $end);
|
||||
Log::debug(sprintf('inactive start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('inactive end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$activities = Steam::getLastActivities($ids);
|
||||
|
||||
|
||||
@@ -170,10 +170,10 @@ class IndexController extends Controller
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
Log::debug(sprintf('index start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('index end: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::finalAccountsBalance($accounts, $start);
|
||||
$endBalances = Steam::finalAccountsBalance($accounts, $end);
|
||||
Log::debug(sprintf('index start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('index end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$activities = Steam::getLastActivities($ids);
|
||||
|
||||
|
||||
|
@@ -40,6 +40,7 @@ use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class ShowController
|
||||
@@ -81,7 +82,9 @@ class ShowController extends Controller
|
||||
* */
|
||||
public function show(Request $request, Account $account, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
|
||||
if (0 === $account->id) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type));
|
||||
|
||||
if (!$this->isEditableAccount($account)) {
|
||||
@@ -115,20 +118,27 @@ class ShowController extends Controller
|
||||
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$firstTransaction = $this->repository->oldestJournalDate($account) ?? $start;
|
||||
|
||||
Log::debug('Start period overview');
|
||||
Timer::start('period-overview');
|
||||
// go back max 3 years.
|
||||
$threeYearsAgo = clone $start;
|
||||
$threeYearsAgo->startOfYear()->subYears(3);
|
||||
if ($firstTransaction->lt($threeYearsAgo)) {
|
||||
$firstTransaction = clone $threeYearsAgo;
|
||||
}
|
||||
|
||||
Log::debug('Start period overview');
|
||||
$timer = Timer::getInstance();
|
||||
$timer->start('period-overview');
|
||||
$periods = $this->getAccountPeriodOverview($account, $firstTransaction, $end);
|
||||
|
||||
Log::debug('End period overview');
|
||||
Timer::stop('period-overview');
|
||||
$timer->stop('period-overview');
|
||||
|
||||
// if layout = v2, overrule the page title.
|
||||
if ('v1' !== config('view.layout')) {
|
||||
$subTitle = (string) trans('firefly.all_journals_for_account', ['name' => $account->name]);
|
||||
}
|
||||
Log::debug('Collect transactions');
|
||||
Timer::start('collection');
|
||||
$timer->start('collection');
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
@@ -136,6 +146,7 @@ class ShowController extends Controller
|
||||
->setAccounts(new Collection([$account]))
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withAttachmentInformation()
|
||||
->withAPIInformation()
|
||||
->setRange($start, $end)
|
||||
;
|
||||
@@ -146,7 +157,7 @@ class ShowController extends Controller
|
||||
|
||||
|
||||
Log::debug('End collect transactions');
|
||||
Timer::stop('collection');
|
||||
$timer->stop('collection');
|
||||
|
||||
// enrich data in arrays.
|
||||
|
||||
|
@@ -57,7 +57,7 @@ class IndexController extends Controller
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
app('view')->share('title', (string) trans('firefly.bills'));
|
||||
app('view')->share('title', (string)trans('firefly.bills'));
|
||||
app('view')->share('mainTitleIcon', 'fa-calendar-o');
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
|
||||
@@ -79,7 +79,6 @@ class IndexController extends Controller
|
||||
$total = $collection->count();
|
||||
|
||||
|
||||
|
||||
$parameters = new ParameterBag();
|
||||
// sub one day from temp start so the last paid date is one day before it should be.
|
||||
$tempStart = clone $start;
|
||||
@@ -112,7 +111,7 @@ class IndexController extends Controller
|
||||
$bills = [
|
||||
0 => [ // the index is the order, not the ID.
|
||||
'object_group_id' => 0,
|
||||
'object_group_title' => (string) trans('firefly.default_group_title_name'),
|
||||
'object_group_title' => (string)trans('firefly.default_group_title_name'),
|
||||
'bills' => [],
|
||||
],
|
||||
];
|
||||
@@ -120,7 +119,7 @@ class IndexController extends Controller
|
||||
/** @var Bill $bill */
|
||||
foreach ($collection as $bill) {
|
||||
$array = $transformer->transform($bill);
|
||||
$groupOrder = (int) $array['object_group_order'];
|
||||
$groupOrder = (int)$array['object_group_order'];
|
||||
// make group array if necessary:
|
||||
$bills[$groupOrder] ??= [
|
||||
'object_group_id' => $array['object_group_id'],
|
||||
@@ -173,16 +172,28 @@ class IndexController extends Controller
|
||||
'currency_symbol' => $bill['currency_symbol'],
|
||||
'currency_decimal_places' => $bill['currency_decimal_places'],
|
||||
'avg' => '0',
|
||||
'total_left_to_pay' => '0',
|
||||
'period' => $range,
|
||||
'per_period' => '0',
|
||||
];
|
||||
|
||||
// only fill in avg when bill is active.
|
||||
if (null !== $bill['next_expected_match']) {
|
||||
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
|
||||
$avg = bcmul($avg, (string) count($bill['pay_dates']));
|
||||
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
|
||||
$avg = bcmul($avg, (string)count($bill['pay_dates']));
|
||||
$sums[$groupOrder][$currencyId]['avg'] = bcadd($sums[$groupOrder][$currencyId]['avg'], $avg);
|
||||
}
|
||||
// only fill in total_left_to_pay when bill is not yet paid.
|
||||
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
|
||||
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
|
||||
if ($count > 0) {
|
||||
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
|
||||
$avg = bcmul($avg, (string)$count);
|
||||
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// fill in per period regardless:
|
||||
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range));
|
||||
}
|
||||
@@ -193,7 +204,7 @@ class IndexController extends Controller
|
||||
|
||||
private function amountPerPeriod(array $bill, string $range): string
|
||||
{
|
||||
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
|
||||
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
|
||||
|
||||
app('log')->debug(sprintf('Amount per period for bill #%d "%s"', $bill['id'], $bill['name']));
|
||||
app('log')->debug(sprintf('Average is %s', $avg));
|
||||
@@ -206,8 +217,8 @@ class IndexController extends Controller
|
||||
'weekly' => '52.17',
|
||||
'daily' => '365.24',
|
||||
];
|
||||
$yearAmount = bcmul($avg, bcdiv($multiplies[$bill['repeat_freq']], (string) ($bill['skip'] + 1)));
|
||||
app('log')->debug(sprintf('Amount per year is %s (%s * %s / %s)', $yearAmount, $avg, $multiplies[$bill['repeat_freq']], (string) ($bill['skip'] + 1)));
|
||||
$yearAmount = bcmul($avg, bcdiv($multiplies[$bill['repeat_freq']], (string)($bill['skip'] + 1)));
|
||||
app('log')->debug(sprintf('Amount per year is %s (%s * %s / %s)', $yearAmount, $avg, $multiplies[$bill['repeat_freq']], (string)($bill['skip'] + 1)));
|
||||
|
||||
// per period:
|
||||
$division = [
|
||||
@@ -258,8 +269,8 @@ class IndexController extends Controller
|
||||
'period' => $entry['period'],
|
||||
'per_period' => '0',
|
||||
];
|
||||
$totals[$currencyId]['avg'] = bcadd($totals[$currencyId]['avg'], (string) $entry['avg']);
|
||||
$totals[$currencyId]['per_period'] = bcadd($totals[$currencyId]['per_period'], (string) $entry['per_period']);
|
||||
$totals[$currencyId]['avg'] = bcadd($totals[$currencyId]['avg'], (string)$entry['avg']);
|
||||
$totals[$currencyId]['per_period'] = bcadd($totals[$currencyId]['per_period'], (string)$entry['per_period']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,8 +282,8 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function setOrder(Request $request, Bill $bill): JsonResponse
|
||||
{
|
||||
$objectGroupTitle = (string) $request->get('objectGroupTitle');
|
||||
$newOrder = (int) $request->get('order');
|
||||
$objectGroupTitle = (string)$request->get('objectGroupTitle');
|
||||
$newOrder = (int)$request->get('order');
|
||||
$this->repository->setOrder($bill, $newOrder);
|
||||
if ('' !== $objectGroupTitle) {
|
||||
$this->repository->setObjectGroup($bill, $objectGroupTitle);
|
||||
|
@@ -35,6 +35,7 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Controllers\AugumentData;
|
||||
use FireflyIII\Support\Http\Controllers\ChartGeneration;
|
||||
@@ -78,6 +79,7 @@ class AccountController extends Controller
|
||||
|
||||
/**
|
||||
* Shows the balances for all the user's expense accounts (on the front page).
|
||||
* 2025-08-06 validated for multi (primary) currency
|
||||
*
|
||||
* This chart is (multi) currency aware.
|
||||
*/
|
||||
@@ -112,11 +114,11 @@ class AccountController extends Controller
|
||||
$accountNames = $this->extractNames($accounts);
|
||||
|
||||
// grab all balances
|
||||
Log::debug(sprintf('expenseAccounts: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('expenseAccounts: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::finalAccountsBalance($accounts, $start);
|
||||
$endBalances = Steam::finalAccountsBalance($accounts, $end);
|
||||
|
||||
Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
Log::debug('Done collecting balances');
|
||||
// loop the accounts, then check for balance and currency info.
|
||||
foreach ($accounts as $account) {
|
||||
// Log::debug(sprintf('[a] Now in account #%d ("%s")', $account->id, $account->name));
|
||||
@@ -157,7 +159,7 @@ class AccountController extends Controller
|
||||
$tempData[] = [
|
||||
'name' => $accountNames[$account->id],
|
||||
'difference' => $diff,
|
||||
'diff_float' => (float) $diff, // intentional float
|
||||
'diff_float' => (float)$diff, // intentional float
|
||||
'currency_id' => $currencies[$searchCode]->id,
|
||||
];
|
||||
}
|
||||
@@ -182,7 +184,7 @@ class AccountController extends Controller
|
||||
foreach ($currencies as $currencyId => $currency) {
|
||||
$dataSet
|
||||
= [
|
||||
'label' => (string) trans('firefly.spent'),
|
||||
'label' => (string)trans('firefly.spent'),
|
||||
'type' => 'bar',
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_code' => $currency->code,
|
||||
@@ -195,7 +197,7 @@ class AccountController extends Controller
|
||||
foreach ($tempData as $entry) {
|
||||
$currencyId = $entry['currency_id'];
|
||||
$name = $entry['name'];
|
||||
$chartData[$currencyId]['entries'][$name] = (float) $entry['difference'];
|
||||
$chartData[$currencyId]['entries'][$name] = (float)$entry['difference'];
|
||||
}
|
||||
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
@@ -223,6 +225,7 @@ class AccountController extends Controller
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($account->id);
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.account.expense-budget');
|
||||
if ($cache->has()) {
|
||||
@@ -231,7 +234,10 @@ class AccountController extends Controller
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$collector->setAccounts(new Collection([$account]))
|
||||
->setRange($start, $end)
|
||||
->withBudgetInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
|
||||
;
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$chartData = [];
|
||||
$result = [];
|
||||
@@ -239,19 +245,37 @@ class AccountController extends Controller
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetId = (int)$journal['budget_id'];
|
||||
$key = sprintf('%d-%d', $budgetId, $journal['currency_id']);
|
||||
$budgetIds[] = $budgetId;
|
||||
|
||||
// currency info:
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$field = 'amount';
|
||||
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
|
||||
$field = 'pc_amount';
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
|
||||
}
|
||||
|
||||
if (!array_key_exists($key, $result)) {
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'budget_id' => $budgetId,
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'total' => '0',
|
||||
'budget_id' => $budgetId,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
}
|
||||
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
|
||||
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
|
||||
}
|
||||
|
||||
$names = $this->getBudgetNames($budgetIds);
|
||||
@@ -259,7 +283,7 @@ class AccountController extends Controller
|
||||
foreach ($result as $row) {
|
||||
$budgetId = $row['budget_id'];
|
||||
$name = $names[$budgetId];
|
||||
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
|
||||
}
|
||||
|
||||
@@ -289,6 +313,7 @@ class AccountController extends Controller
|
||||
$cache->addProperty($account->id);
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty('chart.account.expense-category');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
@@ -305,22 +330,39 @@ class AccountController extends Controller
|
||||
foreach ($journals as $journal) {
|
||||
$key = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']);
|
||||
if (!array_key_exists($key, $result)) {
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => (int) $journal['category_id'],
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
|
||||
// currency info:
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$field = 'amount';
|
||||
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
|
||||
$field = 'pc_amount';
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => (int)$journal['category_id'],
|
||||
'currency_name' => $currencyName,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
}
|
||||
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
|
||||
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
|
||||
}
|
||||
$names = $this->getCategoryNames(array_keys($result));
|
||||
|
||||
foreach ($result as $row) {
|
||||
$categoryId = $row['category_id'];
|
||||
$name = $names[$categoryId] ?? '(unknown)';
|
||||
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
|
||||
}
|
||||
|
||||
@@ -341,11 +383,11 @@ class AccountController extends Controller
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$defaultSet = $repository->getAccountsByType([AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
|
||||
// Log::debug('Default set is ', $defaultSet);
|
||||
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
|
||||
$frontpage = Preferences::get('frontpageAccounts', $defaultSet);
|
||||
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
|
||||
Log::debug('Frontpage preference set is ', $frontpageArray);
|
||||
if (0 === count($frontpageArray)) {
|
||||
app('preferences')->set('frontpageAccounts', $defaultSet);
|
||||
Preferences::set('frontpageAccounts', $defaultSet);
|
||||
Log::debug('frontpage set is empty!');
|
||||
}
|
||||
$accounts = $repository->getAccountsById($frontpageArray);
|
||||
@@ -375,6 +417,7 @@ class AccountController extends Controller
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($account->id);
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.account.income-category');
|
||||
if ($cache->has()) {
|
||||
@@ -394,22 +437,39 @@ class AccountController extends Controller
|
||||
foreach ($journals as $journal) {
|
||||
$key = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']);
|
||||
if (!array_key_exists($key, $result)) {
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => $journal['category_id'],
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
|
||||
// currency info:
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$field = 'amount';
|
||||
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
|
||||
$field = 'pc_amount';
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => $journal['category_id'],
|
||||
'currency_name' => $currencyName,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
}
|
||||
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
|
||||
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
|
||||
}
|
||||
|
||||
$names = $this->getCategoryNames(array_keys($result));
|
||||
foreach ($result as $row) {
|
||||
$categoryId = $row['category_id'];
|
||||
$name = $names[$categoryId] ?? '(unknown)';
|
||||
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
|
||||
}
|
||||
$data = $this->generator->multiCurrencyPieChart($chartData);
|
||||
@@ -450,7 +510,7 @@ class AccountController extends Controller
|
||||
// This period depends on the size of the chart
|
||||
$current = clone $start;
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
$format = (string) trans('config.month_and_day_js', [], $locale);
|
||||
$format = (string)trans('config.month_and_day_js', [], $locale);
|
||||
$accountCurrency = $this->accountRepository->getAccountCurrency($account);
|
||||
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToPrimary);
|
||||
@@ -512,7 +572,7 @@ class AccountController extends Controller
|
||||
foreach ($return as $key => $info) {
|
||||
if ('balance' !== $key && 'pc_balance' !== $key) {
|
||||
// assume it's a currency:
|
||||
$setCurrency = $this->currencyRepository->findByCode((string) $key);
|
||||
$setCurrency = $this->currencyRepository->findByCode((string)$key);
|
||||
$info['currency_symbol'] = $setCurrency->symbol;
|
||||
$info['currency_code'] = $setCurrency->code;
|
||||
$info['label'] = sprintf('%s (%s)', $account->name, $setCurrency->symbol);
|
||||
@@ -525,7 +585,7 @@ class AccountController extends Controller
|
||||
if ('pc_balance' === $key) {
|
||||
$info['currency_symbol'] = $this->primaryCurrency->symbol;
|
||||
$info['currency_code'] = $this->primaryCurrency->code;
|
||||
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string) trans('firefly.sum'), $this->primaryCurrency->symbol);
|
||||
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string)trans('firefly.sum'), $this->primaryCurrency->symbol);
|
||||
}
|
||||
$chartData[] = $info;
|
||||
}
|
||||
@@ -594,10 +654,10 @@ class AccountController extends Controller
|
||||
$accountNames = $this->extractNames($accounts);
|
||||
|
||||
// grab all balances
|
||||
Log::debug(sprintf('revAccounts: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('revAccounts: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::finalAccountsBalance($accounts, $start);
|
||||
$endBalances = Steam::finalAccountsBalance($accounts, $end);
|
||||
Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
|
||||
|
||||
// loop the accounts, then check for balance and currency info.
|
||||
@@ -640,7 +700,7 @@ class AccountController extends Controller
|
||||
$tempData[] = [
|
||||
'name' => $accountNames[$account->id],
|
||||
'difference' => $diff,
|
||||
'diff_float' => (float) $diff, // intentional float
|
||||
'diff_float' => (float)$diff, // intentional float
|
||||
'currency_id' => $currencies[$searchCode]->id,
|
||||
];
|
||||
}
|
||||
@@ -667,7 +727,7 @@ class AccountController extends Controller
|
||||
foreach ($currencies as $currencyId => $currency) {
|
||||
$dataSet
|
||||
= [
|
||||
'label' => (string) trans('firefly.earned'),
|
||||
'label' => (string)trans('firefly.earned'),
|
||||
'type' => 'bar',
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_code' => $currency->code,
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
@@ -33,6 +34,7 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@@ -141,6 +143,14 @@ abstract class Controller extends BaseController
|
||||
View::share('shownDemo', $shownDemo);
|
||||
View::share('current_route_name', $page);
|
||||
View::share('original_route_name', Route::currentRouteName());
|
||||
|
||||
// lottery to send any remaining webhooks:
|
||||
if (7 === random_int(1, 10)) {
|
||||
// trigger event to send them:
|
||||
Log::debug('send event RequestedSendWebhookMessages through lottery');
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
|
||||
}
|
||||
View::share('darkMode', $darkMode);
|
||||
|
||||
|
@@ -157,6 +157,11 @@ class DebugController extends Controller
|
||||
return view('debug', compact('table', 'now', 'logContent'));
|
||||
}
|
||||
|
||||
public function apiTest()
|
||||
{
|
||||
return view('test.api-test');
|
||||
}
|
||||
|
||||
private function generateTable(): string
|
||||
{
|
||||
// system information:
|
||||
|
@@ -32,8 +32,10 @@ use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -114,6 +116,13 @@ class IndexController extends Controller
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
$piggyBanks = [];
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
/** @var PiggyBank $piggy */
|
||||
foreach ($collection as $piggy) {
|
||||
$array = $transformer->transform($piggy);
|
||||
@@ -170,14 +179,6 @@ class IndexController extends Controller
|
||||
$return[$accountId]['target'] = '0';
|
||||
$return[$accountId]['to_save'] = '0';
|
||||
}
|
||||
|
||||
// calculate new interesting fields:
|
||||
// $return[$accountId]['left'] -= $array['current_amount'];
|
||||
// $return[$accountId]['saved'] += $array['current_amount'];
|
||||
// $return[$accountId]['target'] += $array['target_amount'];
|
||||
// $return[$accountId]['to_save'] += ($array['target_amount'] - $array['current_amount']);
|
||||
// $return['account_name'] = $account['name'];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +194,7 @@ class IndexController extends Controller
|
||||
// loop all accounts in this piggy bank subtract the current amount from "left to save" in the $accounts array.
|
||||
/** @var array $piggyAccount */
|
||||
foreach ($piggyBank['accounts'] as $piggyAccount) {
|
||||
$accountId = $piggyAccount['id'];
|
||||
$accountId = $piggyAccount['account_id'];
|
||||
if (array_key_exists($accountId, $accounts)) {
|
||||
$accounts[$accountId]['left'] = bcsub((string) $accounts[$accountId]['left'], (string) $piggyAccount['current_amount']);
|
||||
$accounts[$accountId]['saved'] = bcadd((string) $accounts[$accountId]['saved'], (string) $piggyAccount['current_amount']);
|
||||
|
@@ -29,7 +29,9 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
@@ -75,6 +77,13 @@ class ShowController extends Controller
|
||||
$parameters = new ParameterBag();
|
||||
$parameters->set('end', $end);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new PiggyBankEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$piggyBank = $enrichment->enrichSingle($piggyBank);
|
||||
|
||||
/** @var PiggyBankTransformer $transformer */
|
||||
$transformer = app(PiggyBankTransformer::class);
|
||||
$transformer->setParameters($parameters);
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use FireflyIII\Support\Singleton\PreferencesSingleton;
|
||||
use JsonException;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
@@ -269,7 +270,9 @@ class PreferencesController extends Controller
|
||||
if ($convertToPrimary && !$this->convertToPrimary) {
|
||||
// set to true!
|
||||
Log::debug('User sets convertToPrimary to true.');
|
||||
Preferences::set('convert_to_primary', $convertToPrimary);
|
||||
Preferences::set('convert_to_primary', true);
|
||||
$singleton = PreferencesSingleton::getInstance();
|
||||
$singleton->resetPreferences();
|
||||
event(new UserGroupChangedPrimaryCurrency(auth()->user()->userGroup));
|
||||
}
|
||||
Preferences::set('convert_to_primary', $convertToPrimary);
|
||||
|
@@ -34,7 +34,10 @@ use FireflyIII\Models\RecurrenceRepetition;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\ExpandedForm;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -92,13 +95,20 @@ class EditController extends Controller
|
||||
throw new FireflyException('This recurring transaction has no meta-data. You will have to delete it and recreate it. Sorry!');
|
||||
}
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrence = $enrichment->enrichSingle($recurrence);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
|
||||
$array = $transformer->transform($recurrence);
|
||||
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
|
||||
$bills = app('expandedform')->makeSelectListWithEmpty($this->billRepository->getActiveBills());
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
|
||||
$bills = ExpandedForm::makeSelectListWithEmpty($this->billRepository->getActiveBills());
|
||||
|
||||
/** @var RecurrenceRepetition $repetition */
|
||||
$repetition = $recurrence->recurrenceRepetitions()->first();
|
||||
|
@@ -29,7 +29,9 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
@@ -85,6 +87,13 @@ class IndexController extends Controller
|
||||
$total = $collection->count();
|
||||
$recurrences = $collection->slice(($page - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrences = $enrichment->enrich($recurrences);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
|
@@ -32,8 +32,10 @@ use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Transformers\AttachmentTransformer;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
@@ -80,6 +82,13 @@ class ShowController extends Controller
|
||||
{
|
||||
$repos = app(AttachmentRepositoryInterface::class);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new RecurringEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$recurrence = $enrichment->enrichSingle($recurrence);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Report;
|
||||
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use Throwable;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
@@ -290,11 +291,12 @@ class BudgetController extends Controller
|
||||
$cache->addProperty('budget-period-report');
|
||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
// return $cache->get();
|
||||
}
|
||||
|
||||
$periods = app('navigation')->listOfPeriods($start, $end);
|
||||
$keyFormat = app('navigation')->preferredCarbonFormat($start, $end);
|
||||
$periods = Navigation::listOfPeriods($start, $end);
|
||||
$keyFormat = Navigation::preferredCarbonFormat($start, $end);
|
||||
|
||||
// list expenses for budgets in account(s)
|
||||
$expenses = $this->opsRepository->listExpenses($start, $end, $accounts);
|
||||
|
||||
@@ -303,6 +305,17 @@ class BudgetController extends Controller
|
||||
foreach ($currency['budgets'] as $budget) {
|
||||
$count = 0;
|
||||
foreach ($budget['transaction_journals'] as $journal) {
|
||||
// #10678
|
||||
// skip transactions between two asset / liability accounts.
|
||||
if (
|
||||
in_array($journal['source_account_type'], config('firefly.valid_currency_account_types'), true)
|
||||
&& in_array($journal['destination_account_type'], config('firefly.valid_currency_account_types'), true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
++$count;
|
||||
$key = sprintf('%d-%d', $budget['id'], $currency['currency_id']);
|
||||
$dateKey = $journal['date']->format($keyFormat);
|
||||
|
@@ -188,16 +188,7 @@ class ReportController extends Controller
|
||||
$start->endOfDay(); // end of day so the final balance is at the end of that day.
|
||||
$end->endOfDay();
|
||||
|
||||
app('view')->share(
|
||||
'subTitle',
|
||||
trans(
|
||||
'firefly.report_default',
|
||||
[
|
||||
'start' => $start->isoFormat($this->monthAndDayFormat),
|
||||
'end' => $end->isoFormat($this->monthAndDayFormat),
|
||||
]
|
||||
)
|
||||
);
|
||||
app('view')->share('subTitle', trans('firefly.report_default', ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]));
|
||||
|
||||
$generator = ReportGeneratorFactory::reportGenerator('Standard', $start, $end);
|
||||
$generator->setAccounts($accounts);
|
||||
@@ -222,16 +213,7 @@ class ReportController extends Controller
|
||||
$start->endOfDay(); // end of day so the final balance is at the end of that day.
|
||||
$end->endOfDay();
|
||||
|
||||
app('view')->share(
|
||||
'subTitle',
|
||||
trans(
|
||||
'firefly.report_double',
|
||||
[
|
||||
'start' => $start->isoFormat($this->monthAndDayFormat),
|
||||
'end' => $end->isoFormat($this->monthAndDayFormat),
|
||||
]
|
||||
)
|
||||
);
|
||||
app('view')->share('subTitle', trans('firefly.report_double', ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]));
|
||||
|
||||
$generator = ReportGeneratorFactory::reportGenerator('Account', $start, $end);
|
||||
$generator->setAccounts($accounts);
|
||||
|
@@ -270,6 +270,7 @@ class CreateController extends Controller
|
||||
$data = $request->getRuleData();
|
||||
|
||||
$rule = $this->ruleRepos->store($data);
|
||||
session()->flash('success_url', route('rules.select-transactions', [$rule->id]));
|
||||
session()->flash('success', (string) trans('firefly.stored_new_rule', ['title' => $rule->title]));
|
||||
app('preferences')->mark();
|
||||
|
||||
|
@@ -252,6 +252,7 @@ class TagController extends Controller
|
||||
|
||||
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
@@ -283,6 +284,7 @@ class TagController extends Controller
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
|
@@ -25,16 +25,20 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class ShowController
|
||||
@@ -57,7 +61,7 @@ class ShowController extends Controller
|
||||
$this->repository = app(TransactionGroupRepositoryInterface::class);
|
||||
$this->aleRepository = app(ALERepositoryInterface::class);
|
||||
|
||||
app('view')->share('title', (string) trans('firefly.transactions'));
|
||||
app('view')->share('title', (string)trans('firefly.transactions'));
|
||||
app('view')->share('mainTitleIcon', 'fa-exchange');
|
||||
|
||||
return $next($request);
|
||||
@@ -80,38 +84,62 @@ class ShowController extends Controller
|
||||
*/
|
||||
public function show(TransactionGroup $transactionGroup)
|
||||
{
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($admin)->setTransactionGroup($transactionGroup)->withAPIInformation();
|
||||
|
||||
$selectedGroup = $collector->getGroups()->first();
|
||||
if (null === $selectedGroup) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
// enrich
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
|
||||
/** @var null|TransactionJournal $first */
|
||||
$first = $transactionGroup->transactionJournals()->first(['transaction_journals.*']);
|
||||
$splits = $transactionGroup->transactionJournals()->count();
|
||||
$splits = count($selectedGroup['transactions']);
|
||||
$keys = array_keys($selectedGroup['transactions']);
|
||||
$first = $selectedGroup['transactions'][array_shift($keys)];
|
||||
unset($keys);
|
||||
|
||||
if (null === $first) {
|
||||
throw new FireflyException('This transaction is broken :(.');
|
||||
}
|
||||
|
||||
$type = (string) trans(sprintf('firefly.%s', $first->transactionType->type));
|
||||
$title = 1 === $splits ? $first->description : $transactionGroup->title;
|
||||
$type = (string)trans(sprintf('firefly.%s', $first['transaction_type_type']));
|
||||
$title = 1 === $splits ? $first['description'] : $selectedGroup['title'];
|
||||
$subTitle = sprintf('%s: "%s"', $type, $title);
|
||||
|
||||
// enrich
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
$groupArray = $transformer->transformObject($transactionGroup);
|
||||
|
||||
// do some calculations:
|
||||
$amounts = $this->getAmounts($groupArray);
|
||||
$accounts = $this->getAccounts($groupArray);
|
||||
$amounts = $this->getAmounts($selectedGroup);
|
||||
$accounts = $this->getAccounts($selectedGroup);
|
||||
|
||||
foreach (array_keys($groupArray['transactions']) as $index) {
|
||||
$groupArray['transactions'][$index]['tags'] = $this->repository->getTagObjects(
|
||||
(int) $groupArray['transactions'][$index]['transaction_journal_id']
|
||||
);
|
||||
foreach (array_keys($selectedGroup['transactions']) as $index) {
|
||||
$selectedGroup['transactions'][$index]['tags'] = $this->repository->getTagObjects((int)$selectedGroup['transactions'][$index]['transaction_journal_id']);
|
||||
}
|
||||
|
||||
// get audit log entries:
|
||||
$groupLogEntries = $this->aleRepository->getForObject($transactionGroup);
|
||||
$logEntries = [];
|
||||
foreach ($transactionGroup->transactionJournals as $journal) {
|
||||
$logEntries[$journal->id] = $this->aleRepository->getForObject($journal);
|
||||
foreach ($selectedGroup['transactions'] as $journal) {
|
||||
$logEntries[$journal['transaction_journal_id']] = $this->aleRepository->getForId(TransactionJournal::class, $journal['transaction_journal_id']);
|
||||
}
|
||||
|
||||
$events = $this->repository->getPiggyEvents($transactionGroup);
|
||||
@@ -129,6 +157,7 @@ class ShowController extends Controller
|
||||
'groupLogEntries',
|
||||
'subTitle',
|
||||
'splits',
|
||||
'selectedGroup',
|
||||
'groupArray',
|
||||
'events',
|
||||
'attachments',
|
||||
@@ -142,34 +171,38 @@ class ShowController extends Controller
|
||||
{
|
||||
$amounts = [];
|
||||
foreach ($group['transactions'] as $transaction) {
|
||||
// add normal amount:
|
||||
$symbol = $transaction['currency_symbol'];
|
||||
if (!array_key_exists($symbol, $amounts)) {
|
||||
$amounts[$symbol] = [
|
||||
'amount' => '0',
|
||||
'symbol' => $symbol,
|
||||
'decimal_places' => $transaction['currency_decimal_places'],
|
||||
];
|
||||
}
|
||||
$amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], (string) $transaction['amount']);
|
||||
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount']
|
||||
&& 0 !== bccomp(
|
||||
'0',
|
||||
(string) $transaction['foreign_amount']
|
||||
)) {
|
||||
$amounts[$symbol] ??= [
|
||||
'amount' => '0',
|
||||
'symbol' => $symbol,
|
||||
'decimal_places' => $transaction['currency_decimal_places'],
|
||||
];
|
||||
$amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], (string)$transaction['amount']);
|
||||
|
||||
// add foreign amount:
|
||||
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string)$transaction['foreign_amount'])) {
|
||||
// same for foreign currency:
|
||||
$foreignSymbol = $transaction['foreign_currency_symbol'];
|
||||
if (!array_key_exists($foreignSymbol, $amounts)) {
|
||||
$amounts[$foreignSymbol] = [
|
||||
'amount' => '0',
|
||||
'symbol' => $foreignSymbol,
|
||||
'decimal_places' => $transaction['foreign_currency_decimal_places'],
|
||||
];
|
||||
}
|
||||
$amounts[$foreignSymbol]['amount'] = bcadd(
|
||||
$amounts[$foreignSymbol]['amount'],
|
||||
(string) $transaction['foreign_amount']
|
||||
);
|
||||
$amounts[$foreignSymbol] ??= [
|
||||
'amount' => '0',
|
||||
'symbol' => $foreignSymbol,
|
||||
'decimal_places' => $transaction['foreign_currency_decimal_places'],
|
||||
];
|
||||
$amounts[$foreignSymbol]['amount'] = bcadd($amounts[$foreignSymbol]['amount'], (string)$transaction['foreign_amount']);
|
||||
}
|
||||
// add primary currency amount
|
||||
if (null !== $transaction['pc_amount'] && $transaction['currency_id'] !== $this->primaryCurrency->id) {
|
||||
// same for foreign currency:
|
||||
$primarySymbol = $this->primaryCurrency->symbol;
|
||||
$amounts[$primarySymbol] ??= [
|
||||
'amount' => '0',
|
||||
'symbol' => $this->primaryCurrency->symbol,
|
||||
'decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
];
|
||||
$amounts[$primarySymbol]['amount'] = bcadd($amounts[$primarySymbol]['amount'], (string)$transaction['pc_amount']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $amounts;
|
||||
@@ -184,16 +217,16 @@ class ShowController extends Controller
|
||||
|
||||
foreach ($group['transactions'] as $transaction) {
|
||||
$accounts['source'][] = [
|
||||
'type' => $transaction['source_type'],
|
||||
'id' => $transaction['source_id'],
|
||||
'name' => $transaction['source_name'],
|
||||
'iban' => $transaction['source_iban'],
|
||||
'type' => $transaction['source_account_type'],
|
||||
'id' => $transaction['source_account_id'],
|
||||
'name' => $transaction['source_account_name'],
|
||||
'iban' => $transaction['source_account_iban'],
|
||||
];
|
||||
$accounts['destination'][] = [
|
||||
'type' => $transaction['destination_type'],
|
||||
'id' => $transaction['destination_id'],
|
||||
'name' => $transaction['destination_name'],
|
||||
'iban' => $transaction['destination_iban'],
|
||||
'type' => $transaction['destination_account_type'],
|
||||
'id' => $transaction['destination_account_id'],
|
||||
'name' => $transaction['destination_account_name'],
|
||||
'iban' => $transaction['destination_account_iban'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -25,13 +25,18 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class WarnAboutBills
|
||||
@@ -63,7 +68,7 @@ class WarnAboutBills implements ShouldQueue
|
||||
|
||||
$this->force = false;
|
||||
|
||||
app('log')->debug(sprintf('Created new WarnAboutBills("%s")', $this->date->format('Y-m-d')));
|
||||
Log::debug(sprintf('Created new WarnAboutBills("%s")', $this->date->format('Y-m-d')));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,36 +76,42 @@ class WarnAboutBills implements ShouldQueue
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now at start of WarnAboutBills() job for %s.', $this->date->format('D d M Y')));
|
||||
$bills = Bill::all();
|
||||
Log::debug(sprintf('Now at start of WarnAboutBills() job for %s.', $this->date->format('D d M Y')));
|
||||
foreach (User::all() as $user) {
|
||||
$bills = $user->bills()->where('active', true)->get();
|
||||
$overdue = [];
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
app('log')->debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
|
||||
if ($this->hasDateFields($bill)) {
|
||||
if ($this->needsWarning($bill, 'end_date')) {
|
||||
$this->sendWarning($bill, 'end_date');
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
Log::debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
|
||||
$dates = $this->getDates($bill);
|
||||
if ($this->needsOverdueAlert($dates)) {
|
||||
$overdue[] = ['bill' => $bill, 'dates' => $dates];
|
||||
}
|
||||
if ($this->needsWarning($bill, 'extension_date')) {
|
||||
$this->sendWarning($bill, 'extension_date');
|
||||
if ($this->hasDateFields($bill)) {
|
||||
if ($this->needsWarning($bill, 'end_date')) {
|
||||
$this->sendWarning($bill, 'end_date');
|
||||
}
|
||||
if ($this->needsWarning($bill, 'extension_date')) {
|
||||
$this->sendWarning($bill, 'extension_date');
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->sendOverdueAlerts($user, $overdue);
|
||||
}
|
||||
app('log')->debug('Done with handle()');
|
||||
Log::debug('Done with handle()');
|
||||
|
||||
// clear cache:
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
private function hasDateFields(Bill $bill): bool
|
||||
{
|
||||
if (false === $bill->active) {
|
||||
app('log')->debug('Bill is not active.');
|
||||
Log::debug('Bill is not active.');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (null === $bill->end_date && null === $bill->extension_date) {
|
||||
app('log')->debug('Bill has no date fields.');
|
||||
Log::debug('Bill has no date fields.');
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -115,7 +126,7 @@ class WarnAboutBills implements ShouldQueue
|
||||
}
|
||||
$diff = $this->getDiff($bill, $field);
|
||||
$list = config('firefly.bill_reminder_periods');
|
||||
app('log')->debug(sprintf('Difference in days for field "%s" ("%s") is %d day(s)', $field, $bill->{$field}->format('Y-m-d'), $diff));
|
||||
Log::debug(sprintf('Difference in days for field "%s" ("%s") is %d day(s)', $field, $bill->{$field}->format('Y-m-d'), $diff));
|
||||
if (in_array($diff, $list, true)) {
|
||||
return true;
|
||||
}
|
||||
@@ -128,13 +139,13 @@ class WarnAboutBills implements ShouldQueue
|
||||
$today = clone $this->date;
|
||||
$carbon = clone $bill->{$field};
|
||||
|
||||
return (int) $today->diffInDays($carbon);
|
||||
return (int)$today->diffInDays($carbon);
|
||||
}
|
||||
|
||||
private function sendWarning(Bill $bill, string $field): void
|
||||
{
|
||||
$diff = $this->getDiff($bill, $field);
|
||||
app('log')->debug('Will now send warning!');
|
||||
Log::debug('Will now send warning!');
|
||||
event(new WarnUserAboutBill($bill, $field, $diff));
|
||||
}
|
||||
|
||||
@@ -149,4 +160,49 @@ class WarnAboutBills implements ShouldQueue
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
private function getDates(Bill $bill): array
|
||||
{
|
||||
$start = clone $this->date;
|
||||
$start = Navigation::startOfPeriod($start, $bill->repeat_freq);
|
||||
$end = clone $start;
|
||||
$end = Navigation::endOfPeriod($end, $bill->repeat_freq);
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($bill->user);
|
||||
$enrichment->setStart($start);
|
||||
$enrichment->setEnd($end);
|
||||
$single = $enrichment->enrichSingle($bill);
|
||||
|
||||
return [
|
||||
'pay_dates' => $single->meta['pay_dates'] ?? [],
|
||||
'paid_dates' => $single->meta['paid_dates'] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
private function needsOverdueAlert(array $dates): bool
|
||||
{
|
||||
$count = count($dates['pay_dates']) - count($dates['paid_dates']);
|
||||
if (0 === $count || 0 === count($dates['pay_dates'])) {
|
||||
return false;
|
||||
}
|
||||
// the earliest date in the list of pay dates must be 48hrs or more ago.
|
||||
$earliest = new Carbon($dates['pay_dates'][0]);
|
||||
$earliest->startOfDay();
|
||||
Log::debug(sprintf('Earliest expected pay date is %s', $earliest->toAtomString()));
|
||||
$diff = $earliest->diffInDays($this->date);
|
||||
Log::debug(sprintf('Difference in days is %s', $diff));
|
||||
if ($diff < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sendOverdueAlerts(User $user, array $overdue): void
|
||||
{
|
||||
if (count($overdue) > 0) {
|
||||
Log::debug(sprintf('Will now send warning about overdue bill for user #%d.', $user->id));
|
||||
event(new WarnUserAboutOverdueSubscriptions($user, $overdue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
113
app/Notifications/User/SubscriptionsOverdueReminder.php
Normal file
113
app/Notifications/User/SubscriptionsOverdueReminder.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Notifications\User;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Notifications\ReturnsAvailableChannels;
|
||||
use FireflyIII\Notifications\ReturnsSettings;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\Pushover\PushoverMessage;
|
||||
|
||||
class SubscriptionsOverdueReminder extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(private array $overdue) {}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toArray(User $notifiable): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
// format the data
|
||||
$info = [];
|
||||
$count = 0;
|
||||
foreach ($this->overdue as $item) {
|
||||
$current = [
|
||||
'bill' => $item['bill'],
|
||||
];
|
||||
$current['pay_dates'] = array_map(
|
||||
static function (string $date): string {
|
||||
return new Carbon($date)->isoFormat((string)trans('config.month_and_day_moment_js'));
|
||||
},
|
||||
$item['dates']['pay_dates']
|
||||
);
|
||||
$info[] = $current;
|
||||
++$count;
|
||||
}
|
||||
|
||||
return new MailMessage()
|
||||
->markdown('emails.subscriptions-overdue-warning', ['info' => $info, 'count' => $count])
|
||||
->subject($this->getSubject())
|
||||
;
|
||||
}
|
||||
|
||||
private function getSubject(): string
|
||||
{
|
||||
if (count($this->overdue) > 1) {
|
||||
return (string)trans('email.subscriptions_overdue_subject_multi', ['count' => count($this->overdue)]);
|
||||
}
|
||||
|
||||
return (string)trans('email.subscriptions_overdue_subject_single');
|
||||
}
|
||||
|
||||
public function toNtfy(User $notifiable): Message
|
||||
{
|
||||
$settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable);
|
||||
$message = new Message();
|
||||
$message->topic($settings['ntfy_topic']);
|
||||
$message->title($this->getSubject());
|
||||
$message->body((string)trans('email.bill_warning_please_action'));
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toPushover(User $notifiable): PushoverMessage
|
||||
{
|
||||
return PushoverMessage::create((string)trans('email.bill_warning_please_action'))
|
||||
->title($this->getSubject())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toSlack(User $notifiable): SlackMessage
|
||||
{
|
||||
$url = route('bills.index');
|
||||
|
||||
return new SlackMessage()
|
||||
->warning()
|
||||
->attachment(static function ($attachment) use ($url): void {
|
||||
$attachment->title((string)trans('firefly.visit_bills'), $url);
|
||||
})
|
||||
->content($this->getSubject())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function via(User $notifiable): array
|
||||
{
|
||||
return ReturnsAvailableChannels::returnChannels('user', $notifiable);
|
||||
}
|
||||
}
|
@@ -27,6 +27,8 @@ use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Events\Admin\InvitationCreated;
|
||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
@@ -58,7 +60,6 @@ use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Events\UpdatedAccount;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Handlers\Observer\AccountObserver;
|
||||
use FireflyIII\Handlers\Observer\AttachmentObserver;
|
||||
use FireflyIII\Handlers\Observer\AutoBudgetObserver;
|
||||
@@ -114,150 +115,153 @@ class EventServiceProvider extends ServiceProvider
|
||||
protected $listen
|
||||
= [
|
||||
// is a User related event.
|
||||
RegisteredUser::class => [
|
||||
RegisteredUser::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendAdminRegistrationNotification',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@createGroupMembership',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@createExchangeRates',
|
||||
],
|
||||
UserAttemptedLogin::class => [
|
||||
UserAttemptedLogin::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendLoginAttemptNotification',
|
||||
],
|
||||
// is a User related event.
|
||||
Login::class => [
|
||||
Login::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
|
||||
],
|
||||
ActuallyLoggedIn::class => [
|
||||
ActuallyLoggedIn::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
|
||||
],
|
||||
DetectedNewIPAddress::class => [
|
||||
DetectedNewIPAddress::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
|
||||
],
|
||||
RequestedVersionCheckStatus::class => [
|
||||
RequestedVersionCheckStatus::class => [
|
||||
'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates',
|
||||
],
|
||||
RequestedReportOnJournals::class => [
|
||||
RequestedReportOnJournals::class => [
|
||||
'FireflyIII\Handlers\Events\AutomationHandler@reportJournals',
|
||||
],
|
||||
|
||||
// is a User related event.
|
||||
RequestedNewPassword::class => [
|
||||
RequestedNewPassword::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword',
|
||||
],
|
||||
UserTestNotificationChannel::class => [
|
||||
UserTestNotificationChannel::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendTestNotification',
|
||||
],
|
||||
// is a User related event.
|
||||
UserChangedEmail::class => [
|
||||
UserChangedEmail::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeConfirmMail',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail',
|
||||
],
|
||||
// admin related
|
||||
OwnerTestNotificationChannel::class => [
|
||||
OwnerTestNotificationChannel::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification',
|
||||
],
|
||||
NewVersionAvailable::class => [
|
||||
NewVersionAvailable::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
|
||||
],
|
||||
InvitationCreated::class => [
|
||||
InvitationCreated::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
|
||||
],
|
||||
UnknownUserAttemptedLogin::class => [
|
||||
UnknownUserAttemptedLogin::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendLoginAttemptNotification',
|
||||
],
|
||||
|
||||
// is a Transaction Journal related event.
|
||||
StoredTransactionGroup::class => [
|
||||
StoredTransactionGroup::class => [
|
||||
'FireflyIII\Handlers\Events\StoredGroupEventHandler@runAllHandlers',
|
||||
],
|
||||
// is a Transaction Journal related event.
|
||||
UpdatedTransactionGroup::class => [
|
||||
UpdatedTransactionGroup::class => [
|
||||
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers',
|
||||
],
|
||||
DestroyedTransactionGroup::class => [
|
||||
DestroyedTransactionGroup::class => [
|
||||
'FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers',
|
||||
],
|
||||
// API related events:
|
||||
AccessTokenCreated::class => [
|
||||
AccessTokenCreated::class => [
|
||||
'FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated',
|
||||
],
|
||||
|
||||
// Webhook related event:
|
||||
RequestedSendWebhookMessages::class => [
|
||||
RequestedSendWebhookMessages::class => [
|
||||
'FireflyIII\Handlers\Events\WebhookEventHandler@sendWebhookMessages',
|
||||
],
|
||||
|
||||
// account related events:
|
||||
StoredAccount::class => [
|
||||
StoredAccount::class => [
|
||||
'FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit',
|
||||
],
|
||||
UpdatedAccount::class => [
|
||||
UpdatedAccount::class => [
|
||||
'FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit',
|
||||
],
|
||||
|
||||
// bill related events:
|
||||
WarnUserAboutBill::class => [
|
||||
WarnUserAboutBill::class => [
|
||||
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
|
||||
],
|
||||
WarnUserAboutOverdueSubscriptions::class => [
|
||||
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscriptions',
|
||||
],
|
||||
|
||||
// audit log events:
|
||||
TriggeredAuditLog::class => [
|
||||
TriggeredAuditLog::class => [
|
||||
'FireflyIII\Handlers\Events\AuditEventHandler@storeAuditEvent',
|
||||
],
|
||||
// piggy bank related events:
|
||||
ChangedAmount::class => [
|
||||
ChangedAmount::class => [
|
||||
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
|
||||
],
|
||||
ChangedName::class => [
|
||||
ChangedName::class => [
|
||||
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
|
||||
],
|
||||
|
||||
// budget related events: CRUD budget limit
|
||||
Created::class => [
|
||||
Created::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created',
|
||||
],
|
||||
Updated::class => [
|
||||
Updated::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated',
|
||||
],
|
||||
Deleted::class => [
|
||||
Deleted::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted',
|
||||
],
|
||||
|
||||
// rule actions
|
||||
RuleActionFailedOnArray::class => [
|
||||
RuleActionFailedOnArray::class => [
|
||||
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
|
||||
],
|
||||
RuleActionFailedOnObject::class => [
|
||||
RuleActionFailedOnObject::class => [
|
||||
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnObject',
|
||||
],
|
||||
|
||||
// security related
|
||||
EnabledMFA::class => [
|
||||
EnabledMFA::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAEnabledMail',
|
||||
],
|
||||
DisabledMFA::class => [
|
||||
DisabledMFA::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFADisabledMail',
|
||||
],
|
||||
MFANewBackupCodes::class => [
|
||||
MFANewBackupCodes::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendNewMFABackupCodesMail',
|
||||
],
|
||||
MFAUsedBackupCode::class => [
|
||||
MFAUsedBackupCode::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendUsedBackupCodeMail',
|
||||
],
|
||||
MFABackupFewLeft::class => [
|
||||
MFABackupFewLeft::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupFewLeftMail',
|
||||
],
|
||||
MFABackupNoLeft::class => [
|
||||
MFABackupNoLeft::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupNoLeftMail',
|
||||
],
|
||||
MFAManyFailedAttempts::class => [
|
||||
MFAManyFailedAttempts::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAFailedAttemptsMail',
|
||||
],
|
||||
// preferences
|
||||
UserGroupChangedPrimaryCurrency::class => [
|
||||
UserGroupChangedPrimaryCurrency::class => [
|
||||
'FireflyIII\Handlers\Events\PreferencesEventHandler@resetPrimaryCurrencyAmounts',
|
||||
],
|
||||
];
|
||||
|
@@ -562,7 +562,13 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
|
||||
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
|
||||
|
||||
// fields
|
||||
'transaction_journals.date', 'transaction_types.type', 'transaction_journals.transaction_currency_id', 'transactions.amount'])
|
||||
'transaction_journals.date',
|
||||
'transaction_types.type',
|
||||
'transaction_journals.transaction_currency_id',
|
||||
'transactions.amount',
|
||||
'transactions.native_amount as pc_amount',
|
||||
'transactions.foreign_amount',
|
||||
])
|
||||
->toArray()
|
||||
;
|
||||
|
||||
|
@@ -50,10 +50,10 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
|
||||
$yesterday = clone $start;
|
||||
$yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including.
|
||||
$end->endOfDay(); // needs to be end of day to be correct.
|
||||
Log::debug(sprintf('getAccountReport: finalAccountsBalance("%s")', $yesterday->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('getAccountReport: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startSet = Steam::finalAccountsBalance($accounts, $yesterday);
|
||||
$endSet = Steam::finalAccountsBalance($accounts, $end);
|
||||
Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $yesterday->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
|
||||
$startSet = Steam::accountsBalancesOptimized($accounts, $yesterday);
|
||||
$endSet = Steam::accountsBalancesOptimized($accounts, $end);
|
||||
Log::debug('Start of accountreport');
|
||||
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
|
@@ -52,4 +52,10 @@ class ALERepository implements ALERepositoryInterface
|
||||
|
||||
return $auditLogEntry;
|
||||
}
|
||||
|
||||
public function getForId(string $model, int $modelId): Collection
|
||||
{
|
||||
// all Models have an ID.
|
||||
return AuditLogEntry::where('auditable_id', $modelId)->where('auditable_type', $model)->get();
|
||||
}
|
||||
}
|
||||
|
@@ -46,5 +46,7 @@ interface ALERepositoryInterface
|
||||
{
|
||||
public function getForObject(Model $model): Collection;
|
||||
|
||||
public function getForId(string $model, int $modelId): Collection;
|
||||
|
||||
public function store(array $data): AuditLogEntry;
|
||||
}
|
||||
|
@@ -202,8 +202,11 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
'amount' => $amount,
|
||||
'destination_account_id' => $journal['destination_account_id'],
|
||||
'destination_account_name' => $journal['destination_account_name'],
|
||||
'destination_account_type' => $journal['destination_account_type'],
|
||||
'currency_id' => $journalCurrencyId,
|
||||
'source_account_id' => $journal['source_account_id'],
|
||||
'source_account_name' => $journal['source_account_name'],
|
||||
'source_account_type' => $journal['source_account_type'],
|
||||
'category_name' => $journal['category_name'],
|
||||
'description' => $journal['description'],
|
||||
'transaction_group_id' => $journal['transaction_group_id'],
|
||||
|
@@ -55,20 +55,17 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
|
||||
// orderBy('date', 'DESC')->toRawSql();
|
||||
return
|
||||
$this->userGroup->currencyExchangeRates()
|
||||
->where(function (Builder $q1) use ($from, $to): void {
|
||||
$q1->where(function (Builder $q) use ($from, $to): void {
|
||||
$q->where('from_currency_id', $from->id)
|
||||
->where('to_currency_id', $to->id)
|
||||
;
|
||||
})->orWhere(function (Builder $q) use ($from, $to): void {
|
||||
$q->where('from_currency_id', $to->id)
|
||||
->where('to_currency_id', $from->id)
|
||||
;
|
||||
});
|
||||
})
|
||||
->orderBy('date', 'DESC')
|
||||
->get(['currency_exchange_rates.*'])
|
||||
;
|
||||
->where(function (Builder $q1) use ($from, $to): void {
|
||||
$q1->where(function (Builder $q) use ($from, $to): void {
|
||||
$q->where('from_currency_id', $from->id)
|
||||
->where('to_currency_id', $to->id);
|
||||
})->orWhere(function (Builder $q) use ($from, $to): void {
|
||||
$q->where('from_currency_id', $to->id)
|
||||
->where('to_currency_id', $from->id);
|
||||
});
|
||||
})
|
||||
->orderBy('date', 'DESC')
|
||||
->get(['currency_exchange_rates.*']);
|
||||
|
||||
}
|
||||
|
||||
@@ -78,11 +75,10 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
|
||||
/** @var null|CurrencyExchangeRate */
|
||||
return
|
||||
$this->userGroup->currencyExchangeRates()
|
||||
->where('from_currency_id', $from->id)
|
||||
->where('to_currency_id', $to->id)
|
||||
->where('date', $date->format('Y-m-d'))
|
||||
->first()
|
||||
;
|
||||
->where('from_currency_id', $from->id)
|
||||
->where('to_currency_id', $to->id)
|
||||
->where('date', $date->format('Y-m-d'))
|
||||
->first();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
@@ -112,4 +108,12 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function deleteRates(TransactionCurrency $from, TransactionCurrency $to): void
|
||||
{
|
||||
$this->userGroup->currencyExchangeRates()
|
||||
->where('from_currency_id', $from->id)
|
||||
->where('to_currency_id', $to->id)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ use Illuminate\Support\Collection;
|
||||
interface ExchangeRateRepositoryInterface
|
||||
{
|
||||
public function deleteRate(CurrencyExchangeRate $rate): void;
|
||||
public function deleteRates(TransactionCurrency $from, TransactionCurrency $to): void;
|
||||
|
||||
public function getAll(): Collection;
|
||||
|
||||
|
@@ -32,6 +32,7 @@ use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use JsonException;
|
||||
|
||||
use function Safe\json_encode;
|
||||
@@ -65,9 +66,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
try {
|
||||
$signature = $signatureGenerator->generate($this->message);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error('Did not send message because of a Firefly III Exception.');
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error('Did not send message because of a Firefly III Exception.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$attempt = new WebhookAttempt();
|
||||
$attempt->webhookMessage()->associate($this->message);
|
||||
$attempt->status_code = 0;
|
||||
@@ -80,14 +81,14 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
return;
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Trying to send webhook message #%d', $this->message->id));
|
||||
Log::debug(sprintf('Trying to send webhook message #%d', $this->message->id));
|
||||
|
||||
try {
|
||||
$json = json_encode($this->message->message, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
app('log')->error('Did not send message because of a JSON error.');
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error('Did not send message because of a JSON error.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$attempt = new WebhookAttempt();
|
||||
$attempt->webhookMessage()->associate($this->message);
|
||||
$attempt->status_code = 0;
|
||||
@@ -115,9 +116,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
try {
|
||||
$res = $client->request('POST', $this->message->webhook->url, $options);
|
||||
} catch (ConnectException|RequestException $e) {
|
||||
app('log')->error('The webhook could NOT be submitted but Firefly III caught the error below.');
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error('The webhook could NOT be submitted but Firefly III caught the error below.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
$logs = sprintf("%s\n%s", $e->getMessage(), $e->getTraceAsString());
|
||||
|
||||
@@ -130,9 +131,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
$attempt->status_code = 0;
|
||||
if (method_exists($e, 'hasResponse') && method_exists($e, 'getResponse')) {
|
||||
$attempt->status_code = $e->hasResponse() ? $e->getResponse()->getStatusCode() : 0;
|
||||
app('log')->error(sprintf('The status code of the error response is: %d', $attempt->status_code));
|
||||
Log::error(sprintf('The status code of the error response is: %d', $attempt->status_code));
|
||||
$body = (string) ($e->hasResponse() ? $e->getResponse()->getBody() : '');
|
||||
app('log')->error(sprintf('The body of the error response is: %s', $body));
|
||||
Log::error(sprintf('The body of the error response is: %s', $body));
|
||||
}
|
||||
$attempt->logs = $logs;
|
||||
$attempt->save();
|
||||
@@ -142,9 +143,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
$this->message->sent = true;
|
||||
$this->message->save();
|
||||
|
||||
app('log')->debug(sprintf('Webhook message #%d was sent. Status code %d', $this->message->id, $res->getStatusCode()));
|
||||
app('log')->debug(sprintf('Webhook request body size: %d bytes', strlen($json)));
|
||||
app('log')->debug(sprintf('Response body: %s', $res->getBody()));
|
||||
Log::debug(sprintf('Webhook message #%d was sent. Status code %d', $this->message->id, $res->getStatusCode()));
|
||||
Log::debug(sprintf('Webhook request body size: %d bytes', strlen($json)));
|
||||
Log::debug(sprintf('Response body: %s', $res->getBody()));
|
||||
}
|
||||
|
||||
public function setMessage(WebhookMessage $message): void
|
||||
|
@@ -29,9 +29,9 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Singleton\PreferencesSingleton;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use NumberFormatter;
|
||||
|
||||
/**
|
||||
@@ -116,11 +116,28 @@ class Amount
|
||||
|
||||
public function convertToPrimary(?User $user = null): bool
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
if (!$user instanceof User) {
|
||||
return true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$pref = $instance->getPreference('convert_to_primary_no_user');
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference('convert_to_primary_no_user', $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return $pref;
|
||||
}
|
||||
$key = sprintf('convert_to_primary_%d', $user->id);
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference($key, $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
return $pref;
|
||||
}
|
||||
|
||||
public function getPrimaryCurrency(): TransactionCurrency
|
||||
|
@@ -26,6 +26,9 @@ namespace FireflyIII\Support\Chart;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
class ChartData
|
||||
{
|
||||
private array $series;
|
||||
|
@@ -28,6 +28,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Jobs\CreateAutoBudgetLimits;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AutoBudgetCronjob
|
||||
@@ -42,22 +43,22 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
if (0 === $lastTime) {
|
||||
app('log')->info('Auto budget cron-job has never fired before.');
|
||||
Log::info('Auto budget cron-job has never fired before.');
|
||||
}
|
||||
// less than half a day ago:
|
||||
if ($lastTime > 0 && $diff <= 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the auto budget cron-job has fired.', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the auto budget cron-job has fired.', $diffForHumans));
|
||||
if (false === $this->force) {
|
||||
app('log')->info('The auto budget cron-job will not fire now.');
|
||||
Log::info('The auto budget cron-job will not fire now.');
|
||||
$this->message = sprintf('It has been %s since the auto budget cron-job has fired. It will not fire now.', $diffForHumans);
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->info('Execution of the auto budget cron-job has been FORCED.');
|
||||
Log::info('Execution of the auto budget cron-job has been FORCED.');
|
||||
}
|
||||
|
||||
if ($lastTime > 0 && $diff > 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the auto budget cron-job has fired. It will fire now!', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the auto budget cron-job has fired. It will fire now!', $diffForHumans));
|
||||
}
|
||||
|
||||
$this->fireAutoBudget();
|
||||
@@ -66,7 +67,7 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
|
||||
private function fireAutoBudget(): void
|
||||
{
|
||||
app('log')->info(sprintf('Will now fire auto budget cron job task for date "%s".', $this->date->format('Y-m-d')));
|
||||
Log::info(sprintf('Will now fire auto budget cron job task for date "%s".', $this->date->format('Y-m-d')));
|
||||
|
||||
/** @var CreateAutoBudgetLimits $job */
|
||||
$job = app(CreateAutoBudgetLimits::class, [$this->date]);
|
||||
@@ -80,6 +81,6 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
$this->message = 'Auto-budget cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_ab_job', (int) $this->date->format('U'));
|
||||
app('log')->info('Done with auto budget cron job task.');
|
||||
Log::info('Done with auto budget cron job task.');
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Jobs\WarnAboutBills;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BillWarningCronjob
|
||||
@@ -39,22 +41,22 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
*/
|
||||
public function fire(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
/** @var Configuration $config */
|
||||
$config = app('fireflyconfig')->get('last_bw_job', 0);
|
||||
$config = FireflyConfig::get('last_bw_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
if (0 === $lastTime) {
|
||||
app('log')->info('The bill notification cron-job has never fired before.');
|
||||
Log::info('The bill notification cron-job has never fired before.');
|
||||
}
|
||||
// less than half a day ago:
|
||||
if ($lastTime > 0 && $diff <= 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the bill notification cron-job has fired.', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the bill notification cron-job has fired.', $diffForHumans));
|
||||
if (false === $this->force) {
|
||||
app('log')->info('The cron-job will not fire now.');
|
||||
Log::info('The cron-job will not fire now.');
|
||||
$this->message = sprintf('It has been %s since the bill notification cron-job has fired. It will not fire now.', $diffForHumans);
|
||||
$this->jobFired = false;
|
||||
$this->jobErrored = false;
|
||||
@@ -63,11 +65,11 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
return;
|
||||
}
|
||||
|
||||
app('log')->info('Execution of the bill notification cron-job has been FORCED.');
|
||||
Log::info('Execution of the bill notification cron-job has been FORCED.');
|
||||
}
|
||||
|
||||
if ($lastTime > 0 && $diff > 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the bill notification cron-job has fired. It will fire now!', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the bill notification cron-job has fired. It will fire now!', $diffForHumans));
|
||||
}
|
||||
|
||||
$this->fireWarnings();
|
||||
@@ -77,7 +79,7 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
|
||||
private function fireWarnings(): void
|
||||
{
|
||||
app('log')->info(sprintf('Will now fire bill notification job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
Log::info(sprintf('Will now fire bill notification job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
|
||||
/** @var WarnAboutBills $job */
|
||||
$job = app(WarnAboutBills::class);
|
||||
@@ -91,8 +93,8 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Bill notification cron job fired successfully.';
|
||||
|
||||
app('fireflyconfig')->set('last_bw_job', (int) $this->date->format('U'));
|
||||
app('log')->info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
app('log')->info('Done with bill notification cron job task.');
|
||||
FireflyConfig::set('last_bw_job', (int) $this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
Log::info('Done with bill notification cron job task.');
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ namespace FireflyIII\Support\Cronjobs;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Jobs\DownloadExchangeRates;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
|
||||
public function fire(): void
|
||||
{
|
||||
/** @var Configuration $config */
|
||||
$config = app('fireflyconfig')->get('last_cer_job', 0);
|
||||
$config = FireflyConfig::get('last_cer_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
@@ -80,7 +81,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Exchange rates cron job fired successfully.';
|
||||
|
||||
app('fireflyconfig')->set('last_cer_job', (int) $this->date->format('U'));
|
||||
FireflyConfig::set('last_cer_job', (int) $this->date->format('U'));
|
||||
Log::info('Done with exchange rates job task.');
|
||||
}
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ class UpdateCheckCronjob extends AbstractCronjob
|
||||
Log::debug('Now in checkForUpdates()');
|
||||
|
||||
// should not check for updates:
|
||||
$permission = app('fireflyconfig')->get('permission_update_check', -1);
|
||||
$permission = FireflyConfig::get('permission_update_check', -1);
|
||||
$value = (int) $permission->data;
|
||||
if (1 !== $value) {
|
||||
Log::debug('Update check is not enabled.');
|
||||
|
97
app/Support/Cronjobs/WebhookCronjob.php
Normal file
97
app/Support/Cronjobs/WebhookCronjob.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RecurringCronjob.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Cronjobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class WebhookCronjob
|
||||
*/
|
||||
class WebhookCronjob extends AbstractCronjob
|
||||
{
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function fire(): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
/** @var Configuration $config */
|
||||
$config = FireflyConfig::get('last_webhook_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
if (0 === $lastTime) {
|
||||
Log::info('The webhook cron-job has never fired before.');
|
||||
}
|
||||
// less than ten minutes ago.
|
||||
if ($lastTime > 0 && $diff <= 600) {
|
||||
Log::info(sprintf('It has been %s since the webhook cron-job has fired.', $diffForHumans));
|
||||
if (false === $this->force) {
|
||||
Log::info('The cron-job will not fire now.');
|
||||
$this->message = sprintf('It has been %s since the webhook cron-job has fired. It will not fire now.', $diffForHumans);
|
||||
$this->jobFired = false;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::info('Execution of the webhook cron-job has been FORCED.');
|
||||
}
|
||||
|
||||
if ($lastTime > 0 && $diff > 600) {
|
||||
Log::info(sprintf('It has been %s since the webhook cron-job has fired. It will fire now!', $diffForHumans));
|
||||
}
|
||||
|
||||
$this->fireWebhookmessages();
|
||||
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
private function fireWebhookmessages(): void
|
||||
{
|
||||
Log::info(sprintf('Will now send webhook messages for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
|
||||
Log::debug('send event RequestedSendWebhookMessages through cron job.');
|
||||
event(new RequestedSendWebhookMessages());
|
||||
|
||||
// get stuff from job:
|
||||
$this->jobFired = true;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Send webhook messages cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_webhook_job', (int) $this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
Log::info('Done with webhook cron job task.');
|
||||
}
|
||||
}
|
@@ -28,19 +28,34 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Timer
|
||||
{
|
||||
private static array $times = [];
|
||||
private array $times = [];
|
||||
private static ?Timer $instance = null;
|
||||
|
||||
public static function start(string $title): void
|
||||
private function __construct()
|
||||
{
|
||||
self::$times[$title] = microtime(true);
|
||||
// Private constructor to prevent direct instantiation.
|
||||
}
|
||||
|
||||
public static function stop(string $title): void
|
||||
public static function getInstance(): self
|
||||
{
|
||||
$start = self::$times[$title] ?? 0;
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function start(string $title): void
|
||||
{
|
||||
$this->times[$title] = microtime(true);
|
||||
}
|
||||
|
||||
public function stop(string $title): void
|
||||
{
|
||||
$start = $this->times[$title] ?? 0;
|
||||
$end = microtime(true);
|
||||
$diff = $end - $start;
|
||||
unset(self::$times[$title]);
|
||||
unset($this->times[$title]);
|
||||
Log::debug(sprintf('Timer "%s" took %f seconds', $title, $diff));
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -64,36 +66,40 @@ class AccountBalanceGrouped
|
||||
/** @var array $currency */
|
||||
foreach ($this->data as $currency) {
|
||||
// income and expense array prepped:
|
||||
$income = [
|
||||
$income = [
|
||||
'label' => 'earned',
|
||||
'currency_id' => (string) $currency['currency_id'],
|
||||
'currency_id' => (string)$currency['currency_id'],
|
||||
'currency_symbol' => $currency['currency_symbol'],
|
||||
'currency_code' => $currency['currency_code'],
|
||||
'currency_decimal_places' => $currency['currency_decimal_places'],
|
||||
'primary_currency_id' => (string) $currency['primary_currency_id'],
|
||||
'primary_currency_id' => (string)$currency['primary_currency_id'],
|
||||
'primary_currency_symbol' => $currency['primary_currency_symbol'],
|
||||
'primary_currency_code' => $currency['primary_currency_code'],
|
||||
'primary_currency_decimal_places' => $currency['primary_currency_decimal_places'],
|
||||
'date' => $this->start->toAtomString(),
|
||||
'start' => $this->start->toAtomString(),
|
||||
'end' => $this->end->toAtomString(),
|
||||
'start_date' => $this->start->toAtomString(),
|
||||
'end_date' => $this->end->toAtomString(),
|
||||
'yAxisID' => 0,
|
||||
'type' => 'line',
|
||||
'period' => $this->preferredRange,
|
||||
'entries' => [],
|
||||
'primary_entries' => [],
|
||||
'pc_entries' => [],
|
||||
];
|
||||
$expense = [
|
||||
$expense = [
|
||||
'label' => 'spent',
|
||||
'currency_id' => (string) $currency['currency_id'],
|
||||
'currency_id' => (string)$currency['currency_id'],
|
||||
'currency_symbol' => $currency['currency_symbol'],
|
||||
'currency_code' => $currency['currency_code'],
|
||||
'currency_decimal_places' => $currency['currency_decimal_places'],
|
||||
'primary_currency_id' => (string) $currency['primary_currency_id'],
|
||||
'primary_currency_id' => (string)$currency['primary_currency_id'],
|
||||
'primary_currency_symbol' => $currency['primary_currency_symbol'],
|
||||
'primary_currency_code' => $currency['primary_currency_code'],
|
||||
'primary_currency_decimal_places' => $currency['primary_currency_decimal_places'],
|
||||
'date' => $this->start->toAtomString(),
|
||||
'start' => $this->start->toAtomString(),
|
||||
'end' => $this->end->toAtomString(),
|
||||
'start_date' => $this->start->toAtomString(),
|
||||
'end_date' => $this->end->toAtomString(),
|
||||
'type' => 'line',
|
||||
'yAxisID' => 0,
|
||||
'period' => $this->preferredRange,
|
||||
'entries' => [],
|
||||
'pc_entries' => [],
|
||||
@@ -101,22 +107,22 @@ class AccountBalanceGrouped
|
||||
// loop all possible periods between $start and $end, and add them to the correct dataset.
|
||||
$currentStart = clone $this->start;
|
||||
while ($currentStart <= $this->end) {
|
||||
$key = $currentStart->format($this->carbonFormat);
|
||||
$label = $currentStart->toAtomString();
|
||||
$key = $currentStart->format($this->carbonFormat);
|
||||
$label = $currentStart->toAtomString();
|
||||
// normal entries
|
||||
$income['entries'][$label] = app('steam')->bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
|
||||
$expense['entries'][$label] = app('steam')->bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
|
||||
$income['entries'][$label] = Steam::bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
|
||||
$expense['entries'][$label] = Steam::bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
|
||||
|
||||
// converted entries
|
||||
$income['pc_entries'][$label] = app('steam')->bcround($currency[$key]['pc_earned'] ?? '0', $currency['primary_currency_decimal_places']);
|
||||
$expense['pc_entries'][$label] = app('steam')->bcround($currency[$key]['pc_spent'] ?? '0', $currency['primary_currency_decimal_places']);
|
||||
$income['pc_entries'][$label] = Steam::bcround($currency[$key]['pc_earned'] ?? '0', $currency['primary_currency_decimal_places']);
|
||||
$expense['pc_entries'][$label] = Steam::bcround($currency[$key]['pc_spent'] ?? '0', $currency['primary_currency_decimal_places']);
|
||||
|
||||
// next loop
|
||||
$currentStart = app('navigation')->addPeriod($currentStart, $this->preferredRange, 0);
|
||||
$currentStart = Navigation::addPeriod($currentStart, $this->preferredRange, 0);
|
||||
}
|
||||
|
||||
$chartData[] = $income;
|
||||
$chartData[] = $expense;
|
||||
$chartData[] = $income;
|
||||
$chartData[] = $expense;
|
||||
}
|
||||
|
||||
return $chartData;
|
||||
@@ -142,9 +148,9 @@ class AccountBalanceGrouped
|
||||
private function processJournal(array $journal): void
|
||||
{
|
||||
// format the date according to the period
|
||||
$period = $journal['date']->format($this->carbonFormat);
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currency = $this->findCurrency($currencyId);
|
||||
$period = $journal['date']->format($this->carbonFormat);
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currency = $this->findCurrency($currencyId);
|
||||
|
||||
// set the array with monetary info, if it does not exist.
|
||||
$this->createDefaultDataEntry($journal);
|
||||
@@ -152,25 +158,25 @@ class AccountBalanceGrouped
|
||||
$this->createDefaultPeriodEntry($journal);
|
||||
|
||||
// is this journal's amount in- our outgoing?
|
||||
$key = $this->getDataKey($journal);
|
||||
$amount = 'spent' === $key ? app('steam')->negative($journal['amount']) : app('steam')->positive($journal['amount']);
|
||||
$key = $this->getDataKey($journal);
|
||||
$amount = 'spent' === $key ? Steam::negative($journal['amount']) : Steam::positive($journal['amount']);
|
||||
|
||||
// get conversion rate
|
||||
$rate = $this->getRate($currency, $journal['date']);
|
||||
$amountConverted = bcmul((string) $amount, $rate);
|
||||
$rate = $this->getRate($currency, $journal['date']);
|
||||
$amountConverted = bcmul((string)$amount, $rate);
|
||||
|
||||
// perhaps transaction already has the foreign amount in the primary currency.
|
||||
if ((int) $journal['foreign_currency_id'] === $this->primary->id) {
|
||||
if ((int)$journal['foreign_currency_id'] === $this->primary->id) {
|
||||
$amountConverted = $journal['foreign_amount'] ?? '0';
|
||||
$amountConverted = 'earned' === $key ? app('steam')->positive($amountConverted) : app('steam')->negative($amountConverted);
|
||||
$amountConverted = 'earned' === $key ? Steam::positive($amountConverted) : Steam::negative($amountConverted);
|
||||
}
|
||||
|
||||
// add normal entry
|
||||
$this->data[$currencyId][$period][$key] = bcadd((string) $this->data[$currencyId][$period][$key], (string) $amount);
|
||||
$this->data[$currencyId][$period][$key] = bcadd((string)$this->data[$currencyId][$period][$key], (string)$amount);
|
||||
|
||||
// add converted entry
|
||||
$convertedKey = sprintf('pc_%s', $key);
|
||||
$this->data[$currencyId][$period][$convertedKey] = bcadd((string) $this->data[$currencyId][$period][$convertedKey], (string) $amountConverted);
|
||||
$this->data[$currencyId][$period][$convertedKey] = bcadd((string)$this->data[$currencyId][$period][$convertedKey], (string)$amountConverted);
|
||||
}
|
||||
|
||||
private function findCurrency(int $currencyId): TransactionCurrency
|
||||
@@ -185,15 +191,15 @@ class AccountBalanceGrouped
|
||||
|
||||
private function createDefaultDataEntry(array $journal): void
|
||||
{
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$this->data[$currencyId] ??= [
|
||||
'currency_id' => (string) $currencyId,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_decimal_places' => $journal['currency_decimal_places'],
|
||||
// primary currency info (could be the same)
|
||||
'primary_currency_id' => (string) $this->primary->id,
|
||||
'primary_currency_id' => (string)$this->primary->id,
|
||||
'primary_currency_code' => $this->primary->code,
|
||||
'primary_currency_symbol' => $this->primary->symbol,
|
||||
'primary_currency_decimal_places' => $this->primary->decimal_places,
|
||||
@@ -202,14 +208,14 @@ class AccountBalanceGrouped
|
||||
|
||||
private function createDefaultPeriodEntry(array $journal): void
|
||||
{
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$period = $journal['date']->format($this->carbonFormat);
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$period = $journal['date']->format($this->carbonFormat);
|
||||
$this->data[$currencyId][$period] ??= [
|
||||
'period' => $period,
|
||||
'spent' => '0',
|
||||
'earned' => '0',
|
||||
'pc_spent' => '0',
|
||||
'pc_earned' => '0',
|
||||
'period' => $period,
|
||||
'spent' => '0',
|
||||
'earned' => '0',
|
||||
'pc_spent' => '0',
|
||||
'pc_earned' => '0',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -258,12 +264,12 @@ class AccountBalanceGrouped
|
||||
$primaryCurrencyId = $primary->id;
|
||||
$this->currencies = [$primary->id => $primary]; // currency cache
|
||||
$this->data[$primaryCurrencyId] = [
|
||||
'currency_id' => (string) $primaryCurrencyId,
|
||||
'currency_id' => (string)$primaryCurrencyId,
|
||||
'currency_symbol' => $primary->symbol,
|
||||
'currency_code' => $primary->code,
|
||||
'currency_name' => $primary->name,
|
||||
'currency_decimal_places' => $primary->decimal_places,
|
||||
'primary_currency_id' => (string) $primaryCurrencyId,
|
||||
'primary_currency_id' => (string)$primaryCurrencyId,
|
||||
'primary_currency_symbol' => $primary->symbol,
|
||||
'primary_currency_code' => $primary->code,
|
||||
'primary_currency_name' => $primary->name,
|
||||
@@ -284,7 +290,7 @@ class AccountBalanceGrouped
|
||||
public function setPreferredRange(string $preferredRange): void
|
||||
{
|
||||
$this->preferredRange = $preferredRange;
|
||||
$this->carbonFormat = app('navigation')->preferredCarbonFormatByPeriod($preferredRange);
|
||||
$this->carbonFormat = Navigation::preferredCarbonFormatByPeriod($preferredRange);
|
||||
}
|
||||
|
||||
public function setStart(Carbon $start): void
|
||||
|
@@ -47,24 +47,29 @@ trait CleansChartData
|
||||
* @var array $array
|
||||
*/
|
||||
foreach ($data as $index => $array) {
|
||||
if (array_key_exists('currency_id', $array)) {
|
||||
$array['currency_id'] = (string) $array['currency_id'];
|
||||
}
|
||||
if (array_key_exists('primary_currency_id', $array)) {
|
||||
$array['primary_currency_id'] = (string) $array['primary_currency_id'];
|
||||
}
|
||||
if (!array_key_exists('start', $array)) {
|
||||
throw new FireflyException(sprintf('Data-set "%s" is missing the "start"-variable.', $index));
|
||||
}
|
||||
if (!array_key_exists('end', $array)) {
|
||||
throw new FireflyException(sprintf('Data-set "%s" is missing the "end"-variable.', $index));
|
||||
}
|
||||
if (!array_key_exists('period', $array)) {
|
||||
throw new FireflyException(sprintf('Data-set "%s" is missing the "period"-variable.', $index));
|
||||
}
|
||||
$array = $this->cleanSingleArray($index, $array);
|
||||
$return[] = $array;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function cleanSingleArray(mixed $index, array $array): array {
|
||||
if (array_key_exists('currency_id', $array)) {
|
||||
$array['currency_id'] = (string)$array['currency_id'];
|
||||
}
|
||||
if (array_key_exists('primary_currency_id', $array)) {
|
||||
$array['primary_currency_id'] = (string)$array['primary_currency_id'];
|
||||
}
|
||||
$required = [
|
||||
'start_date', 'end_date', 'period', 'yAxisID','type','entries','pc_entries',
|
||||
'currency_id', 'primary_currency_id'
|
||||
];
|
||||
foreach ($required as $field) {
|
||||
if (!array_key_exists($field, $array)) {
|
||||
throw new FireflyException(sprintf('Data-set "%s" is missing the "%s"-variable.', $index, $field));
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ trait CollectsAccountsFromFilter
|
||||
if ('all' === $queryParameters['preselected']) {
|
||||
return $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value]);
|
||||
}
|
||||
if ('assets' === $queryParameters['preselected']) {
|
||||
if ('assets' === $queryParameters['preselected'] || 'Asset account' === $queryParameters['preselected']) {
|
||||
return $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
|
||||
}
|
||||
if ('liabilities' === $queryParameters['preselected']) {
|
||||
|
@@ -67,7 +67,7 @@ trait ChartGeneration
|
||||
/** @var AccountRepositoryInterface $accountRepos */
|
||||
$accountRepos = app(AccountRepositoryInterface::class);
|
||||
|
||||
$default = app('amount')->getPrimaryCurrency();
|
||||
$primary = app('amount')->getPrimaryCurrency();
|
||||
$chartData = [];
|
||||
|
||||
Log::debug(sprintf('Start of accountBalanceChart(list, %s, %s)', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
@@ -75,10 +75,10 @@ trait ChartGeneration
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Now at account #%d ("%s)', $account->id, $account->name));
|
||||
$currency = $accountRepos->getAccountCurrency($account) ?? $default;
|
||||
$usePrimary = $convertToPrimary && $default->id !== $currency->id;
|
||||
$currency = $accountRepos->getAccountCurrency($account) ?? $primary;
|
||||
$usePrimary = $convertToPrimary && $primary->id !== $currency->id;
|
||||
$field = $convertToPrimary ? 'pc_balance' : 'balance';
|
||||
$currency = $usePrimary ? $default : $currency;
|
||||
$currency = $usePrimary ? $primary : $currency;
|
||||
Log::debug(sprintf('Will use field %s', $field));
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Support\Cronjobs\AutoBudgetCronjob;
|
||||
use FireflyIII\Support\Cronjobs\BillWarningCronjob;
|
||||
use FireflyIII\Support\Cronjobs\ExchangeRatesCronjob;
|
||||
use FireflyIII\Support\Cronjobs\RecurringCronjob;
|
||||
use FireflyIII\Support\Cronjobs\WebhookCronjob;
|
||||
|
||||
/**
|
||||
* Trait CronRunner
|
||||
@@ -62,6 +63,32 @@ trait CronRunner
|
||||
];
|
||||
}
|
||||
|
||||
protected function webhookCronJob(bool $force, Carbon $date): array
|
||||
{
|
||||
/** @var WebhookCronjob $webhook */
|
||||
$webhook = app(WebhookCronjob::class);
|
||||
$webhook->setForce($force);
|
||||
$webhook->setDate($date);
|
||||
|
||||
try {
|
||||
$webhook->fire();
|
||||
} catch (FireflyException $e) {
|
||||
return [
|
||||
'job_fired' => false,
|
||||
'job_succeeded' => false,
|
||||
'job_errored' => true,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'job_fired' => $webhook->jobFired,
|
||||
'job_succeeded' => $webhook->jobSucceeded,
|
||||
'job_errored' => $webhook->jobErrored,
|
||||
'message' => $webhook->message,
|
||||
];
|
||||
}
|
||||
|
||||
protected function exchangeRatesCronJob(bool $force, Carbon $date): array
|
||||
{
|
||||
/** @var ExchangeRatesCronjob $exchangeRates */
|
||||
|
@@ -35,6 +35,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -79,9 +80,10 @@ trait PeriodOverview
|
||||
protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug('Now in getAccountPeriodOverview()');
|
||||
Timer::start('account-period-total');
|
||||
$timer = Timer::getInstance();
|
||||
$timer->start('account-period-total');
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for cache
|
||||
@@ -91,32 +93,30 @@ trait PeriodOverview
|
||||
$cache->addProperty('account-show-period-entries');
|
||||
$cache->addProperty($account->id);
|
||||
if ($cache->has()) {
|
||||
Log::debug('Return CACHED in getAccountPeriodOverview()');
|
||||
|
||||
return $cache->get();
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
$transferredAway = [];
|
||||
$transferredIn = [];
|
||||
|
||||
// run a custom query because doing this with the collector is MEGA slow.
|
||||
$timer->start('account-period-collect');
|
||||
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
|
||||
|
||||
$timer->stop('account-period-collect');
|
||||
// loop dates
|
||||
Log::debug(sprintf('Count of loops: %d', count($dates)));
|
||||
$loops = 0;
|
||||
// stop after 10 loops for memory reasons.
|
||||
$timer->start('account-period-loop');
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
|
||||
if ($loops < 10) {
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
$title = Navigation::periodShow($currentDate['start'], $currentDate['period']);
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
@@ -129,8 +129,9 @@ trait PeriodOverview
|
||||
];
|
||||
++$loops;
|
||||
}
|
||||
$timer->stop('account-period-loop');
|
||||
$cache->store($entries);
|
||||
Timer::stop('account-period-total');
|
||||
$timer->stop('account-period-total');
|
||||
Log::debug('End of getAccountPeriodOverview()');
|
||||
|
||||
return $entries;
|
||||
@@ -138,7 +139,8 @@ trait PeriodOverview
|
||||
|
||||
private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
$result = [];
|
||||
$filtered = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
@@ -146,38 +148,46 @@ trait PeriodOverview
|
||||
*/
|
||||
foreach ($transactions as $index => $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
if ($item['type'] === $type->value && $date >= $start && $date <= $end) {
|
||||
$fits = $item['type'] === $type->value && $date >= $start && $date <= $end;
|
||||
if ($fits) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
}
|
||||
if (!$fits) {
|
||||
$filtered[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return [$transactions, $result];
|
||||
return [$filtered, $result];
|
||||
}
|
||||
|
||||
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
$result = [];
|
||||
$filtered = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $item
|
||||
*/
|
||||
foreach ($transactions as $index => $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
$date = Carbon::parse($item['date']);
|
||||
if ($date >= $start && $date <= $end) {
|
||||
if ('away' === $direction && -1 === bccomp((string) $item['amount'], '0')) {
|
||||
if ('away' === $direction && -1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
|
||||
continue;
|
||||
}
|
||||
if ('in' === $direction && 1 === bccomp((string) $item['amount'], '0')) {
|
||||
if ('in' === $direction && 1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$filtered[] = $item;
|
||||
}
|
||||
|
||||
return [$transactions, $result];
|
||||
return [$filtered, $result];
|
||||
}
|
||||
|
||||
private function groupByCurrency(array $journals): array
|
||||
@@ -186,7 +196,7 @@ trait PeriodOverview
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
@@ -203,7 +213,7 @@ trait PeriodOverview
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId === $this->primaryCurrency->id) {
|
||||
$currencyId = (int) $foreignCurrencyId;
|
||||
$currencyId = (int)$foreignCurrencyId;
|
||||
$currencyCode = $journal['foreign_currency_code'];
|
||||
$currencyName = $journal['foreign_currency_name'];
|
||||
$currencySymbol = $journal['foreign_currency_symbol'];
|
||||
@@ -235,7 +245,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for entries with their amounts.
|
||||
@@ -251,7 +261,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// collect all expenses in this period:
|
||||
@@ -281,7 +291,7 @@ trait PeriodOverview
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'transactions' => 0,
|
||||
@@ -327,7 +337,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
@@ -342,7 +352,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// get all expenses without a budget.
|
||||
@@ -353,7 +363,7 @@ trait PeriodOverview
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
@@ -380,17 +390,17 @@ trait PeriodOverview
|
||||
protected function getNoCategoryPeriodOverview(Carbon $theDate): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
$first = $this->journalRepos->firstNull();
|
||||
$start = null === $first ? new Carbon() : $first->date;
|
||||
$end = clone $theDate;
|
||||
$end = app('navigation')->endOfPeriod($end, $range);
|
||||
$end = Navigation::endOfPeriod($end, $range);
|
||||
|
||||
app('log')->debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
|
||||
|
||||
// properties for cache
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// collect all expenses in this period:
|
||||
@@ -422,7 +432,7 @@ trait PeriodOverview
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
@@ -445,7 +455,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags.
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for cache
|
||||
@@ -459,7 +469,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// collect all expenses in this period:
|
||||
@@ -495,7 +505,7 @@ trait PeriodOverview
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'transactions' => 0,
|
||||
@@ -540,7 +550,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
$types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
@@ -555,7 +565,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
@@ -567,7 +577,7 @@ trait PeriodOverview
|
||||
$loops = 0;
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
|
||||
if ($loops < 10) {
|
||||
// set to correct array
|
||||
@@ -582,14 +592,14 @@ trait PeriodOverview
|
||||
}
|
||||
}
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
++$loops;
|
||||
}
|
||||
|
||||
@@ -605,7 +615,7 @@ trait PeriodOverview
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if ($account->id === (int) $journal['source_account_id']) {
|
||||
if ($account->id === (int)$journal['source_account_id']) {
|
||||
$return[] = $journal;
|
||||
}
|
||||
}
|
||||
@@ -622,7 +632,7 @@ trait PeriodOverview
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if ($account->id === (int) $journal['destination_account_id']) {
|
||||
if ($account->id === (int)$journal['destination_account_id']) {
|
||||
$return[] = $journal;
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Location;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
@@ -41,6 +42,7 @@ use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Override;
|
||||
|
||||
@@ -51,36 +53,30 @@ use Override;
|
||||
*/
|
||||
class AccountEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private array $accountIds;
|
||||
private array $accountTypeIds;
|
||||
private array $accountTypes;
|
||||
private array $ids = [];
|
||||
private array $accountTypeIds = [];
|
||||
private array $accountTypes = [];
|
||||
private Collection $collection;
|
||||
private array $currencies;
|
||||
private array $locations;
|
||||
private array $meta;
|
||||
private array $currencies = [];
|
||||
private array $locations = [];
|
||||
private array $meta = [];
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private array $notes;
|
||||
private array $openingBalances;
|
||||
private array $notes = [];
|
||||
private array $openingBalances = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private array $lastActivities;
|
||||
private ?Carbon $date = null;
|
||||
private bool $convertToPrimary = false;
|
||||
private array $lastActivities = [];
|
||||
private ?Carbon $date = null;
|
||||
private bool $convertToPrimary;
|
||||
private array $balances = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
|
||||
/**
|
||||
* TODO The account enricher must do conversion from and to the primary currency.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->accountIds = [];
|
||||
$this->openingBalances = [];
|
||||
$this->currencies = [];
|
||||
$this->accountTypeIds = [];
|
||||
$this->accountTypes = [];
|
||||
$this->meta = [];
|
||||
$this->notes = [];
|
||||
$this->lastActivities = [];
|
||||
$this->locations = [];
|
||||
$this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
$this->convertToPrimary = Amount::convertToPrimary();
|
||||
}
|
||||
@@ -105,27 +101,28 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
// prep local fields
|
||||
$this->collection = $collection;
|
||||
$this->collectAccountIds();
|
||||
$this->collectIds();
|
||||
$this->getAccountTypes();
|
||||
$this->collectMetaData();
|
||||
$this->collectNotes();
|
||||
$this->collectLastActivities();
|
||||
$this->collectLocations();
|
||||
$this->collectOpeningBalances();
|
||||
$this->collectObjectGroups();
|
||||
$this->collectBalances();
|
||||
$this->appendCollectedData();
|
||||
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
private function collectAccountIds(): void
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($this->collection as $account) {
|
||||
$this->accountIds[] = (int) $account->id;
|
||||
$this->accountTypeIds[] = (int) $account->account_type_id;
|
||||
$this->ids[] = (int)$account->id;
|
||||
$this->accountTypeIds[] = (int)$account->account_type_id;
|
||||
}
|
||||
$this->accountIds = array_unique($this->accountIds);
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->accountTypeIds = array_unique($this->accountTypeIds);
|
||||
}
|
||||
|
||||
@@ -135,27 +132,29 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
/** @var AccountType $type */
|
||||
foreach ($types as $type) {
|
||||
$this->accountTypes[(int) $type->id] = $type->type;
|
||||
$this->accountTypes[(int)$type->id] = $type->type;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectMetaData(): void
|
||||
{
|
||||
$set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
|
||||
->whereIn('account_id', $this->accountIds)
|
||||
->whereIn('account_id', $this->ids)
|
||||
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray()
|
||||
;
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($set as $entry) {
|
||||
$this->meta[(int) $entry['account_id']][$entry['name']] = (string) $entry['data'];
|
||||
$this->meta[(int)$entry['account_id']][$entry['name']] = (string)$entry['data'];
|
||||
if ('currency_id' === $entry['name']) {
|
||||
$this->currencies[(int) $entry['data']] = true;
|
||||
$this->currencies[(int)$entry['data']] = true;
|
||||
}
|
||||
}
|
||||
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int) $currency->id] = $currency;
|
||||
if (count($this->currencies) > 0) {
|
||||
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
$this->currencies[0] = $this->primaryCurrency;
|
||||
foreach ($this->currencies as $id => $currency) {
|
||||
@@ -167,28 +166,28 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->accountIds)
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
private function collectLocations(): void
|
||||
{
|
||||
$locations = Location::query()->whereIn('locatable_id', $this->accountIds)
|
||||
$locations = Location::query()->whereIn('locatable_id', $this->ids)
|
||||
->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray()
|
||||
;
|
||||
foreach ($locations as $location) {
|
||||
$this->locations[(int) $location['locatable_id']]
|
||||
$this->locations[(int)$location['locatable_id']]
|
||||
= [
|
||||
'latitude' => (float) $location['latitude'],
|
||||
'longitude' => (float) $location['longitude'],
|
||||
'zoom_level' => (int) $location['zoom_level'],
|
||||
'latitude' => (float)$location['latitude'],
|
||||
'longitude' => (float)$location['longitude'],
|
||||
'zoom_level' => (int)$location['zoom_level'],
|
||||
];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
|
||||
@@ -208,12 +207,12 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
;
|
||||
$journals = $collector->getExtractedJournals();
|
||||
foreach ($journals as $journal) {
|
||||
$this->openingBalances[(int) $journal['source_account_id']]
|
||||
$this->openingBalances[(int)$journal['source_account_id']]
|
||||
= [
|
||||
'amount' => Steam::negative($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
$this->openingBalances[(int) $journal['destination_account_id']]
|
||||
$this->openingBalances[(int)$journal['destination_account_id']]
|
||||
= [
|
||||
'amount' => Steam::positive($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
@@ -234,59 +233,72 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$notes = $this->notes;
|
||||
$openingBalances = $this->openingBalances;
|
||||
$locations = $this->locations;
|
||||
$lastActivities = $this->lastActivities;
|
||||
$this->collection = $this->collection->map(function (Account $item) use ($notes, $openingBalances, $locations, $lastActivities) {
|
||||
$item->full_account_type = $this->accountTypes[(int) $item->account_type_id] ?? null;
|
||||
$accountMeta = [
|
||||
'currency' => null,
|
||||
'location' => [
|
||||
$this->collection = $this->collection->map(function (Account $item) {
|
||||
$id = (int)$item->id;
|
||||
$item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null;
|
||||
$meta = [
|
||||
'currency' => null,
|
||||
'location' => [
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'zoom_level' => null,
|
||||
],
|
||||
'opening_balance_date' => null,
|
||||
'object_group_id' => null,
|
||||
'object_group_order' => null,
|
||||
'object_group_title' => null,
|
||||
'opening_balance_date' => null,
|
||||
'opening_balance_amount' => null,
|
||||
'account_number' => null,
|
||||
'notes' => $notes[$id] ?? null,
|
||||
'last_activity' => $this->lastActivities[$id] ?? null,
|
||||
];
|
||||
if (array_key_exists((int) $item->id, $this->meta)) {
|
||||
foreach ($this->meta[(int) $item->id] as $name => $value) {
|
||||
$accountMeta[$name] = $value;
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
|
||||
// if location, add location:
|
||||
if (array_key_exists($id, $this->locations)) {
|
||||
$meta['location'] = $this->locations[$id];
|
||||
}
|
||||
if (array_key_exists($id, $this->meta)) {
|
||||
foreach ($this->meta[$id] as $name => $value) {
|
||||
$meta[$name] = $value;
|
||||
}
|
||||
}
|
||||
// also add currency, if present.
|
||||
if (array_key_exists('currency_id', $accountMeta)) {
|
||||
$currencyId = (int) $accountMeta['currency_id'];
|
||||
$accountMeta['currency'] = $this->currencies[$currencyId];
|
||||
if (array_key_exists('currency_id', $meta)) {
|
||||
$currencyId = (int)$meta['currency_id'];
|
||||
$meta['currency'] = $this->currencies[$currencyId];
|
||||
}
|
||||
|
||||
// if notes, add notes.
|
||||
if (array_key_exists($item->id, $notes)) {
|
||||
$accountMeta['notes'] = $notes[$item->id];
|
||||
}
|
||||
// if opening balance, add opening balance
|
||||
if (array_key_exists($item->id, $openingBalances)) {
|
||||
$accountMeta['opening_balance_date'] = $openingBalances[$item->id]['date'];
|
||||
$accountMeta['opening_balance_amount'] = $openingBalances[$item->id]['amount'];
|
||||
if (array_key_exists($id, $this->openingBalances)) {
|
||||
$meta['opening_balance_date'] = $this->openingBalances[$id]['date'];
|
||||
$meta['opening_balance_amount'] = $this->openingBalances[$id]['amount'];
|
||||
}
|
||||
|
||||
// add balances
|
||||
// get currencies:
|
||||
$currency = $this->primaryCurrency; // assume primary currency
|
||||
if (null !== $accountMeta['currency']) {
|
||||
$currency = $accountMeta['currency'];
|
||||
if (null !== $meta['currency']) {
|
||||
$currency = $meta['currency'];
|
||||
}
|
||||
|
||||
// get the current balance:
|
||||
$date = $this->getDate();
|
||||
$finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
|
||||
// $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$finalBalance = $this->balances[$id];
|
||||
Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance);
|
||||
|
||||
// collect current balances:
|
||||
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
|
||||
$openingBalance = Steam::bcround($accountMeta['opening_balance_amount'] ?? '0', $currency->decimal_places);
|
||||
$openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places);
|
||||
$virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places);
|
||||
$debtAmount = $accountMeta['current_debt'] ?? null;
|
||||
$debtAmount = $meta['current_debt'] ?? null;
|
||||
|
||||
// set some pc_ default values to NULL:
|
||||
$pcCurrentBalance = null;
|
||||
@@ -311,11 +323,11 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
}
|
||||
|
||||
// set opening balance(s) to NULL if the date is null
|
||||
if (null === $accountMeta['opening_balance_date']) {
|
||||
if (null === $meta['opening_balance_date']) {
|
||||
$openingBalance = null;
|
||||
$pcOpeningBalance = null;
|
||||
}
|
||||
$accountMeta['balances'] = [
|
||||
$meta['balances'] = [
|
||||
'current_balance' => $currentBalance,
|
||||
'pc_current_balance' => $pcCurrentBalance,
|
||||
'opening_balance' => $openingBalance,
|
||||
@@ -326,16 +338,7 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
'pc_debt_amount' => $pcDebtAmount,
|
||||
];
|
||||
// end add balances
|
||||
|
||||
|
||||
// if location, add location:
|
||||
if (array_key_exists($item->id, $locations)) {
|
||||
$accountMeta['location'] = $locations[$item->id];
|
||||
}
|
||||
if (array_key_exists($item->id, $lastActivities)) {
|
||||
$accountMeta['last_activity'] = $lastActivities[$item->id];
|
||||
}
|
||||
$item->meta = $accountMeta;
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
@@ -343,10 +346,35 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
private function collectLastActivities(): void
|
||||
{
|
||||
$this->lastActivities = Steam::getLastActivities($this->accountIds);
|
||||
$this->lastActivities = Steam::getLastActivities($this->ids);
|
||||
}
|
||||
|
||||
private function collectBalances(): void {}
|
||||
private function collectBalances(): void
|
||||
{
|
||||
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
->whereIn('object_groupable_id', $this->ids)
|
||||
->where('object_groupable_type', Account::class)
|
||||
->get(['object_groupable_id', 'object_group_id'])
|
||||
;
|
||||
|
||||
$ids = array_unique($set->pluck('object_group_id')->toArray());
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
|
||||
}
|
||||
|
||||
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
|
||||
foreach ($groups as $group) {
|
||||
$group['id'] = (int)$group['id'];
|
||||
$group['order'] = (int)$group['order'];
|
||||
$this->objectGroups[(int)$group['id']] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
public function setDate(?Carbon $date): void
|
||||
{
|
||||
|
@@ -43,8 +43,10 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private bool $convertToPrimary = false;
|
||||
private bool $convertToPrimary;
|
||||
private array $ids = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private Collection $collection;
|
||||
private array $spentInBudgets = [];
|
||||
private array $spentOutsideBudgets = [];
|
||||
@@ -72,6 +74,7 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->collectIds();
|
||||
$this->collectCurrencies();
|
||||
$this->collectSpentInfo();
|
||||
$this->appendCollectedData();
|
||||
|
||||
@@ -108,7 +111,8 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
/** @var AvailableBudget $availableBudget */
|
||||
foreach ($this->collection as $availableBudget) {
|
||||
$this->ids[] = (int) $availableBudget->id;
|
||||
$this->ids[] = (int)$availableBudget->id;
|
||||
$this->currencyIds[(int)$availableBudget->id] = (int)$availableBudget->transaction_currency_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
}
|
||||
@@ -121,15 +125,17 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
$spentInBudgets = $this->opsRepository->collectExpenses($start, $end, null, $allActive, null);
|
||||
$spentOutsideBudgets = $this->noBudgetRepository->collectExpenses($start, $end, null, null, null);
|
||||
foreach ($this->collection as $availableBudget) {
|
||||
$id = (int) $availableBudget->id;
|
||||
$filteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, false);
|
||||
$filteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, false);
|
||||
$id = (int)$availableBudget->id;
|
||||
$currencyId = $this->currencyIds[$id];
|
||||
$currency = $this->currencies[$currencyId];
|
||||
$filteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, false);
|
||||
$filteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, false);
|
||||
$this->spentInBudgets[$id] = array_values($filteredSpentInBudgets);
|
||||
$this->spentOutsideBudgets[$id] = array_values($filteredSpentOutsideBudgets);
|
||||
|
||||
if (true === $this->convertToPrimary) {
|
||||
$pcFilteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, true);
|
||||
$pcFilteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, true);
|
||||
$pcFilteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, true);
|
||||
$pcFilteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, true);
|
||||
$this->pcSpentInBudgets[$id] = array_values($pcFilteredSpentInBudgets);
|
||||
$this->pcSpentOutsideBudgets[$id] = array_values($pcFilteredSpentOutsideBudgets);
|
||||
}
|
||||
@@ -145,22 +151,29 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$spentInsideBudgets = $this->spentInBudgets;
|
||||
$spentOutsideBudgets = $this->spentOutsideBudgets;
|
||||
$pcSpentInBudgets = $this->pcSpentInBudgets;
|
||||
$pcSpentOutsideBudgets = $this->pcSpentOutsideBudgets;
|
||||
$this->collection = $this->collection->map(function (AvailableBudget $item) use ($spentInsideBudgets, $spentOutsideBudgets, $pcSpentInBudgets, $pcSpentOutsideBudgets) {
|
||||
$id = (int) $item->id;
|
||||
$this->collection = $this->collection->map(function (AvailableBudget $item) {
|
||||
$id = (int)$item->id;
|
||||
$currencyId = $this->currencyIds[$id];
|
||||
$currency = $this->currencies[$currencyId];
|
||||
$meta = [
|
||||
'spent_in_budgets' => $spentInsideBudgets[$id] ?? [],
|
||||
'pc_spent_in_budgets' => $pcSpentInBudgets[$id] ?? [],
|
||||
|
||||
'spent_outside_budgets' => $spentOutsideBudgets[$id] ?? [],
|
||||
'pc_spent_outside_budgets' => $pcSpentOutsideBudgets[$id] ?? [],
|
||||
'currency' => $currency,
|
||||
'spent_in_budgets' => $this->spentInBudgets[$id] ?? [],
|
||||
'pc_spent_in_budgets' => $this->pcSpentInBudgets[$id] ?? [],
|
||||
'spent_outside_budgets' => $this->spentOutsideBudgets[$id] ?? [],
|
||||
'pc_spent_outside_budgets' => $this->pcSpentOutsideBudgets[$id] ?? [],
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
private function collectCurrencies(): void
|
||||
{
|
||||
$ids = array_unique(array_values($this->currencyIds));
|
||||
$set = TransactionCurrency::whereIn('id', $ids)->get();
|
||||
foreach ($set as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Models\AutoBudget;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
@@ -15,23 +16,26 @@ use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private bool $convertToPrimary = true;
|
||||
private bool $convertToPrimary;
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private array $ids = [];
|
||||
private array $notes = [];
|
||||
private array $autoBudgets = [];
|
||||
private array $currencies = [];
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $spent = [];
|
||||
private array $pcSpent = [];
|
||||
private array $ids = [];
|
||||
private array $notes = [];
|
||||
private array $autoBudgets = [];
|
||||
private array $currencies = [];
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $spent = [];
|
||||
private array $pcSpent = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -46,6 +50,8 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
$this->collectNotes();
|
||||
$this->collectAutoBudgets();
|
||||
$this->collectExpenses();
|
||||
$this->collectObjectGroups();
|
||||
|
||||
$this->appendCollectedData();
|
||||
|
||||
return $this->collection;
|
||||
@@ -98,12 +104,25 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
$this->collection = $this->collection->map(function (Budget $item) {
|
||||
$id = (int)$item->id;
|
||||
$meta = [
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'currency' => $this->currencies[$id] ?? null,
|
||||
'auto_budget' => $this->autoBudgets[$id] ?? null,
|
||||
'spent' => $this->spent[$id] ?? null,
|
||||
'pc_spent' => $this->pcSpent[$id] ?? null,
|
||||
'object_group_id' => null,
|
||||
'object_group_order' => null,
|
||||
'object_group_title' => null,
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'currency' => $this->currencies[$id] ?? null,
|
||||
'auto_budget' => $this->autoBudgets[$id] ?? null,
|
||||
'spent' => $this->spent[$id] ?? null,
|
||||
'pc_spent' => $this->pcSpent[$id] ?? null,
|
||||
];
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
|
||||
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
@@ -154,4 +173,26 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
->whereIn('object_groupable_id', $this->ids)
|
||||
->where('object_groupable_type', Budget::class)
|
||||
->get(['object_groupable_id', 'object_group_id'])
|
||||
;
|
||||
|
||||
$ids = array_unique($set->pluck('object_group_id')->toArray());
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
|
||||
}
|
||||
|
||||
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
|
||||
foreach ($groups as $group) {
|
||||
$group['id'] = (int)$group['id'];
|
||||
$group['order'] = (int)$group['order'];
|
||||
$this->objectGroups[(int)$group['id']] = $group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,8 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
private Collection $budgets;
|
||||
private array $expenses = [];
|
||||
private array $pcExpenses = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private bool $convertToPrimary = true;
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
|
||||
@@ -42,6 +44,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->collectIds();
|
||||
$this->collectCurrencies();
|
||||
$this->collectNotes();
|
||||
$this->collectBudgets();
|
||||
$this->appendCollectedData();
|
||||
@@ -71,14 +74,19 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
$this->start = $this->collection->min('start_date');
|
||||
$this->end = $this->collection->max('end_date');
|
||||
$this->start = $this->collection->min('start_date');
|
||||
$this->end = $this->collection->max('end_date');
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($this->collection as $limit) {
|
||||
$this->ids[] = (int)$limit->id;
|
||||
$id = (int)$limit->id;
|
||||
$this->ids[] = $id;
|
||||
if (0 !== (int)$limit->transaction_currency_id) {
|
||||
$this->currencyIds[$id] = (int)$limit->transaction_currency_id;
|
||||
}
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->currencyIds = array_unique($this->currencyIds);
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
@@ -98,10 +106,15 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->collection = $this->collection->map(function (BudgetLimit $item) {
|
||||
$id = (int)$item->id;
|
||||
$currencyId = (int)$item->transaction_currency_id;
|
||||
if (0 === $currencyId) {
|
||||
$currencyId = $this->primaryCurrency->id;
|
||||
}
|
||||
$meta = [
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'spent' => $this->expenses[$id] ?? [],
|
||||
'pc_spent' => $this->pcExpenses[$id] ?? [],
|
||||
'currency' => $this->currencies[$currencyId],
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
@@ -133,4 +146,13 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCurrencies(): void
|
||||
{
|
||||
$this->currencies[$this->primaryCurrency->id] = $this->primaryCurrency;
|
||||
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot('id', $this->primaryCurrency->id)->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
273
app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php
Normal file
273
app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi\Enrichments;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PiggyBankEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private Collection $collection;
|
||||
private array $ids = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private array $accountIds = [];
|
||||
// private array $accountCurrencies = [];
|
||||
private array $notes = [];
|
||||
private array $mappedObjects = [];
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private array $amounts = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
}
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->collectIds();
|
||||
$this->collectObjectGroups();
|
||||
$this->collectNotes();
|
||||
$this->collectCurrentAmounts();
|
||||
|
||||
|
||||
$this->appendCollectedData();
|
||||
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
public function enrichSingle(array|Model $model): array|Model
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection([$model]);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->setUserGroup($user->userGroup);
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var PiggyBank $piggy */
|
||||
foreach ($this->collection as $piggy) {
|
||||
$id = (int)$piggy->id;
|
||||
$this->ids[] = $id;
|
||||
$this->currencyIds[$id] = (int)$piggy->transaction_currency_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
|
||||
// collect currencies.
|
||||
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
|
||||
// collect accounts
|
||||
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->ids)->get(['piggy_bank_id', 'account_id', 'current_amount', 'native_current_amount']);
|
||||
foreach ($set as $item) {
|
||||
$id = (int)$item->piggy_bank_id;
|
||||
$accountId = (int)$item->account_id;
|
||||
$this->amounts[$id] ??= [];
|
||||
if (!array_key_exists($id, $this->accountIds)) {
|
||||
$this->accountIds[$id] = (int)$item->account_id;
|
||||
}
|
||||
if (!array_key_exists($accountId, $this->amounts[$id])) {
|
||||
$this->amounts[$id][$accountId] = [
|
||||
'current_amount' => '0',
|
||||
'pc_current_amount' => '0',
|
||||
];
|
||||
}
|
||||
$this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], $item->current_amount);
|
||||
if (null !== $this->amounts[$id][$accountId]['pc_current_amount'] && null !== $item->native_current_amount) {
|
||||
$this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], $item->native_current_amount);
|
||||
}
|
||||
}
|
||||
|
||||
// get account currency preference for ALL.
|
||||
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
|
||||
|
||||
/** @var AccountMeta $item */
|
||||
foreach ($set as $item) {
|
||||
$accountId = (int)$item->account_id;
|
||||
$currencyId = (int)$item->data;
|
||||
if (!array_key_exists($currencyId, $this->currencies)) {
|
||||
$this->currencies[$currencyId] = TransactionCurrency::find($currencyId);
|
||||
}
|
||||
// $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
|
||||
}
|
||||
|
||||
// get account info.
|
||||
$set = Account::whereIn('id', array_values($this->accountIds))->get();
|
||||
|
||||
/** @var Account $item */
|
||||
foreach ($set as $item) {
|
||||
$id = (int)$item->id;
|
||||
$this->accounts[$id] = [
|
||||
'id' => $id,
|
||||
'name' => $item->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (PiggyBank $item) {
|
||||
$id = (int)$item->id;
|
||||
$currencyId = (int)$item->transaction_currency_id;
|
||||
$currency = $this->currencies[$currencyId] ?? $this->primaryCurrency;
|
||||
$targetAmount = null;
|
||||
if (0 !== bccomp($item->target_amount, '0')) {
|
||||
$targetAmount = $item->target_amount;
|
||||
}
|
||||
$meta = [
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'currency' => $this->currencies[$currencyId] ?? null,
|
||||
// 'auto_budget' => $this->autoBudgets[$id] ?? null,
|
||||
// 'spent' => $this->spent[$id] ?? null,
|
||||
// 'pc_spent' => $this->pcSpent[$id] ?? null,
|
||||
'object_group_id' => null,
|
||||
'object_group_order' => null,
|
||||
'object_group_title' => null,
|
||||
'current_amount' => '0',
|
||||
'pc_current_amount' => '0',
|
||||
'target_amount' => null === $targetAmount ? null : Steam::bcround($targetAmount, $currency->decimal_places),
|
||||
'pc_target_amount' => null === $item->native_target_amount ? null : Steam::bcround($item->native_target_amount, $this->primaryCurrency->decimal_places),
|
||||
'left_to_save' => null,
|
||||
'pc_left_to_save' => null,
|
||||
'save_per_month' => null,
|
||||
'pc_save_per_month' => null,
|
||||
'accounts' => [],
|
||||
];
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
// add current amount(s).
|
||||
foreach ($this->amounts[$id] as $accountId => $row) {
|
||||
$meta['accounts'][] = [
|
||||
'account_id' => (string)$accountId,
|
||||
'name' => $this->accounts[$accountId]['name'] ?? '',
|
||||
'current_amount' => Steam::bcround($row['current_amount'], $currency->decimal_places),
|
||||
'pc_current_amount' => Steam::bcround($row['pc_current_amount'], $this->primaryCurrency->decimal_places),
|
||||
];
|
||||
$meta['current_amount'] = bcadd($meta['current_amount'], $row['current_amount']);
|
||||
// only add pc_current_amount when the pc_current_amount is set
|
||||
$meta['pc_current_amount'] = null === $row['pc_current_amount'] ? null : bcadd($meta['pc_current_amount'], $row['pc_current_amount']);
|
||||
}
|
||||
$meta['current_amount'] = Steam::bcround($meta['current_amount'], $currency->decimal_places);
|
||||
// only round this number when pc_current_amount is set.
|
||||
$meta['pc_current_amount'] = null === $meta['pc_current_amount'] ? null : Steam::bcround($meta['pc_current_amount'], $this->primaryCurrency->decimal_places);
|
||||
|
||||
// calculate left to save, only when there is a target amount.
|
||||
if (null !== $targetAmount) {
|
||||
$meta['left_to_save'] = bcsub($meta['target_amount'], $meta['current_amount']);
|
||||
$meta['pc_left_to_save'] = null === $meta['pc_target_amount'] ? null : bcsub($meta['pc_target_amount'], $meta['pc_current_amount']);
|
||||
}
|
||||
|
||||
// get suggested per month.
|
||||
$meta['save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['target_amount'], $meta['current_amount']), $currency->decimal_places);
|
||||
$meta['pc_save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['pc_target_amount'], $meta['pc_current_amount']), $currency->decimal_places);
|
||||
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', PiggyBank::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
->whereIn('object_groupable_id', $this->ids)
|
||||
->where('object_groupable_type', PiggyBank::class)
|
||||
->get(['object_groupable_id', 'object_group_id'])
|
||||
;
|
||||
|
||||
$ids = array_unique($set->pluck('object_group_id')->toArray());
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
|
||||
}
|
||||
|
||||
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
|
||||
foreach ($groups as $group) {
|
||||
$group['id'] = (int)$group['id'];
|
||||
$group['order'] = (int)$group['order'];
|
||||
$this->objectGroups[(int)$group['id']] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCurrentAmounts(): void {}
|
||||
|
||||
/**
|
||||
* Returns the suggested amount the user should save per month, or "".
|
||||
*/
|
||||
private function getSuggestedMonthlyAmount(?Carbon $startDate, ?Carbon $targetDate, ?string $targetAmount, string $currentAmount): string
|
||||
{
|
||||
if (null === $targetAmount || null === $targetDate || null === $startDate) {
|
||||
return '0';
|
||||
}
|
||||
$savePerMonth = '0';
|
||||
if (1 === bccomp($targetAmount, $currentAmount)) {
|
||||
$now = today(config('app.timezone'));
|
||||
$diffInMonths = (int)$startDate->diffInMonths($targetDate);
|
||||
$remainingAmount = bcsub($targetAmount, $currentAmount);
|
||||
|
||||
// more than 1 month to go and still need money to save:
|
||||
if ($diffInMonths > 0 && 1 === bccomp($remainingAmount, '0')) {
|
||||
$savePerMonth = bcdiv($remainingAmount, (string)$diffInMonths);
|
||||
}
|
||||
|
||||
// less than 1 month to go but still need money to save:
|
||||
if (0 === $diffInMonths && 1 === bccomp($remainingAmount, '0')) {
|
||||
$savePerMonth = $remainingAmount;
|
||||
}
|
||||
}
|
||||
|
||||
return $savePerMonth;
|
||||
}
|
||||
}
|
132
app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php
Normal file
132
app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi\Enrichments;
|
||||
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private Collection $collection;
|
||||
private array $ids = [];
|
||||
private array $journalIds = [];
|
||||
private array $groupIds = [];
|
||||
private array $accountIds = [];
|
||||
private array $piggyBankIds = [];
|
||||
private array $accountCurrencies = [];
|
||||
private array $currencies = [];
|
||||
// private bool $convertToPrimary = false;
|
||||
// private TransactionCurrency $primaryCurrency;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// $this->convertToPrimary = Amount::convertToPrimary();
|
||||
// $this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
}
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->collectIds();
|
||||
$this->appendCollectedData();
|
||||
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
public function enrichSingle(array|Model $model): array|Model
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection([$model]);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->setUserGroup($user->userGroup);
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var PiggyBankEvent $event */
|
||||
foreach ($this->collection as $event) {
|
||||
$this->ids[] = (int)$event->id;
|
||||
$this->journalIds[(int)$event->id] = (int)$event->transaction_journal_id;
|
||||
$this->piggyBankIds[(int)$event->id] = (int)$event->piggy_bank_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
// collect groups with journal info.
|
||||
$set = TransactionJournal::whereIn('id', $this->journalIds)->get(['id', 'transaction_group_id']);
|
||||
|
||||
/** @var TransactionJournal $item */
|
||||
foreach ($set as $item) {
|
||||
$this->groupIds[(int)$item->id] = (int)$item->transaction_group_id;
|
||||
}
|
||||
|
||||
// collect account info.
|
||||
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggyBankIds)->get(['piggy_bank_id', 'account_id']);
|
||||
foreach ($set as $item) {
|
||||
$id = (int)$item->piggy_bank_id;
|
||||
if (!array_key_exists($id, $this->accountIds)) {
|
||||
$this->accountIds[$id] = (int)$item->account_id;
|
||||
}
|
||||
}
|
||||
|
||||
// get account currency preference for ALL.
|
||||
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
|
||||
|
||||
/** @var AccountMeta $item */
|
||||
foreach ($set as $item) {
|
||||
$accountId = (int)$item->account_id;
|
||||
$currencyId = (int)$item->data;
|
||||
if (!array_key_exists($currencyId, $this->currencies)) {
|
||||
$this->currencies[$currencyId] = TransactionCurrency::find($currencyId);
|
||||
}
|
||||
$this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
|
||||
}
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (PiggyBankEvent $item) {
|
||||
$id = (int)$item->id;
|
||||
$piggyId = (int)$item->piggy_bank_id;
|
||||
$journalId = (int)$item->transaction_journal_id;
|
||||
$currency = null;
|
||||
if (array_key_exists($piggyId, $this->accountIds)) {
|
||||
$accountId = $this->accountIds[$piggyId];
|
||||
if (array_key_exists($accountId, $this->accountCurrencies)) {
|
||||
$currency = $this->accountCurrencies[$accountId];
|
||||
}
|
||||
}
|
||||
$meta = [
|
||||
'transaction_group_id' => array_key_exists($journalId, $this->groupIds) ? (string)$this->groupIds[$journalId] : null,
|
||||
'currency' => $currency,
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
586
app/Support/JsonApi/Enrichments/RecurringEnrichment.php
Normal file
586
app/Support/JsonApi/Enrichments/RecurringEnrichment.php
Normal file
@@ -0,0 +1,586 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi\Enrichments;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\RecurrenceRepetitionWeekend;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\CategoryFactory;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Models\RecurrenceRepetition;
|
||||
use FireflyIII\Models\RecurrenceTransaction;
|
||||
use FireflyIII\Models\RecurrenceTransactionMeta;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class RecurringEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private array $ids = [];
|
||||
private array $transactionTypeIds = [];
|
||||
private array $transactionTypes = [];
|
||||
private array $notes = [];
|
||||
private array $repetitions = [];
|
||||
private array $transactions = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private string $language = 'en_US';
|
||||
private array $currencyIds = [];
|
||||
private array $foreignCurrencyIds = [];
|
||||
private array $sourceAccountIds = [];
|
||||
private array $destinationAccountIds = [];
|
||||
private array $accounts = [];
|
||||
private array $currencies = [];
|
||||
private array $recurrenceIds = [];
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private bool $convertToPrimary = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
$this->convertToPrimary = Amount::convertToPrimary();
|
||||
}
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->collectIds();
|
||||
$this->collectRepetitions();
|
||||
$this->collectTransactions();
|
||||
$this->collectCurrencies();
|
||||
$this->collectNotes();
|
||||
$this->collectAccounts();
|
||||
$this->collectTransactionMetaData();
|
||||
|
||||
$this->appendCollectedData();
|
||||
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
public function enrichSingle(array|Model $model): array|Model
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection([$model]);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->setUserGroup($user->userGroup);
|
||||
$this->getLanguage();
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($this->collection as $recurrence) {
|
||||
$id = (int)$recurrence->id;
|
||||
$typeId = (int)$recurrence->transaction_type_id;
|
||||
$this->ids[] = $id;
|
||||
$this->transactionTypeIds[$id] = $typeId;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
|
||||
// collect transaction types.
|
||||
$transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get();
|
||||
foreach ($transactionTypes as $transactionType) {
|
||||
$id = (int)$transactionType->id;
|
||||
$this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectRepetitions(): void
|
||||
{
|
||||
Log::debug('Start of enrichment: collectRepetitions()');
|
||||
$repository = app(RecurringRepositoryInterface::class);
|
||||
$repository->setUserGroup($this->userGroup);
|
||||
$set = RecurrenceRepetition::whereIn('recurrence_id', $this->ids)->get();
|
||||
|
||||
/** @var RecurrenceRepetition $repetition */
|
||||
foreach ($set as $repetition) {
|
||||
$recurrence = $this->collection->filter(function (Recurrence $item) use ($repetition) {
|
||||
return (int)$item->id === (int)$repetition->recurrence_id;
|
||||
})->first();
|
||||
$fromDate = $recurrence->latest_date ?? $recurrence->first_date;
|
||||
$id = (int)$repetition->recurrence_id;
|
||||
$repId = (int)$repetition->id;
|
||||
$this->repetitions[$id] ??= [];
|
||||
|
||||
// get the (future) occurrences for this specific type of repetition:
|
||||
$amount = 'daily' === $repetition->repetition_type ? 9 : 5;
|
||||
$set = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount);
|
||||
|
||||
/** @var Carbon $carbon */
|
||||
foreach ($set as $carbon) {
|
||||
$occurrences[] = $carbon->toAtomString();
|
||||
}
|
||||
|
||||
$this->repetitions[$id][$repId] = [
|
||||
'id' => (string)$repId,
|
||||
'created_at' => $repetition->created_at->toAtomString(),
|
||||
'updated_at' => $repetition->updated_at->toAtomString(),
|
||||
'type' => $repetition->repetition_type,
|
||||
'moment' => (string)$repetition->moment,
|
||||
'skip' => (int)$repetition->skip,
|
||||
'weekend' => RecurrenceRepetitionWeekend::from((int)$repetition->weekend),
|
||||
'description' => $this->getRepetitionDescription($repetition),
|
||||
'occurrences' => $occurrences,
|
||||
];
|
||||
}
|
||||
Log::debug('End of enrichment: collectRepetitions()');
|
||||
}
|
||||
|
||||
private function collectTransactions(): void
|
||||
{
|
||||
$set = RecurrenceTransaction::whereIn('recurrence_id', $this->ids)->get();
|
||||
|
||||
/** @var RecurrenceTransaction $transaction */
|
||||
foreach ($set as $transaction) {
|
||||
$id = (int)$transaction->recurrence_id;
|
||||
$transactionId = (int)$transaction->id;
|
||||
$this->recurrenceIds[$transactionId] = $id;
|
||||
$this->transactions[$id] ??= [];
|
||||
$amount = $transaction->amount;
|
||||
$foreignAmount = $transaction->foreign_amount;
|
||||
|
||||
$this->transactions[$id][$transactionId] = [
|
||||
'id' => (string)$transactionId,
|
||||
// 'recurrence_id' => $id,
|
||||
'transaction_currency_id' => (int)$transaction->transaction_currency_id,
|
||||
'foreign_currency_id' => null === $transaction->foreign_currency_id ? null : (int)$transaction->foreign_currency_id,
|
||||
'source_id' => (int)$transaction->source_id,
|
||||
'object_has_currency_setting' => true,
|
||||
'destination_id' => (int)$transaction->destination_id,
|
||||
'amount' => $amount,
|
||||
'foreign_amount' => $foreignAmount,
|
||||
'pc_amount' => null,
|
||||
'pc_foreign_amount' => null,
|
||||
'description' => $transaction->description,
|
||||
'tags' => [],
|
||||
'category_id' => null,
|
||||
'category_name' => null,
|
||||
'budget_id' => null,
|
||||
'budget_name' => null,
|
||||
'piggy_bank_id' => null,
|
||||
'piggy_bank_name' => null,
|
||||
'subscription_id' => null,
|
||||
'subscription_name' => null,
|
||||
|
||||
];
|
||||
// collect all kinds of meta data to be collected later.
|
||||
$this->currencyIds[$transactionId] = (int)$transaction->transaction_currency_id;
|
||||
$this->sourceAccountIds[$transactionId] = (int)$transaction->source_id;
|
||||
$this->destinationAccountIds[$transactionId] = (int)$transaction->destination_id;
|
||||
if (null !== $transaction->foreign_currency_id) {
|
||||
$this->foreignCurrencyIds[$transactionId] = (int)$transaction->foreign_currency_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (Recurrence $item) {
|
||||
$id = (int)$item->id;
|
||||
$meta = [
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'repetitions' => array_values($this->repetitions[$id] ?? []),
|
||||
'transactions' => $this->processTransactions(array_values($this->transactions[$id] ?? [])),
|
||||
];
|
||||
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the repetition in a string that is user readable.
|
||||
* TODO duplicate with repository.
|
||||
*/
|
||||
public function getRepetitionDescription(RecurrenceRepetition $repetition): string
|
||||
{
|
||||
if ('daily' === $repetition->repetition_type) {
|
||||
return (string)trans('firefly.recurring_daily', [], $this->language);
|
||||
}
|
||||
if ('weekly' === $repetition->repetition_type) {
|
||||
$dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $this->language);
|
||||
if ($repetition->repetition_skip > 0) {
|
||||
return (string)trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $this->language);
|
||||
}
|
||||
|
||||
return (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $this->language);
|
||||
}
|
||||
if ('monthly' === $repetition->repetition_type) {
|
||||
if ($repetition->repetition_skip > 0) {
|
||||
return (string)trans('firefly.recurring_monthly_skip', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip + 1], $this->language);
|
||||
}
|
||||
|
||||
return (string)trans('firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1], $this->language);
|
||||
}
|
||||
if ('ndom' === $repetition->repetition_type) {
|
||||
$parts = explode(',', $repetition->repetition_moment);
|
||||
// first part is number of week, second is weekday.
|
||||
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $this->language);
|
||||
if ($repetition->repetition_skip > 0) {
|
||||
return (string)trans('firefly.recurring_ndom_skip', ['skip' => $repetition->repetition_skip, 'weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $this->language);
|
||||
}
|
||||
|
||||
return (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $this->language);
|
||||
}
|
||||
if ('yearly' === $repetition->repetition_type) {
|
||||
$today = today(config('app.timezone'))->endOfYear();
|
||||
$repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment);
|
||||
if (!$repDate instanceof Carbon) {
|
||||
$repDate = clone $today;
|
||||
}
|
||||
// $diffInYears = (int)$today->diffInYears($repDate, true);
|
||||
// $repDate->addYears($diffInYears); // technically not necessary.
|
||||
$string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
|
||||
|
||||
return (string)trans('firefly.recurring_yearly', ['date' => $string], $this->language);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getLanguage(): void
|
||||
{
|
||||
/** @var Preference $preference */
|
||||
$preference = Preferences::getForUser($this->user, 'language', config('firefly.default_language', 'en_US'));
|
||||
$language = $preference->data;
|
||||
if (is_array($language)) {
|
||||
$language = 'en_US';
|
||||
}
|
||||
$language = (string)$language;
|
||||
$this->language = $language;
|
||||
}
|
||||
|
||||
private function collectCurrencies(): void
|
||||
{
|
||||
$all = array_merge(array_unique($this->currencyIds), array_unique($this->foreignCurrencyIds));
|
||||
$currencies = TransactionCurrency::whereIn('id', array_unique($all))->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$id = (int)$currency->id;
|
||||
$this->currencies[$id] = $currency;
|
||||
}
|
||||
}
|
||||
|
||||
private function processTransactions(array $transactions): array
|
||||
{
|
||||
$return = [];
|
||||
$converter = new ExchangeRateConverter();
|
||||
foreach ($transactions as $transaction) {
|
||||
$currencyId = $transaction['transaction_currency_id'];
|
||||
$pcAmount = null;
|
||||
$pcForeignAmount = null;
|
||||
// set the same amount in the primary currency, if both are the same anyway.
|
||||
if (true === $this->convertToPrimary && $currencyId === (int)$this->primaryCurrency->id) {
|
||||
$pcAmount = $transaction['amount'];
|
||||
}
|
||||
// convert the amount to the primary currency, if it is not the same.
|
||||
if (true === $this->convertToPrimary && $currencyId !== (int)$this->primaryCurrency->id) {
|
||||
$pcAmount = $converter->convert($this->currencies[$currencyId], $this->primaryCurrency, today(), $transaction['amount']);
|
||||
}
|
||||
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
|
||||
$foreignCurrencyId = $transaction['foreign_currency_id'];
|
||||
if ($foreignCurrencyId !== $this->primaryCurrency->id) {
|
||||
$pcForeignAmount = $converter->convert($this->currencies[$foreignCurrencyId], $this->primaryCurrency, today(), $transaction['foreign_amount']);
|
||||
}
|
||||
}
|
||||
|
||||
$transaction['pc_amount'] = $pcAmount;
|
||||
$transaction['pc_foreign_amount'] = $pcForeignAmount;
|
||||
|
||||
$sourceId = $transaction['source_id'];
|
||||
$transaction['source_name'] = $this->accounts[$sourceId]->name;
|
||||
$transaction['source_iban'] = $this->accounts[$sourceId]->iban;
|
||||
$transaction['source_type'] = $this->accounts[$sourceId]->accountType->type;
|
||||
$transaction['source_id'] = (string)$transaction['source_id'];
|
||||
|
||||
$destId = $transaction['destination_id'];
|
||||
$transaction['destination_name'] = $this->accounts[$destId]->name;
|
||||
$transaction['destination_iban'] = $this->accounts[$destId]->iban;
|
||||
$transaction['destination_type'] = $this->accounts[$destId]->accountType->type;
|
||||
$transaction['destination_id'] = (string)$transaction['destination_id'];
|
||||
|
||||
$transaction['currency_id'] = (string)$currencyId;
|
||||
$transaction['currency_name'] = $this->currencies[$currencyId]->name;
|
||||
$transaction['currency_code'] = $this->currencies[$currencyId]->code;
|
||||
$transaction['currency_symbol'] = $this->currencies[$currencyId]->symbol;
|
||||
$transaction['currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
|
||||
|
||||
$transaction['primary_currency_id'] = (string)$this->primaryCurrency->id;
|
||||
$transaction['primary_currency_name'] = $this->primaryCurrency->name;
|
||||
$transaction['primary_currency_code'] = $this->primaryCurrency->code;
|
||||
$transaction['primary_currency_symbol'] = $this->primaryCurrency->symbol;
|
||||
$transaction['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
|
||||
|
||||
// $transaction['foreign_currency_id'] = null;
|
||||
$transaction['foreign_currency_name'] = null;
|
||||
$transaction['foreign_currency_code'] = null;
|
||||
$transaction['foreign_currency_symbol'] = null;
|
||||
$transaction['foreign_currency_decimal_places'] = null;
|
||||
if (null !== $transaction['foreign_currency_id']) {
|
||||
$currencyId = $transaction['foreign_currency_id'];
|
||||
$transaction['foreign_currency_id'] = (string)$currencyId;
|
||||
$transaction['foreign_currency_name'] = $this->currencies[$currencyId]->name;
|
||||
$transaction['foreign_currency_code'] = $this->currencies[$currencyId]->code;
|
||||
$transaction['foreign_currency_symbol'] = $this->currencies[$currencyId]->symbol;
|
||||
$transaction['foreign_currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
|
||||
}
|
||||
unset($transaction['transaction_currency_id']);
|
||||
$return[] = $transaction;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function collectAccounts(): void
|
||||
{
|
||||
$all = array_merge(array_unique($this->sourceAccountIds), array_unique($this->destinationAccountIds));
|
||||
$accounts = Account::with(['accountType'])->whereIn('id', array_unique($all))->get();
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$id = (int)$account->id;
|
||||
$this->accounts[$id] = $account;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectTransactionMetaData(): void
|
||||
{
|
||||
$ids = array_keys($this->transactions);
|
||||
$meta = RecurrenceTransactionMeta::whereIn('rt_id', $ids)->get();
|
||||
// other meta-data to be collected:
|
||||
$billIds = [];
|
||||
$piggyBankIds = [];
|
||||
$categoryIds = [];
|
||||
$categoryNames = [];
|
||||
$budgetIds = [];
|
||||
foreach ($meta as $entry) {
|
||||
$id = (int)$entry->id;
|
||||
$transactionId = (int)$entry->rt_id;
|
||||
$recurrenceId = $this->recurrenceIds[$transactionId];
|
||||
$name = (string)$entry->name;
|
||||
|
||||
switch ($name) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Recurrence transformer cant handle field "%s"', $name));
|
||||
|
||||
case 'bill_id':
|
||||
if ((int)$entry->value > 0) {
|
||||
$this->transactions[$recurrenceId][$transactionId]['subscription_id'] = $entry->value;
|
||||
if (!array_key_exists($id, $billIds)) {
|
||||
$billIds[$id] = [
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'transaction_id' => $transactionId,
|
||||
'bill_id' => (int)$entry->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'tags':
|
||||
$this->transactions[$recurrenceId][$transactionId]['tags'] = json_decode((string)$entry->value);
|
||||
|
||||
break;
|
||||
|
||||
case 'piggy_bank_id':
|
||||
if ((int)$entry->value > 0) {
|
||||
$this->transactions[$recurrenceId][$transactionId]['piggy_bank_id'] = (string)$entry->value;
|
||||
if (!array_key_exists($id, $piggyBankIds)) {
|
||||
$piggyBankIds[$id] = [
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'transaction_id' => $transactionId,
|
||||
'piggy_bank_id' => (int)$entry->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'category_id':
|
||||
if ((int)$entry->value > 0) {
|
||||
$this->transactions[$recurrenceId][$transactionId]['category_id'] = (string)$entry->value;
|
||||
if (!array_key_exists($id, $categoryIds)) {
|
||||
$categoryIds[$id] = [
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'transaction_id' => $transactionId,
|
||||
'category_id' => (int)$entry->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'category_name':
|
||||
if ('' !== (string)$entry->value) {
|
||||
$this->transactions[$recurrenceId][$transactionId]['category_name'] = (string)$entry->value;
|
||||
if (!array_key_exists($id, $categoryIds)) {
|
||||
$categoryNames[$id] = [
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'transaction_id' => $transactionId,
|
||||
'category_name' => $entry->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'budget_id':
|
||||
if ((int)$entry->value > 0) {
|
||||
$this->transactions[$recurrenceId][$transactionId]['budget_id'] = (string)$entry->value;
|
||||
if (!array_key_exists($id, $budgetIds)) {
|
||||
$budgetIds[$id] = [
|
||||
'recurrence_id' => $recurrenceId,
|
||||
'transaction_id' => $transactionId,
|
||||
'budget_id' => (int)$entry->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->collectBillInfo($billIds);
|
||||
$this->collectPiggyBankInfo($piggyBankIds);
|
||||
$this->collectCategoryIdInfo($categoryIds);
|
||||
$this->collectCategoryNameInfo($categoryNames);
|
||||
$this->collectBudgetInfo($budgetIds);
|
||||
}
|
||||
|
||||
private function collectBillInfo(array $billIds): void
|
||||
{
|
||||
if (0 === count($billIds)) {
|
||||
return;
|
||||
}
|
||||
$ids = Arr::pluck($billIds, 'bill_id');
|
||||
$bills = Bill::whereIn('id', $ids)->get();
|
||||
$mapped = [];
|
||||
foreach ($bills as $bill) {
|
||||
$mapped[(int)$bill->id] = $bill;
|
||||
}
|
||||
foreach ($billIds as $info) {
|
||||
$recurrenceId = $info['recurrence_id'];
|
||||
$transactionId = $info['transaction_id'];
|
||||
$this->transactions[$recurrenceId][$transactionId]['subscription_name'] = $mapped[$info['bill_id']]->name ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
private function collectPiggyBankInfo(array $piggyBankIds): void
|
||||
{
|
||||
if (0 === count($piggyBankIds)) {
|
||||
return;
|
||||
}
|
||||
$ids = Arr::pluck($piggyBankIds, 'piggy_bank_id');
|
||||
$piggyBanks = PiggyBank::whereIn('id', $ids)->get();
|
||||
$mapped = [];
|
||||
foreach ($piggyBanks as $piggyBank) {
|
||||
$mapped[(int)$piggyBank->id] = $piggyBank;
|
||||
}
|
||||
foreach ($piggyBankIds as $info) {
|
||||
$recurrenceId = $info['recurrence_id'];
|
||||
$transactionId = $info['transaction_id'];
|
||||
$this->transactions[$recurrenceId][$transactionId]['piggy_bank_name'] = $mapped[$info['piggy_bank_id']]->name ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCategoryIdInfo(array $categoryIds): void
|
||||
{
|
||||
if (0 === count($categoryIds)) {
|
||||
return;
|
||||
}
|
||||
$ids = Arr::pluck($categoryIds, 'category_id');
|
||||
$categories = Category::whereIn('id', $ids)->get();
|
||||
$mapped = [];
|
||||
foreach ($categories as $category) {
|
||||
$mapped[(int)$category->id] = $category;
|
||||
}
|
||||
foreach ($categoryIds as $info) {
|
||||
$recurrenceId = $info['recurrence_id'];
|
||||
$transactionId = $info['transaction_id'];
|
||||
$this->transactions[$recurrenceId][$transactionId]['category_name'] = $mapped[$info['category_id']]->name ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO This method does look-up in a loop.
|
||||
*/
|
||||
private function collectCategoryNameInfo(array $categoryNames): void
|
||||
{
|
||||
if (0 === count($categoryNames)) {
|
||||
return;
|
||||
}
|
||||
$factory = app(CategoryFactory::class);
|
||||
$factory->setUser($this->user);
|
||||
foreach ($categoryNames as $info) {
|
||||
$recurrenceId = $info['recurrence_id'];
|
||||
$transactionId = $info['transaction_id'];
|
||||
$category = $factory->findOrCreate(null, $info['category_name']);
|
||||
if (null !== $category) {
|
||||
$this->transactions[$recurrenceId][$transactionId]['category_id'] = (string)$category->id;
|
||||
$this->transactions[$recurrenceId][$transactionId]['category_name'] = $category->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function collectBudgetInfo(array $budgetIds): void
|
||||
{
|
||||
if (0 === count($budgetIds)) {
|
||||
return;
|
||||
}
|
||||
$ids = Arr::pluck($budgetIds, 'budget_id');
|
||||
$categories = Budget::whereIn('id', $ids)->get();
|
||||
$mapped = [];
|
||||
foreach ($categories as $category) {
|
||||
$mapped[(int)$category->id] = $category;
|
||||
}
|
||||
foreach ($budgetIds as $info) {
|
||||
$recurrenceId = $info['recurrence_id'];
|
||||
$transactionId = $info['transaction_id'];
|
||||
$this->transactions[$recurrenceId][$transactionId]['budget_name'] = $mapped[$info['budget_id']]->name ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Recurrence::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
}
|
@@ -27,15 +27,15 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private Collection $collection;
|
||||
private bool $convertToPrimary = false;
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $subscriptionIds = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $paidDates = [];
|
||||
private array $notes = [];
|
||||
private array $payDates = [];
|
||||
private bool $convertToPrimary;
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $subscriptionIds = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $paidDates = [];
|
||||
private array $notes = [];
|
||||
private array $payDates = [];
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private BillDateCalculator $calculator;
|
||||
|
||||
@@ -56,6 +56,9 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
$this->collectPaidDates();
|
||||
$this->collectPayDates();
|
||||
|
||||
|
||||
// TODO clean me up.
|
||||
|
||||
$notes = $this->notes;
|
||||
$objectGroups = $this->objectGroups;
|
||||
$paidDates = $this->paidDates;
|
||||
@@ -342,7 +345,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
|
||||
private function getLastPaidDate(array $paidData): ?Carbon
|
||||
{
|
||||
Log::debug('getLastPaidDate()');
|
||||
// Log::debug('getLastPaidDate()');
|
||||
$return = null;
|
||||
foreach ($paidData as $entry) {
|
||||
if (null !== $return) {
|
||||
@@ -351,15 +354,15 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
if ($current->gt($return)) {
|
||||
$return = clone $current;
|
||||
}
|
||||
Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
Log::debug(sprintf('[a] Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
if (null === $return) {
|
||||
/** @var Carbon $return */
|
||||
$return = $entry['date_object'];
|
||||
Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
Log::debug(sprintf('[b] Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
Log::debug(sprintf('[c] Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
@@ -45,26 +45,20 @@ use Override;
|
||||
|
||||
class TransactionGroupEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private array $attachmentCount;
|
||||
private array $attachmentCount = [];
|
||||
private Collection $collection;
|
||||
private readonly array $dateFields;
|
||||
private array $journalIds;
|
||||
private array $locations;
|
||||
private array $metaData; // @phpstan-ignore-line
|
||||
private array $notes; // @phpstan-ignore-line
|
||||
private array $tags;
|
||||
private array $journalIds = [];
|
||||
private array $locations = [];
|
||||
private array $metaData = [];
|
||||
private array $notes = [];
|
||||
private array $tags = [];
|
||||
private User $user;
|
||||
private readonly TransactionCurrency $primaryCurrency;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->notes = [];
|
||||
$this->journalIds = [];
|
||||
$this->tags = [];
|
||||
$this->metaData = [];
|
||||
$this->locations = [];
|
||||
$this->attachmentCount = [];
|
||||
$this->dateFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
|
||||
$this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
}
|
||||
|
@@ -110,8 +110,8 @@ class BillDateCalculator
|
||||
$currentStart = clone $nextExpectedMatch;
|
||||
|
||||
++$loop;
|
||||
if ($loop > 12) {
|
||||
Log::debug('Loop is more than 12, so we break.');
|
||||
if ($loop > 31) {
|
||||
Log::debug('Loop is more than 31, so we break.');
|
||||
|
||||
break;
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Support;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Support\Singleton\PreferencesSingleton;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Contracts\Encryption\EncryptException;
|
||||
@@ -283,6 +284,12 @@ class Preferences
|
||||
*/
|
||||
public function lastActivity(): string
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$pref = $instance->getPreference('last_activity');
|
||||
if (null !== $pref) {
|
||||
// Log::debug(sprintf('Found last activity in singleton: %s', $pref));
|
||||
return $pref;
|
||||
}
|
||||
$lastActivity = microtime();
|
||||
$preference = $this->get('lastActivity', microtime());
|
||||
|
||||
@@ -292,13 +299,17 @@ class Preferences
|
||||
if (is_array($lastActivity)) {
|
||||
$lastActivity = implode(',', $lastActivity);
|
||||
}
|
||||
$setting = hash('sha256', (string) $lastActivity);
|
||||
$instance->setPreference('last_activity', $setting);
|
||||
|
||||
return hash('sha256', (string) $lastActivity);
|
||||
return $setting;
|
||||
}
|
||||
|
||||
public function mark(): void
|
||||
{
|
||||
$this->set('lastActivity', microtime());
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$instance->setPreference('last_activity', microtime());
|
||||
Session::forget('first');
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Support\Repositories\Recurring;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class CalculateXOccurrencesSince
|
||||
@@ -37,7 +38,7 @@ trait CalculateXOccurrencesSince
|
||||
*/
|
||||
protected function getXDailyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$return = [];
|
||||
$mutator = clone $date;
|
||||
$total = 0;
|
||||
@@ -62,7 +63,7 @@ trait CalculateXOccurrencesSince
|
||||
*/
|
||||
protected function getXMonthlyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s(%s, %s, %d)', __METHOD__, $date->format('Y-m-d'), $afterDate->format('Y-m-d'), $count));
|
||||
Log::debug(sprintf('Now in %s(%s, %s, %d)', __METHOD__, $date->format('Y-m-d'), $afterDate->format('Y-m-d'), $count));
|
||||
$return = [];
|
||||
$mutator = clone $date;
|
||||
$total = 0;
|
||||
@@ -70,24 +71,25 @@ trait CalculateXOccurrencesSince
|
||||
$dayOfMonth = (int) $moment;
|
||||
$dayOfMonth = 0 === $dayOfMonth ? 1 : $dayOfMonth;
|
||||
if ($mutator->day > $dayOfMonth) {
|
||||
app('log')->debug(sprintf('%d is after %d, add a month. Mutator is now', $mutator->day, $dayOfMonth));
|
||||
Log::debug(sprintf('%d is after %d, add a month. Mutator is now...', $mutator->day, $dayOfMonth));
|
||||
// day has passed already, add a month.
|
||||
$mutator->addMonth();
|
||||
app('log')->debug(sprintf('%s', $mutator->format('Y-m-d')));
|
||||
Log::debug(sprintf('%s', $mutator->toAtomString()));
|
||||
}
|
||||
|
||||
while ($total < $count) {
|
||||
$domCorrected = min($dayOfMonth, $mutator->daysInMonth);
|
||||
$mutator->day = $domCorrected;
|
||||
app('log')->debug(sprintf('Mutator is now %s', $mutator->format('Y-m-d')));
|
||||
$mutator->setTime(0, 0, 0);
|
||||
if (0 === $attempts % $skipMod && $mutator->gte($afterDate)) {
|
||||
app('log')->debug('Is added to the list.');
|
||||
Log::debug(sprintf('Mutator is now %s and is added to the list.', $mutator->toAtomString()));
|
||||
$return[] = clone $mutator;
|
||||
++$total;
|
||||
}
|
||||
++$attempts;
|
||||
$mutator = $mutator->endOfMonth()->addDay();
|
||||
}
|
||||
Log::debug('Collected enough occurrences.');
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -100,7 +102,7 @@ trait CalculateXOccurrencesSince
|
||||
*/
|
||||
protected function getXNDomOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$return = [];
|
||||
$total = 0;
|
||||
$attempts = 0;
|
||||
@@ -134,7 +136,7 @@ trait CalculateXOccurrencesSince
|
||||
*/
|
||||
protected function getXWeeklyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$return = [];
|
||||
$total = 0;
|
||||
$attempts = 0;
|
||||
@@ -173,7 +175,7 @@ trait CalculateXOccurrencesSince
|
||||
*/
|
||||
protected function getXYearlyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s(%s, %d, %d, %s)', __METHOD__, $date->format('Y-m-d'), $date->format('Y-m-d'), $count, $skipMod));
|
||||
Log::debug(sprintf('Now in %s(%s, %d, %d, %s)', __METHOD__, $date->format('Y-m-d'), $date->format('Y-m-d'), $count, $skipMod));
|
||||
$return = [];
|
||||
$mutator = clone $date;
|
||||
$total = 0;
|
||||
@@ -181,19 +183,19 @@ trait CalculateXOccurrencesSince
|
||||
$date = new Carbon($moment);
|
||||
$date->year = $mutator->year;
|
||||
if ($mutator > $date) {
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf('mutator (%s) > date (%s), so add a year to date (%s)', $mutator->format('Y-m-d'), $date->format('Y-m-d'), $date->format('Y-m-d'))
|
||||
);
|
||||
$date->addYear();
|
||||
app('log')->debug(sprintf('Date is now %s', $date->format('Y-m-d')));
|
||||
Log::debug(sprintf('Date is now %s', $date->format('Y-m-d')));
|
||||
}
|
||||
$obj = clone $date;
|
||||
while ($total < $count) {
|
||||
app('log')->debug(sprintf('total (%d) < count (%d) so go.', $total, $count));
|
||||
app('log')->debug(sprintf('attempts (%d) %% skipmod (%d) === %d', $attempts, $skipMod, $attempts % $skipMod));
|
||||
app('log')->debug(sprintf('Obj (%s) gte afterdate (%s)? %s', $obj->format('Y-m-d'), $afterDate->format('Y-m-d'), var_export($obj->gte($afterDate), true)));
|
||||
Log::debug(sprintf('total (%d) < count (%d) so go.', $total, $count));
|
||||
Log::debug(sprintf('attempts (%d) %% skipmod (%d) === %d', $attempts, $skipMod, $attempts % $skipMod));
|
||||
Log::debug(sprintf('Obj (%s) gte afterdate (%s)? %s', $obj->format('Y-m-d'), $afterDate->format('Y-m-d'), var_export($obj->gte($afterDate), true)));
|
||||
if (0 === $attempts % $skipMod && $obj->gte($afterDate)) {
|
||||
app('log')->debug('All conditions true, add obj.');
|
||||
Log::debug('All conditions true, add obj.');
|
||||
$return[] = clone $obj;
|
||||
++$total;
|
||||
}
|
||||
|
41
app/Support/Singleton/PreferencesSingleton.php
Normal file
41
app/Support/Singleton/PreferencesSingleton.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Singleton;
|
||||
|
||||
class PreferencesSingleton
|
||||
{
|
||||
private static ?PreferencesSingleton $instance = null;
|
||||
|
||||
private array $preferences = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
// Private constructor to prevent direct instantiation.
|
||||
}
|
||||
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function resetPreferences(): void
|
||||
{
|
||||
$this->preferences = [];
|
||||
}
|
||||
|
||||
public function setPreference(string $key, mixed $value): void
|
||||
{
|
||||
$this->preferences[$key] = $value;
|
||||
}
|
||||
|
||||
public function getPreference(string $key): mixed
|
||||
{
|
||||
return $this->preferences[$key] ?? null;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user