mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-29 22:47:42 +00:00
Compare commits
218 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b330313c55 | ||
|
|
aa9ce73758 | ||
|
|
d97fda41b2 | ||
|
|
945ad79c03 | ||
|
|
6273807525 | ||
|
|
50803a2c24 | ||
|
|
a0e9b05680 | ||
|
|
41e74cd816 | ||
|
|
b379c8e36b | ||
|
|
76e893f86e | ||
|
|
5da1599959 | ||
|
|
1d997e7c86 | ||
|
|
35e2eba303 | ||
|
|
b50f8f8ecd | ||
|
|
c16d3be85f | ||
|
|
bd1232644f | ||
|
|
37ca460ff2 | ||
|
|
478acdc847 | ||
|
|
f9ec94ea97 | ||
|
|
576c5f242c | ||
|
|
dba8bba41a | ||
|
|
55a00aa6fe | ||
|
|
22133f64cf | ||
|
|
2efb2377b6 | ||
|
|
9f75a96ad6 | ||
|
|
d0be9bb957 | ||
|
|
c25adf0a56 | ||
|
|
0ca79fd843 | ||
|
|
efc516eb3b | ||
|
|
dca3ac9250 | ||
|
|
33668a3688 | ||
|
|
5414a70abb | ||
|
|
85aee63d1e | ||
|
|
ad09c851f6 | ||
|
|
c57f36820b | ||
|
|
6bb2702e07 | ||
|
|
b1dbd3ee17 | ||
|
|
2d41db349a | ||
|
|
6b73b9327a | ||
|
|
e3ea54329d | ||
|
|
686a76f32c | ||
|
|
cdafb82a49 | ||
|
|
0075f10f98 | ||
|
|
b70c0e4ab3 | ||
|
|
01aca092a1 | ||
|
|
8e6449ec12 | ||
|
|
984c4e2449 | ||
|
|
002c5485f5 | ||
|
|
12d74f15c0 | ||
|
|
2e87e179f0 | ||
|
|
a861126c0f | ||
|
|
8f5e58e8ad | ||
|
|
6c26f1f677 | ||
|
|
d7caaca5e4 | ||
|
|
835c81f329 | ||
|
|
1615b0cb29 | ||
|
|
c07a279c81 | ||
|
|
1478dae316 | ||
|
|
83a1e6616a | ||
|
|
66bd786842 | ||
|
|
33d73d8be8 | ||
|
|
75e190ba64 | ||
|
|
820569a52b | ||
|
|
05005eac32 | ||
|
|
5aa677c6fb | ||
|
|
456a3a9216 | ||
|
|
be25a7596f | ||
|
|
59a07e5dde | ||
|
|
6946a815e2 | ||
|
|
4a84e94022 | ||
|
|
5ba5d1f90e | ||
|
|
beb2bbcdc9 | ||
|
|
805fd5ae08 | ||
|
|
96093e313a | ||
|
|
afe9e88f9a | ||
|
|
b43048c674 | ||
|
|
11c38a599b | ||
|
|
0ce245f480 | ||
|
|
aef15cf3d3 | ||
|
|
d1880de30e | ||
|
|
9d900a69ed | ||
|
|
1653f77b15 | ||
|
|
9418436d51 | ||
|
|
1c9a6a194a | ||
|
|
11cfb5a962 | ||
|
|
7d21467447 | ||
|
|
5ce9f32deb | ||
|
|
337440259f | ||
|
|
c483e0768f | ||
|
|
fd9b0d9417 | ||
|
|
c3165f4937 | ||
|
|
a5c9adc872 | ||
|
|
59cc007931 | ||
|
|
8b0b12b521 | ||
|
|
6ae1cfd82e | ||
|
|
4d7eb27fd0 | ||
|
|
fe0b8d0128 | ||
|
|
04f0fcfbf7 | ||
|
|
7e182cf070 | ||
|
|
ad78c302ef | ||
|
|
36b4c69491 | ||
|
|
30bd0711f4 | ||
|
|
d847827584 | ||
|
|
786c1fcd58 | ||
|
|
90e7f0c0f7 | ||
|
|
63f334abe5 | ||
|
|
2466cd942f | ||
|
|
ea37db87f4 | ||
|
|
c168fb6960 | ||
|
|
03fb707b93 | ||
|
|
db226c2584 | ||
|
|
1c6ec82c91 | ||
|
|
40eb77ffde | ||
|
|
17ddb01cd1 | ||
|
|
ca56c7af70 | ||
|
|
55c428070f | ||
|
|
574eec1c08 | ||
|
|
15cde8173e | ||
|
|
ccb581b4ee | ||
|
|
c5f8db5b50 | ||
|
|
611748fbaf | ||
|
|
b57dfa9432 | ||
|
|
253982d579 | ||
|
|
94c7a19aa0 | ||
|
|
e9b360a721 | ||
|
|
ebf7f5932a | ||
|
|
4427f2fb99 | ||
|
|
6626dfbac3 | ||
|
|
093a6387a2 | ||
|
|
21ddde9e0d | ||
|
|
44d4e4e6da | ||
|
|
e34e53eeb3 | ||
|
|
07fb45bb34 | ||
|
|
d381669733 | ||
|
|
fb9eb15ae1 | ||
|
|
a2127c382b | ||
|
|
e10d39c093 | ||
|
|
a933b49e7c | ||
|
|
38bcd610df | ||
|
|
5beb476a28 | ||
|
|
3a43ce6546 | ||
|
|
c11fddb097 | ||
|
|
98f79cd9bf | ||
|
|
a3d3fe758b | ||
|
|
93587cf1b6 | ||
|
|
361e95f102 | ||
|
|
be212e7d1d | ||
|
|
003a30727f | ||
|
|
27b662e2dc | ||
|
|
0b4d7ad95e | ||
|
|
7acbfb230a | ||
|
|
b974310798 | ||
|
|
e391725eed | ||
|
|
f505580b33 | ||
|
|
270f932a48 | ||
|
|
b9afa908e3 | ||
|
|
6dcc78b9e5 | ||
|
|
7b0e7e8612 | ||
|
|
1ece4abd9d | ||
|
|
07c03b672b | ||
|
|
27ea50ec16 | ||
|
|
e1195e6663 | ||
|
|
faeb17f319 | ||
|
|
db91b1b127 | ||
|
|
b5b511c86b | ||
|
|
8c805fe0c9 | ||
|
|
b0672eb294 | ||
|
|
750019808c | ||
|
|
7f4510467f | ||
|
|
a46f8430df | ||
|
|
5e1ecb2b11 | ||
|
|
4fff59f16b | ||
|
|
eea8f5e07e | ||
|
|
a3fcd636e7 | ||
|
|
de6dc19077 | ||
|
|
7a8e3aca03 | ||
|
|
076047ad24 | ||
|
|
a4e1c8c24f | ||
|
|
3bf1c0075e | ||
|
|
1821ade319 | ||
|
|
dcd4f072d5 | ||
|
|
1734c7f545 | ||
|
|
aef3d340ae | ||
|
|
e39d4bc288 | ||
|
|
ed35b0f81a | ||
|
|
4805c9e1c9 | ||
|
|
eebcbe0f67 | ||
|
|
61e2b79357 | ||
|
|
fd3b03d3de | ||
|
|
9423a28158 | ||
|
|
55062068fd | ||
|
|
542b6f670d | ||
|
|
5b163b42b4 | ||
|
|
613dce51fb | ||
|
|
40adb5b203 | ||
|
|
fba796fa84 | ||
|
|
db4c3f9bfa | ||
|
|
d960cc6ad7 | ||
|
|
c620ec1f24 | ||
|
|
2d78bba6f4 | ||
|
|
b7a3f5d740 | ||
|
|
96def3c5d4 | ||
|
|
e1941d9d87 | ||
|
|
a30cf03678 | ||
|
|
aa0dcd3b7e | ||
|
|
810e92f7a3 | ||
|
|
5dd49fc1e1 | ||
|
|
8517af105d | ||
|
|
442cf7f9b3 | ||
|
|
82878b5214 | ||
|
|
b0f1102540 | ||
|
|
2efee7e6de | ||
|
|
abcecc7476 | ||
|
|
c28005f649 | ||
|
|
a369e61f18 | ||
|
|
05d82b6c05 | ||
|
|
68ca8fd250 | ||
|
|
b55b565ee5 |
37
.ci/php-cs-fixer/composer.lock
generated
37
.ci/php-cs-fixer/composer.lock
generated
@@ -406,16 +406,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.72.0",
|
||||
"version": "v3.75.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "900389362c43d116fee1ffc51f7878145fa61b57"
|
||||
"reference": "399a128ff2fdaf4281e4e79b755693286cdf325c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/900389362c43d116fee1ffc51f7878145fa61b57",
|
||||
"reference": "900389362c43d116fee1ffc51f7878145fa61b57",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c",
|
||||
"reference": "399a128ff2fdaf4281e4e79b755693286cdf325c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -423,6 +423,7 @@
|
||||
"composer/semver": "^3.4",
|
||||
"composer/xdebug-handler": "^3.0.3",
|
||||
"ext-filter": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"fidry/cpu-core-counter": "^1.2",
|
||||
@@ -497,7 +498,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.72.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -505,7 +506,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-13T11:25:37+00:00"
|
||||
"time": "2025-03-31T18:40:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1255,16 +1256,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.2.1",
|
||||
"version": "v7.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
|
||||
"reference": "e51498ea18570c062e7df29d05a7003585b19b88"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
|
||||
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88",
|
||||
"reference": "e51498ea18570c062e7df29d05a7003585b19b88",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1328,7 +1329,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.2.1"
|
||||
"source": "https://github.com/symfony/console/tree/v7.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1344,7 +1345,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-11T03:49:26+00:00"
|
||||
"time": "2025-03-12T08:11:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -2242,16 +2243,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.2.4",
|
||||
"version": "v7.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf"
|
||||
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
|
||||
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
|
||||
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2283,7 +2284,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.2.4"
|
||||
"source": "https://github.com/symfony/process/tree/v7.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2299,7 +2300,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-05T08:33:46+00:00"
|
||||
"time": "2025-03-13T12:21:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
|
||||
@@ -164,6 +164,13 @@ MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_SENDMAIL_COMMAND=
|
||||
|
||||
#
|
||||
# If you use self-signed certificates for your STMP server, you can use the following settings.
|
||||
#
|
||||
MAIL_ALLOW_SELF_SIGNED=false
|
||||
MAIL_VERIFY_PEER=true
|
||||
MAIL_VERIFY_PEER_NAME=true
|
||||
|
||||
# Other mail drivers:
|
||||
# If you use Docker or similar, you can set these variables from a file by appending them with _FILE
|
||||
MAILGUN_DOMAIN=
|
||||
|
||||
352
.github/workflows/release.yml
vendored
352
.github/workflows/release.yml
vendored
@@ -22,32 +22,65 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Switch branch
|
||||
run: |
|
||||
if [[ "develop" == "$version" ]]; then
|
||||
git checkout --track origin/develop
|
||||
git pull
|
||||
elif [[ "$version" == branch* ]]; then
|
||||
PULLBRANCH=${version:7}
|
||||
echo "The branch is '$PULLBRANCH' ($version)"
|
||||
git checkout --track origin/$PULLBRANCH
|
||||
git pull
|
||||
else
|
||||
git config user.name github-actions
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git checkout --track origin/develop
|
||||
git pull
|
||||
git checkout main
|
||||
git merge develop
|
||||
fi
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
git_user_signingkey: true
|
||||
git_commit_gpgsign: true
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ github.event.inputs.phpversion }}
|
||||
extensions: mbstring, intl, zip, bcmath
|
||||
- name: crowdin action
|
||||
- name: Switch and pull
|
||||
run: |
|
||||
#
|
||||
# Always check out origin/develop, unless its a branch release.
|
||||
#
|
||||
BRANCH_TO_PULL=origin/develop
|
||||
if [[ "$version" == branch* ]]; then
|
||||
BRANCH_TO_PULL=origin/$version
|
||||
fi
|
||||
|
||||
echo "Version is '$version', check out '$BRANCH_TO_PULL'-branch"
|
||||
|
||||
git checkout --track $BRANCH_TO_PULL
|
||||
git pull
|
||||
echo "Current branch is $(git branch --show-current)"
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Configure Git
|
||||
run: |
|
||||
# do some configuration
|
||||
sudo timedatectl set-timezone Europe/Amsterdam
|
||||
git config user.name JC5
|
||||
git config user.email release@firefly-iii.org
|
||||
git config advice.addIgnoredFile false
|
||||
git config push.autoSetupRemote true
|
||||
- name: Lint PHP
|
||||
run: |
|
||||
php_lint_file()
|
||||
{
|
||||
local php_file="$1"
|
||||
php -l "$php_file" &> /dev/null
|
||||
if [ "$?" -ne 0 ]
|
||||
then
|
||||
echo -e "[FAIL] $php_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
export -f php_lint_file
|
||||
|
||||
find . -path ./vendor -prune -o -name '*.php' | parallel -j 4 php_lint_file {}
|
||||
|
||||
if [ "$?" -ne 0 ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
- name: Crowdin action
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: true
|
||||
@@ -76,15 +109,6 @@ jobs:
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ''
|
||||
- name: Extract changelog
|
||||
id: extract-changelog
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
with:
|
||||
action: 'ff3:extract-changelog'
|
||||
output: 'output'
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ""
|
||||
- name: Replace version
|
||||
id: replace-version
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
@@ -134,18 +158,8 @@ jobs:
|
||||
composer update --no-dev --no-scripts --no-plugins -q
|
||||
sudo chown -R runner:docker resources/lang
|
||||
.ci/phpcs.sh || true
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
- name: Release
|
||||
- name: Calculate variables
|
||||
run: |
|
||||
# do some configuration
|
||||
sudo timedatectl set-timezone Europe/Amsterdam
|
||||
git config user.name github-actions
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git config advice.addIgnoredFile false
|
||||
|
||||
# set some variables
|
||||
releaseName=$version
|
||||
@@ -153,10 +167,6 @@ jobs:
|
||||
zipName=FireflyIII-$version.zip
|
||||
tarName=FireflyIII-$version.tar.gz
|
||||
|
||||
# update composer (again)
|
||||
composer update --no-dev --no-scripts --no-plugins
|
||||
composer dump-autoload
|
||||
|
||||
# if this is a develop build, slightly different variable names.
|
||||
if [[ "develop" == "$version" ]]; then
|
||||
#[[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
|
||||
@@ -191,35 +201,53 @@ jobs:
|
||||
tagFound=false
|
||||
fi
|
||||
done
|
||||
echo "Will use tag and release name $releaseName."
|
||||
|
||||
# set some variables
|
||||
echo "Release name is $releaseName."
|
||||
echo "Original name is $originalName."
|
||||
echo "Zip name is $zipName."
|
||||
echo "Tar name is $tarName."
|
||||
|
||||
# create a new branch to store the difference in.
|
||||
BRANCH_NAME=release-$(date +'%s')
|
||||
git checkout -b $BRANCH_NAME
|
||||
|
||||
echo "Temporary branch name is '$BRANCH_NAME'."
|
||||
|
||||
# share variables with next step.
|
||||
echo "releaseName=$releaseName" >> "$GITHUB_ENV"
|
||||
echo "originalName=$originalName" >> "$GITHUB_ENV"
|
||||
echo "zipName=$zipName" >> "$GITHUB_ENV"
|
||||
echo "tarName=$tarName" >> "$GITHUB_ENV"
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_ENV"
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Commit all changes
|
||||
run: |
|
||||
# add all content, except output.txt (this contains the changelog and/or the download instructions)
|
||||
echo 'Add all and reset output.txt'
|
||||
echo 'Add all'
|
||||
git add -A
|
||||
if test -f "output.txt"; then
|
||||
git reset output.txt
|
||||
fi
|
||||
git commit -m "Auto commit for release '$version' on $(date +'%Y-%m-%d')" || true
|
||||
# push to a new branch.
|
||||
echo "Auto commit on branch '$(git branch --show-current)'."
|
||||
git commit -m "🤖 Auto commit for release '$version' on $(date +'%Y-%m-%d')" || true
|
||||
git push
|
||||
|
||||
# zip and tar everything
|
||||
echo 'Zip and tar...'
|
||||
zip -rq $zipName . -x "*.git*" "*.ci*" "*.github*" "*node_modules*" "*output.txt*" "*Procfile*" "*crowdin.yml*" "*sonar-project.properties*"
|
||||
touch $tarName
|
||||
tar --exclude=$tarName --exclude=$zipName --exclude='./.git' --exclude='./.ci' --exclude='./.github' --exclude='./node_modules' --exclude='./output.txt' --exclude='./Procfile' --exclude='../crowdin.yml' --exclude='./sonar-project.properties' -czf $tarName .
|
||||
|
||||
# add sha256 sum
|
||||
echo 'Sha sum ...'
|
||||
sha256sum -b $zipName > $zipName.sha256
|
||||
sha256sum -b $tarName > $tarName.sha256
|
||||
|
||||
# add signatures:
|
||||
gpg --armor --detach-sign $zipName
|
||||
gpg --armor --detach-sign $tarName
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Extract changelog
|
||||
id: extract-changelog
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
with:
|
||||
action: 'ff3:extract-changelog'
|
||||
output: 'output'
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ""
|
||||
- name: Describe new release
|
||||
run: |
|
||||
|
||||
# describe the development release.
|
||||
if [[ "develop" == "$version" ]]; then
|
||||
echo 'Develop release.'
|
||||
echo 'Describe the latest develop release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
@@ -234,7 +262,7 @@ jobs:
|
||||
fi
|
||||
# describe a branch release
|
||||
if [[ "$version" == branch* ]]; then
|
||||
echo 'Branch release.'
|
||||
echo 'Describe a branch release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
@@ -249,8 +277,11 @@ jobs:
|
||||
fi
|
||||
# describe the main release
|
||||
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]] && [[ "$version" != *alpha* ]] && [[ "$version" != *beta* ]]; then
|
||||
echo 'Main release.'
|
||||
echo 'Describe the latest release'
|
||||
sudo chown -R runner:docker output.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
|
||||
echo '### Instructions' >> output.txt
|
||||
echo '' >> output.txt
|
||||
@@ -262,7 +293,7 @@ jobs:
|
||||
|
||||
# describe alpha release
|
||||
if [[ "$version" == *alpha* ]]; then
|
||||
echo 'ALPHA release.'
|
||||
echo 'Describe an ALPHA release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
@@ -280,7 +311,7 @@ jobs:
|
||||
|
||||
# describe beta release
|
||||
if [[ "$version" == *beta* ]]; then
|
||||
echo 'BETA release.'
|
||||
echo 'Describe a BETA release'
|
||||
rm -f output.txt
|
||||
touch output.txt
|
||||
sudo chown -R runner:docker output.txt
|
||||
@@ -295,55 +326,125 @@ jobs:
|
||||
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
|
||||
if [[ "$version" == branch* ]]; then
|
||||
MERGE_INTO=$version
|
||||
fi
|
||||
|
||||
echo "Merge all changes from $BRANCH_NAME back into '$MERGE_INTO' using a PR"
|
||||
PR_URL=$(gh pr create -B $MERGE_INTO -H $BRANCH_NAME --title "🤖 Automatic PR to merge all changes into the '$MERGE_INTO' branch." --body '🤖 Created by GitHub action')
|
||||
echo "PR URL is '$PR_URL'"
|
||||
IFS='/' read -ra parts <<< "$PR_URL"
|
||||
PR_NR=$(printf %s\\n "${parts[@]:(-1)}")
|
||||
echo "PR number is '$PR_NR'"
|
||||
gh pr merge $PR_NR -b "🤖 Automatically merge the PR into the $MERGE_INTO branch." -d --merge
|
||||
|
||||
# pull the changes from the $MERGE_INTO branch.
|
||||
git checkout $MERGE_INTO
|
||||
git merge origin/$MERGE_INTO
|
||||
git pull
|
||||
git status
|
||||
echo "Current branch '$(git branch --show-current)'."
|
||||
|
||||
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]]; then
|
||||
git checkout main
|
||||
git merge origin/main
|
||||
git pull
|
||||
git status
|
||||
|
||||
echo "Also merge everything into main since this is a release."
|
||||
echo 'create PR'
|
||||
PR_URL=$(gh pr create -B main -H develop --title "🤖 Automatic PR to merge all changes into the main branch." --body "🤖 Created by GitHub action")
|
||||
echo "PR URL is '$PR_URL'"
|
||||
|
||||
IFS='/' read -ra parts <<< "$PR_URL"
|
||||
PR_NR=$(printf %s\\n "${parts[@]:(-1)}")
|
||||
echo "PR number is '$PR_NR'"
|
||||
|
||||
echo 'Merge PR'
|
||||
gh pr merge $PR_NR -b "🤖 Automatically merge the PR into the main branch." --merge
|
||||
git checkout main
|
||||
git merge origin/main
|
||||
git pull
|
||||
git status
|
||||
echo "Current branch '$(git branch --show-current)'."
|
||||
|
||||
fi
|
||||
echo "DONE!"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Create archives
|
||||
run: |
|
||||
echo "Create zip file $zipName"
|
||||
zip -rq $zipName . -x "*.git*" "*.ci*" "*.github*" "*node_modules*" "*output.txt*" "*Procfile*" "*crowdin.yml*" "*sonar-project.properties*"
|
||||
touch $tarName
|
||||
|
||||
echo "Create tar file $tarName"
|
||||
tar --exclude=$tarName --exclude=$zipName --exclude='./.git' --exclude='./.ci' --exclude='./.github' --exclude='./node_modules' --exclude='./output.txt' --exclude='./Procfile' --exclude='../crowdin.yml' --exclude='./sonar-project.properties' -czf $tarName .
|
||||
# add sha256 sum
|
||||
echo 'Sha sum ...'
|
||||
sha256sum -b $zipName > $zipName.sha256
|
||||
sha256sum -b $tarName > $tarName.sha256
|
||||
|
||||
# add signatures:
|
||||
gpg --armor --detach-sign $zipName
|
||||
gpg --armor --detach-sign $tarName
|
||||
- name: Create release
|
||||
run: |
|
||||
|
||||
# create a development release:
|
||||
if [[ "develop" == "$version" ]]; then
|
||||
# create the release:
|
||||
echo "Create develop release."
|
||||
git tag -a $releaseName -m "Development release '$version' on $(date +'%Y-%m-%d')"
|
||||
# pull the changes from the develop branch.
|
||||
git checkout develop
|
||||
git merge origin/develop
|
||||
git pull
|
||||
|
||||
# create the release:
|
||||
echo "Create develop release under tag '$releaseName'."
|
||||
git tag -a $releaseName -m "🤖 Development release '$version' on $(date +'%Y-%m-%d')"
|
||||
git push origin $releaseName
|
||||
|
||||
gh release create $releaseName -p --verify-tag \
|
||||
-t "Development release for $(date +'%Y-%m-%d')" \
|
||||
--latest=false \
|
||||
-F output.txt
|
||||
|
||||
fi
|
||||
|
||||
# create a branch release:
|
||||
if [[ "$version" == branch* ]]; then
|
||||
|
||||
# pull the changes from the branch-* branch.
|
||||
git checkout $version
|
||||
git merge origin/$version
|
||||
git pull
|
||||
|
||||
# create the release:
|
||||
echo "Create branch release."
|
||||
git tag -a $releaseName -m "Branch release '$version' on $(date +'%Y-%m-%d')"
|
||||
|
||||
git push origin $releaseName
|
||||
|
||||
gh release create $releaseName -p --verify-tag \
|
||||
-t "Branch release for $(date +'%Y-%m-%d')" \
|
||||
--latest=false \
|
||||
-F output.txt
|
||||
|
||||
fi
|
||||
|
||||
# create a development (nightly) release:
|
||||
if [[ "develop" == "$version" ]] || [[ "$version" == branch* ]]; then
|
||||
# add zip file to release.
|
||||
gh release upload $releaseName $zipName
|
||||
gh release upload $releaseName $tarName
|
||||
# Create a production release.
|
||||
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]]; then
|
||||
git checkout main
|
||||
git merge origin/main
|
||||
git pull
|
||||
git status
|
||||
|
||||
# add sha256 sum to release
|
||||
gh release upload $releaseName $zipName.sha256
|
||||
gh release upload $releaseName $tarName.sha256
|
||||
|
||||
# add signatures to release
|
||||
gh release upload $releaseName $zipName.asc
|
||||
gh release upload $releaseName $tarName.asc
|
||||
|
||||
# get current HEAD and add as file to the release
|
||||
HEAD=$(git rev-parse HEAD)
|
||||
echo $HEAD > HEAD.txt
|
||||
gh release upload $releaseName HEAD.txt
|
||||
else
|
||||
echo 'MAIN (real) release'
|
||||
git tag -a $releaseName -m "Here be changelog"
|
||||
echo "Create prod release."
|
||||
git tag -a $releaseName -m "Release $version"
|
||||
git push origin $releaseName
|
||||
|
||||
# do not tag as latest when alpha or beta.
|
||||
@@ -355,39 +456,38 @@ jobs:
|
||||
# tag as latest when NOT alpha or beta.
|
||||
if [[ "$version" != *alpha* ]] && [[ "$version" != *beta* ]]; then
|
||||
echo 'Mark prod as the latest.'
|
||||
gh release create $releaseName -F output.txt -t "$releaseName" --verify-tag
|
||||
gh release create $releaseName -F output.txt -t "$releaseName" --verify-tag --latest=true
|
||||
fi
|
||||
|
||||
# add archive files to release
|
||||
gh release upload $releaseName $zipName
|
||||
gh release upload $releaseName $tarName
|
||||
|
||||
# add sha256 sums to release
|
||||
gh release upload $releaseName $zipName.sha256
|
||||
gh release upload $releaseName $tarName.sha256
|
||||
|
||||
# add signatures to release
|
||||
gh release upload $releaseName $zipName.asc
|
||||
gh release upload $releaseName $tarName.asc
|
||||
|
||||
# get current HEAD and add as file to the release
|
||||
HEAD=$(git rev-parse HEAD)
|
||||
echo $HEAD > HEAD.txt
|
||||
gh release upload $releaseName HEAD.txt
|
||||
|
||||
# remove all temporary files
|
||||
rm -f output.txt
|
||||
rm -f HEAD.txt
|
||||
rm -f $zipName
|
||||
rm -f $zipName.sha256
|
||||
rm -f $tarName
|
||||
rm -f $tarName.sha256
|
||||
|
||||
# merge main back into develop
|
||||
git checkout develop
|
||||
git merge main
|
||||
git push
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Upload artifacts
|
||||
run: |
|
||||
# add zip file to release.
|
||||
gh release upload $releaseName $zipName
|
||||
gh release upload $releaseName $tarName
|
||||
|
||||
# add sha256 sum to release
|
||||
gh release upload $releaseName $zipName.sha256
|
||||
gh release upload $releaseName $tarName.sha256
|
||||
|
||||
# add signatures to release
|
||||
gh release upload $releaseName $zipName.asc
|
||||
gh release upload $releaseName $tarName.asc
|
||||
|
||||
# get current HEAD and add as file to the release
|
||||
HEAD=$(git rev-parse HEAD)
|
||||
echo $HEAD > HEAD.txt
|
||||
gh release upload $releaseName HEAD.txt
|
||||
|
||||
# remove all temporary files
|
||||
rm -f output.txt
|
||||
rm -f HEAD.txt
|
||||
rm -f $zipName
|
||||
rm -f $zipName.sha256
|
||||
rm -f $tarName
|
||||
rm -f $tarName.sha256
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ yarn-error.log
|
||||
.env
|
||||
/.ci/php-cs-fixer/vendor
|
||||
coverage.xml
|
||||
output.txt
|
||||
|
||||
# ignore generated files.
|
||||
public/build
|
||||
|
||||
@@ -4,6 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
|
||||
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
|
||||
|
||||
## 2025
|
||||
- Denis Iskandarov
|
||||
- =
|
||||
- Lompi
|
||||
- Jose Diaz-Gonzalez
|
||||
- SoftBrix
|
||||
|
||||
@@ -30,6 +30,7 @@ use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\User;
|
||||
@@ -82,6 +83,7 @@ class AccountController extends Controller
|
||||
$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'));
|
||||
|
||||
// set date to subday + end-of-day for account balance. so it is at $date 23:59:59
|
||||
@@ -135,6 +137,7 @@ class AccountController extends Controller
|
||||
return $posA - $posB;
|
||||
}
|
||||
);
|
||||
Timer::stop(sprintf('AC accounts "%s"', $query));
|
||||
|
||||
return response()->api($return);
|
||||
}
|
||||
|
||||
@@ -27,13 +27,16 @@ namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Data\DateRequest;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Chart\ChartData;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ApiSupport;
|
||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -43,8 +46,10 @@ use Illuminate\Http\JsonResponse;
|
||||
class AccountController extends Controller
|
||||
{
|
||||
use ApiSupport;
|
||||
use CollectsAccountsFromFilter;
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
private ChartData $chartData;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
@@ -56,6 +61,7 @@ class AccountController extends Controller
|
||||
function ($request, $next) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$this->chartData = new ChartData();
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->repository->setUser($user);
|
||||
|
||||
@@ -64,6 +70,29 @@ class AccountController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO fix documentation
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function dashboard(ChartRequest $request): JsonResponse
|
||||
{
|
||||
$queryParameters = $request->getParameters();
|
||||
$accounts = $this->getAccountList($queryParameters);
|
||||
|
||||
// move date to end of day
|
||||
$queryParameters['start']->startOfDay();
|
||||
$queryParameters['end']->endOfDay();
|
||||
|
||||
// loop each account, and collect info:
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->renderAccountData($queryParameters, $account);
|
||||
}
|
||||
|
||||
return response()->json($this->chartData->render());
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/charts/getChartAccountOverview
|
||||
@@ -133,4 +162,45 @@ class AccountController extends Controller
|
||||
|
||||
return response()->json($chartData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function renderAccountData(array $params, Account $account): void
|
||||
{
|
||||
$currency = $this->repository->getAccountCurrency($account);
|
||||
if (null === $currency) {
|
||||
$currency = $this->default;
|
||||
}
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
|
||||
// the currency that belongs to the account.
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
|
||||
// the default currency of the user (could be the same!)
|
||||
'date' => $params['start']->toAtomString(),
|
||||
'start' => $params['start']->toAtomString(),
|
||||
'end' => $params['end']->toAtomString(),
|
||||
'period' => '1D',
|
||||
'entries' => [],
|
||||
];
|
||||
$currentStart = clone $params['start'];
|
||||
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
|
||||
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
while ($currentStart <= $params['end']) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
|
||||
$currentStart->addDay();
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
}
|
||||
$this->chartData->add($currentSet);
|
||||
}
|
||||
}
|
||||
|
||||
260
app/Api/V1/Controllers/Chart/BudgetController.php
Normal file
260
app/Api/V1/Controllers/Chart/BudgetController.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* BudgetController.php
|
||||
* Copyright (c) 2023 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\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Generic\DateRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
*/
|
||||
class BudgetController extends Controller
|
||||
{
|
||||
use CleansChartData;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
protected OperationsRepositoryInterface $opsRepository;
|
||||
private BudgetLimitRepositoryInterface $blRepository;
|
||||
private array $currencies = [];
|
||||
private TransactionCurrency $currency;
|
||||
private BudgetRepositoryInterface $repository;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(BudgetRepositoryInterface::class);
|
||||
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
|
||||
$this->opsRepository = app(OperationsRepositoryInterface::class);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->opsRepository->setUserGroup($userGroup);
|
||||
$this->blRepository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO see autocomplete/accountcontroller
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse
|
||||
{
|
||||
$params = $request->getAll();
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = $params['start'];
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $params['end'];
|
||||
|
||||
// code from FrontpageChartGenerator, but not in separate class
|
||||
$budgets = $this->repository->getActiveBudgets();
|
||||
$data = [];
|
||||
|
||||
/** @var Budget $budget */
|
||||
foreach ($budgets as $budget) {
|
||||
// could return multiple arrays, so merge.
|
||||
$data = array_merge($data, $this->processBudget($budget, $start, $end));
|
||||
}
|
||||
|
||||
return response()->json($this->clean($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get all limits:
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
|
||||
// if no limits
|
||||
if (0 === $limits->count()) {
|
||||
// return as a single item in an array
|
||||
$rows = $this->noBudgetLimits($budget, $start, $end);
|
||||
}
|
||||
if ($limits->count() > 0) {
|
||||
$rows = $this->budgetLimits($budget, $limits);
|
||||
}
|
||||
// is always an array
|
||||
$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_decimal_places' => $row['currency_decimal_places'],
|
||||
'period' => null,
|
||||
'start' => $row['start'],
|
||||
'end' => $row['end'],
|
||||
'entries' => [
|
||||
'spent' => $row['spent'],
|
||||
'left' => $row['left'],
|
||||
'overspent' => $row['overspent'],
|
||||
],
|
||||
];
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* When no budget limits are present, the expenses of the whole period are collected and grouped.
|
||||
* This is grouped per currency. Because there is no limit set, "left to spend" and "overspent" are empty.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function noBudgetLimits(Budget $budget, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
|
||||
|
||||
return $this->processExpenses($budget->id, $spent, $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared between the "noBudgetLimits" function and "processLimit". Will take a single set of expenses and return
|
||||
* its info.
|
||||
*
|
||||
* @param array<int, array<int, string>> $array
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function processExpenses(int $budgetId, array $array, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* This array contains the expenses in this budget. Grouped per currency.
|
||||
* The grouping is on the main currency only.
|
||||
*
|
||||
* @var int $currencyId
|
||||
* @var array $block
|
||||
*/
|
||||
foreach ($array as $currencyId => $block) {
|
||||
$this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
|
||||
$return[$currencyId] ??= [
|
||||
'currency_id' => (string) $currencyId,
|
||||
'currency_code' => $block['currency_code'],
|
||||
'currency_name' => $block['currency_name'],
|
||||
'currency_symbol' => $block['currency_symbol'],
|
||||
'currency_decimal_places' => (int) $block['currency_decimal_places'],
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
'spent' => '0',
|
||||
'left' => '0',
|
||||
'overspent' => '0',
|
||||
];
|
||||
$currentBudgetArray = $block['budgets'][$budgetId];
|
||||
|
||||
// var_dump($return);
|
||||
/** @var array $journal */
|
||||
foreach ($currentBudgetArray['transaction_journals'] as $journal) {
|
||||
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $journal['amount']);
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that processes each budget limit (per budget).
|
||||
*
|
||||
* If you have a budget limit in EUR, only transactions in EUR will be considered.
|
||||
* If you have a budget limit in GBP, only transactions in GBP will be considered.
|
||||
*
|
||||
* If you have a budget limit in EUR, and a transaction in GBP, it will not be considered for the EUR budget limit.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function budgetLimits(Budget $budget, Collection $limits): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in budgetLimits(#%d)', $budget->id));
|
||||
$data = [];
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
$data = array_merge($data, $this->processLimit($budget, $limit));
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function processLimit(Budget $budget, BudgetLimit $limit): array
|
||||
{
|
||||
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
|
||||
$end = clone $limit->end_date;
|
||||
$end->endOfDay();
|
||||
$spent = $this->opsRepository->listExpenses($limit->start_date, $end, null, new Collection([$budget]));
|
||||
$limitCurrencyId = $limit->transaction_currency_id;
|
||||
$filtered = [];
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($spent as $currencyId => $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
|
||||
if ($entry['currency_id'] === $limitCurrencyId) {
|
||||
$filtered[$currencyId] = $entry;
|
||||
}
|
||||
}
|
||||
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
||||
if (1 === count($result)) {
|
||||
$compare = bccomp($limit->amount, app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||
if (1 === $compare) {
|
||||
// convert this amount into the native currency:
|
||||
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, $result[$limitCurrencyId]['spent']);
|
||||
}
|
||||
if ($compare <= 0) {
|
||||
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, $result[$limitCurrencyId]['spent']));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
128
app/Api/V1/Controllers/Chart/CategoryController.php
Normal file
128
app/Api/V1/Controllers/Chart/CategoryController.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* CategoryController.php
|
||||
* Copyright (c) 2023 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\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Generic\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
*/
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
use CleansChartData;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
private AccountRepositoryInterface $accountRepos;
|
||||
private CurrencyRepositoryInterface $currencyRepos;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->accountRepos->setUserGroup($userGroup);
|
||||
$this->currencyRepos->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO may be worth to move to a handler but the data is simple enough.
|
||||
* TODO see autoComplete/account controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse
|
||||
{
|
||||
/** @var Carbon $start */
|
||||
$start = $this->parameters->get('start');
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $this->parameters->get('end');
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
|
||||
$currencies = [];
|
||||
$return = [];
|
||||
|
||||
// get journals for entire period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->withAccountInformation();
|
||||
$collector->setXorAccounts($accounts)->withCategoryInformation();
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::RECONCILIATION->value]);
|
||||
$journals = $collector->getExtractedJournals();
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
|
||||
$currencies[$currencyId] = $currency;
|
||||
$categoryName = null === $journal['category_name'] ? (string) trans('firefly.no_category') : $journal['category_name'];
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
$key = sprintf('%s-%s', $categoryName, $currency->code);
|
||||
// create arrays
|
||||
$return[$key] ??= [
|
||||
'label' => $categoryName,
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'period' => null,
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
'amount' => '0',
|
||||
];
|
||||
|
||||
// add monies
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], $amount);
|
||||
}
|
||||
$return = array_values($return);
|
||||
|
||||
// order by amount
|
||||
usort($return, static function (array $a, array $b) {
|
||||
return (float) $a['amount'] < (float) $b['amount'] ? 1 : -1;
|
||||
});
|
||||
|
||||
return response()->json($this->clean($return));
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\AbstractTransformer;
|
||||
use FireflyIII\Transformers\AbstractTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
@@ -263,7 +263,7 @@ abstract class Controller extends BaseController
|
||||
|
||||
// the transformer, at this point, needs to collect information that ALL items in the collection
|
||||
// require, like meta-data and stuff like that, and save it for later.
|
||||
$objects = $transformer->collectMetaData($objects);
|
||||
// $objects = $transformer->collectMetaData($objects);
|
||||
$paginator->setCollection($objects);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
@@ -284,7 +284,7 @@ abstract class Controller extends BaseController
|
||||
$baseUrl = sprintf('%s/api/v1', request()->getSchemeAndHttpHost());
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$transformer->collectMetaData(new Collection([$object]));
|
||||
// $transformer->collectMetaData(new Collection([$object]));
|
||||
|
||||
$resource = new Item($object, $transformer, $key);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\ExchangeRateTransformer;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\ExchangeRateTransformer;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreRequest;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\ExchangeRateTransformer;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class StoreController extends Controller
|
||||
|
||||
@@ -29,7 +29,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\ExchangeRateTransformer;
|
||||
use FireflyIII\Transformers\ExchangeRateTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UpdateController extends Controller
|
||||
|
||||
@@ -39,8 +39,11 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -97,16 +100,15 @@ class BasicController extends Controller
|
||||
$start = $dates['start'];
|
||||
$end = $dates['end'];
|
||||
$code = $request->get('currency_code');
|
||||
|
||||
// balance information:
|
||||
$balanceData = $this->getBalanceInformation($start, $end);
|
||||
$billData = $this->getBillInformation($start, $end);
|
||||
$billData = $this->getSubscriptionInformation($start, $end);
|
||||
$spentData = $this->getLeftToSpendInfo($start, $end);
|
||||
$netWorthData = $this->getNetWorthInfo($end);
|
||||
// $balanceData = [];
|
||||
// $billData = [];
|
||||
// $balanceData = [];
|
||||
// $billData = [];
|
||||
// $spentData = [];
|
||||
// $netWorthData = [];
|
||||
// $netWorthData = [];
|
||||
$total = array_merge($balanceData, $billData, $spentData, $netWorthData);
|
||||
|
||||
// give new keys
|
||||
@@ -122,6 +124,7 @@ class BasicController extends Controller
|
||||
|
||||
private function getBalanceInformation(Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug('getBalanceInformation');
|
||||
// some config settings
|
||||
$convertToNative = Amount::convertToNative();
|
||||
$default = Amount::getNativeCurrency();
|
||||
@@ -130,47 +133,110 @@ class BasicController extends Controller
|
||||
$expenses = [];
|
||||
$sums = [];
|
||||
$return = [];
|
||||
$currencies = [
|
||||
$default->id => $default,
|
||||
];
|
||||
|
||||
// collect income of user using the new group collector.
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::DEPOSIT->value]);
|
||||
$summarizer = new TransactionSummarizer();
|
||||
$set = $collector->setRange($start, $end)->setTypes([TransactionTypeEnum::DEPOSIT->value])->getExtractedJournals();
|
||||
$incomes = $summarizer->groupByCurrencyId($set, 'positive', false);
|
||||
|
||||
$set = $collector->getExtractedJournals();
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($set as $journal) {
|
||||
$currencyId = $convertToNative ? $default->id : (int) $journal['currency_id'];
|
||||
$amount = Amount::getAmountFromJournal($journal);
|
||||
$incomes[$currencyId] ??= '0';
|
||||
$incomes[$currencyId] = bcadd(
|
||||
$incomes[$currencyId],
|
||||
bcmul($amount, '-1')
|
||||
);
|
||||
$sums[$currencyId] ??= '0';
|
||||
$sums[$currencyId] = bcadd($sums[$currencyId], bcmul($amount, '-1'));
|
||||
}
|
||||
|
||||
// collect expenses of user.
|
||||
// collect expenses of user using the new group collector.
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$set = $collector->getExtractedJournals();
|
||||
$set = $collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->getExtractedJournals();
|
||||
$expenses = $summarizer->groupByCurrencyId($set, 'negative', false);
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($set as $journal) {
|
||||
$currencyId = $convertToNative ? $default->id : (int) $journal['currency_id'];
|
||||
$amount = Amount::getAmountFromJournal($journal);
|
||||
$expenses[$currencyId] ??= '0';
|
||||
$expenses[$currencyId] = bcadd($expenses[$currencyId], $amount);
|
||||
$sums[$currencyId] ??= '0';
|
||||
$sums[$currencyId] = bcadd($sums[$currencyId], $amount);
|
||||
// if convert to native, do so right now.
|
||||
if ($convertToNative) {
|
||||
$newExpenses = [
|
||||
$default->id => [
|
||||
'currency_id' => $default->id,
|
||||
'currency_code' => $default->code,
|
||||
'currency_symbol' => $default->symbol,
|
||||
'currency_decimal_places' => $default->decimal_places,
|
||||
'sum' => '0',
|
||||
],
|
||||
];
|
||||
$newIncomes = [
|
||||
$default->id => [
|
||||
'currency_id' => $default->id,
|
||||
'currency_code' => $default->code,
|
||||
'currency_symbol' => $default->symbol,
|
||||
'currency_decimal_places' => $default->decimal_places,
|
||||
'sum' => '0',
|
||||
],
|
||||
];
|
||||
$sums = [
|
||||
$default->id => [
|
||||
'currency_id' => $default->id,
|
||||
'currency_code' => $default->code,
|
||||
'currency_symbol' => $default->symbol,
|
||||
'currency_decimal_places' => $default->decimal_places,
|
||||
'sum' => '0',
|
||||
],
|
||||
];
|
||||
|
||||
$converter = new ExchangeRateConverter();
|
||||
// loop over income and expenses
|
||||
foreach ([$expenses, $incomes] as $index => $array) {
|
||||
|
||||
// loop over either one.
|
||||
foreach ($array as $entry) {
|
||||
|
||||
// if it is the native currency already.
|
||||
if ($entry['currency_id'] === $default->id) {
|
||||
$sums[$default->id]['sum'] = bcadd($entry['sum'], $sums[$default->id]['sum']);
|
||||
|
||||
// don't forget to add it to newExpenses and newIncome
|
||||
if (0 === $index) {
|
||||
$newExpenses[$default->id]['sum'] = bcadd($newExpenses[$default->id]['sum'], $entry['sum']);
|
||||
}
|
||||
if (1 === $index) {
|
||||
$newIncomes[$default->id]['sum'] = bcadd($newIncomes[$default->id]['sum'], $entry['sum']);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$currencies[$entry['currency_id']] ??= $this->currencyRepos->find($entry['currency_id']);
|
||||
$convertedSum = $converter->convert($currencies[$entry['currency_id']], $default, $start, $entry['sum']);
|
||||
$sums[$default->id]['sum'] = bcadd($sums[$default->id]['sum'], $convertedSum);
|
||||
if (0 === $index) {
|
||||
$newExpenses[$default->id]['sum'] = bcadd($newExpenses[$default->id]['sum'], $convertedSum);
|
||||
}
|
||||
if (1 === $index) {
|
||||
$newIncomes[$default->id]['sum'] = bcadd($newIncomes[$default->id]['sum'], $convertedSum);
|
||||
}
|
||||
}
|
||||
}
|
||||
$incomes = $newIncomes;
|
||||
$expenses = $newExpenses;
|
||||
}
|
||||
if (!$convertToNative) {
|
||||
foreach ([$expenses, $incomes] as $array) {
|
||||
foreach ($array as $entry) {
|
||||
$currencyId = $entry['currency_id'];
|
||||
$sums[$currencyId] ??= [
|
||||
'currency_id' => $entry['currency_id'],
|
||||
'currency_code' => $entry['currency_code'],
|
||||
'currency_symbol' => $entry['currency_symbol'],
|
||||
'currency_decimal_places' => $entry['currency_decimal_places'],
|
||||
'sum' => '0',
|
||||
];
|
||||
$sums[$currencyId]['sum'] = bcadd($sums[$currencyId]['sum'], $entry['sum']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// format amounts:
|
||||
$keys = array_keys($sums);
|
||||
foreach ($keys as $currencyId) {
|
||||
$currency = $this->currencyRepos->find($currencyId);
|
||||
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
|
||||
if (null === $currency) {
|
||||
continue;
|
||||
}
|
||||
@@ -178,37 +244,37 @@ class BasicController extends Controller
|
||||
$return[] = [
|
||||
'key' => sprintf('balance-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_balance_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => $sums[$currencyId] ?? '0',
|
||||
'monetary_value' => $sums[$currencyId]['sum'] ?? '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId] ?? '0', false),
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false)
|
||||
.' + '.app('amount')->formatAnything($currency, $incomes[$currencyId] ?? '0', false),
|
||||
'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
|
||||
.' + '.app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('spent-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_spent_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => $expenses[$currencyId] ?? '0',
|
||||
'monetary_value' => $expenses[$currencyId]['sum'] ?? '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false),
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('earned-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_earned_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => $incomes[$currencyId] ?? '0',
|
||||
'monetary_value' => $incomes[$currencyId]['sum'] ?? '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId] ?? '0', false),
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -227,7 +293,7 @@ class BasicController extends Controller
|
||||
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => app('amount')->formatAnything($currency, '0', false)
|
||||
.' + '.app('amount')->formatAnything($currency, '0', false),
|
||||
.' + '.app('amount')->formatAnything($currency, '0', false),
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('spent-in-%s', $currency->code),
|
||||
@@ -258,15 +324,72 @@ class BasicController extends Controller
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getBillInformation(Carbon $start, Carbon $end): array
|
||||
private function getSubscriptionInformation(Carbon $start, Carbon $end): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
Log::debug(sprintf('Now in getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
/*
|
||||
* Since both this method and the chart use the exact same data, we can suffice
|
||||
* with calling the one method in the bill repository that will get this amount.
|
||||
*/
|
||||
$paidAmount = $this->billRepository->sumPaidInRange($start, $end);
|
||||
$unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end);
|
||||
$currencies = [
|
||||
$this->nativeCurrency->id => $this->nativeCurrency,
|
||||
];
|
||||
|
||||
if ($this->convertToNative) {
|
||||
$converter = new ExchangeRateConverter();
|
||||
$newPaidAmount = [[
|
||||
'id' => $this->nativeCurrency->id,
|
||||
'name' => $this->nativeCurrency->name,
|
||||
'symbol' => $this->nativeCurrency->symbol,
|
||||
'code' => $this->nativeCurrency->code,
|
||||
'decimal_places' => $this->nativeCurrency->decimal_places,
|
||||
'sum' => '0',
|
||||
]];
|
||||
|
||||
$newUnpaidAmount = [[
|
||||
'id' => $this->nativeCurrency->id,
|
||||
'name' => $this->nativeCurrency->name,
|
||||
'symbol' => $this->nativeCurrency->symbol,
|
||||
'code' => $this->nativeCurrency->code,
|
||||
'decimal_places' => $this->nativeCurrency->decimal_places,
|
||||
'sum' => '0',
|
||||
]];
|
||||
foreach ([$paidAmount, $unpaidAmount] as $index => $array) {
|
||||
foreach ($array as $item) {
|
||||
$currencyId = (int) $item['id'];
|
||||
if (0 === $index) {
|
||||
// paid amount
|
||||
if ($currencyId === $this->nativeCurrency->id) {
|
||||
$newPaidAmount[0]['sum'] = bcadd($newPaidAmount[0]['sum'], $item['sum']);
|
||||
|
||||
continue;
|
||||
}
|
||||
$currencies[$currencyId] ??= $this->currencyRepos->find($currencyId);
|
||||
$convertedAmount = $converter->convert($currencies[$currencyId], $this->nativeCurrency, $start, $item['sum']);
|
||||
$newPaidAmount[0]['sum'] = bcadd($newPaidAmount[0]['sum'], $convertedAmount);
|
||||
|
||||
continue;
|
||||
}
|
||||
// unpaid amount
|
||||
if ($currencyId === $this->nativeCurrency->id) {
|
||||
$newUnpaidAmount[0]['sum'] = bcadd($newUnpaidAmount[0]['sum'], $item['sum']);
|
||||
|
||||
continue;
|
||||
}
|
||||
$currencies[$currencyId] ??= $this->currencyRepos->find($currencyId);
|
||||
$convertedAmount = $converter->convert($currencies[$currencyId], $this->nativeCurrency, $start, $item['sum']);
|
||||
$newUnpaidAmount[0]['sum'] = bcadd($newUnpaidAmount[0]['sum'], $convertedAmount);
|
||||
}
|
||||
}
|
||||
$paidAmount = $newPaidAmount;
|
||||
$unpaidAmount = $newUnpaidAmount;
|
||||
}
|
||||
|
||||
// var_dump($paidAmount);
|
||||
// var_dump($unpaidAmount);
|
||||
// exit;
|
||||
|
||||
$return = [];
|
||||
|
||||
@@ -307,7 +430,7 @@ class BasicController extends Controller
|
||||
'sub_title' => '',
|
||||
];
|
||||
}
|
||||
app('log')->debug(sprintf('Done with getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
Log::debug(sprintf('Done with getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
|
||||
if (0 === count($return)) {
|
||||
$currency = $this->nativeCurrency;
|
||||
@@ -348,30 +471,60 @@ class BasicController extends Controller
|
||||
*/
|
||||
private function getLeftToSpendInfo(Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug(sprintf('Now in getLeftToSpendInfo("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
$return = [];
|
||||
$today = today(config('app.timezone'));
|
||||
$available = $this->abRepository->getAvailableBudgetWithCurrency($start, $end);
|
||||
$budgets = $this->budgetRepository->getActiveBudgets();
|
||||
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets);
|
||||
$days = (int) $today->diffInDays($end, true) + 1;
|
||||
|
||||
Log::debug(sprintf('Now in getLeftToSpendInfo("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
$return = [];
|
||||
$today = today(config('app.timezone'));
|
||||
$available = $this->abRepository->getAvailableBudgetWithCurrency($start, $end);
|
||||
$budgets = $this->budgetRepository->getActiveBudgets();
|
||||
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets);
|
||||
$days = (int) $today->diffInDays($end, true) + 1;
|
||||
$currencies = [];
|
||||
|
||||
// first, create an entry for each entry in the "available" array.
|
||||
/** @var array $availableBudget */
|
||||
foreach ($available as $currencyId => $availableBudget) {
|
||||
$currencies[$currencyId] ??= $this->currencyRepos->find($currencyId);
|
||||
$return[$currencyId] = [
|
||||
'key' => sprintf('left-to-spend-in-%s', $currencies[$currencyId]->code),
|
||||
'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $currencies[$currencyId]->symbol]),
|
||||
'no_available_budgets' => false,
|
||||
'monetary_value' => $availableBudget,
|
||||
'currency_id' => (string) $currencies[$currencyId]->id,
|
||||
'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),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$currencies[$currencyId]->symbol,
|
||||
$currencies[$currencyId]->decimal_places,
|
||||
$availableBudget,
|
||||
false
|
||||
),
|
||||
];
|
||||
}
|
||||
foreach ($spent as $row) {
|
||||
// either an amount was budgeted or 0 is available.
|
||||
$currencyId = $row['currency_id'];
|
||||
$amount = (string) ($available[$currencyId] ?? '0');
|
||||
$spentInCurrency = $row['sum'];
|
||||
$leftToSpend = bcadd($amount, $spentInCurrency);
|
||||
$perDay = '0';
|
||||
$currencyId = (int) $row['currency_id'];
|
||||
$amount = (string) ($available[$currencyId] ?? '0');
|
||||
if (0 === bccomp($amount, '0')) {
|
||||
// #9858 skip over currencies with no available budget.
|
||||
continue;
|
||||
}
|
||||
$spentInCurrency = $row['sum'];
|
||||
$leftToSpend = bcadd($amount, $spentInCurrency);
|
||||
$perDay = '0';
|
||||
if (0 !== $days && bccomp($leftToSpend, '0') > -1) {
|
||||
$perDay = bcdiv($leftToSpend, (string) $days);
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Spent %s %s', $row['currency_code'], $row['sum']));
|
||||
|
||||
$return[] = [
|
||||
$return[$currencyId] = [
|
||||
'key' => sprintf('left-to-spend-in-%s', $row['currency_code']),
|
||||
'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]),
|
||||
'no_available_budgets' => false,
|
||||
'monetary_value' => $leftToSpend,
|
||||
'currency_id' => (string) $row['currency_id'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
@@ -387,28 +540,66 @@ class BasicController extends Controller
|
||||
),
|
||||
];
|
||||
}
|
||||
unset($leftToSpend);
|
||||
if (0 === count($return)) {
|
||||
$currency = $this->nativeCurrency;
|
||||
$return[] = [
|
||||
'key' => sprintf('left-to-spend-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'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),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$currency->symbol,
|
||||
$currency->decimal_places,
|
||||
'0',
|
||||
false
|
||||
),
|
||||
];
|
||||
// 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) {
|
||||
// either an amount was budgeted or 0 is available.
|
||||
$currencyId = (int) $row['currency_id'];
|
||||
$spentInCurrency = $row['sum'];
|
||||
$perDay = '0';
|
||||
if (0 !== $days && -1 === bccomp($spentInCurrency, '0')) {
|
||||
$perDay = bcdiv($spentInCurrency, (string) $days);
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Spent %s %s', $row['currency_code'], $row['sum']));
|
||||
|
||||
$return[$currencyId] = [
|
||||
'key' => sprintf('left-to-spend-in-%s', $row['currency_code']),
|
||||
'title' => trans('firefly.spent'),
|
||||
'no_available_budgets' => true,
|
||||
'monetary_value' => $spentInCurrency,
|
||||
'currency_id' => (string) $row['currency_id'],
|
||||
'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),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$row['currency_symbol'],
|
||||
$row['currency_decimal_places'],
|
||||
$perDay,
|
||||
false
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// $amount = '0';
|
||||
// // $days
|
||||
// // fill in by money spent, just count it.
|
||||
// $currency = $this->nativeCurrency;
|
||||
// $return[$currency->id] = [
|
||||
// 'key' => sprintf('left-to-spend-in-%s', $currency->code),
|
||||
// 'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $currency->symbol]),
|
||||
// 'monetary_value' => '0',
|
||||
// 'no_available_budgets' => true,
|
||||
// 'currency_id' => (string) $currency->id,
|
||||
// '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),
|
||||
// 'local_icon' => 'money',
|
||||
// 'sub_title' => app('amount')->formatFlat(
|
||||
// $currency->symbol,
|
||||
// $currency->decimal_places,
|
||||
// '0',
|
||||
// false
|
||||
// ),
|
||||
// ];
|
||||
}
|
||||
|
||||
return $return;
|
||||
return array_values($return);
|
||||
}
|
||||
|
||||
private function getNetWorthInfo(Carbon $end): array
|
||||
|
||||
91
app/Api/V1/Requests/Chart/ChartRequest.php
Normal file
91
app/Api/V1/Requests/Chart/ChartRequest.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DashboardChartRequest.php
|
||||
* Copyright (c) 2023 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\Chart;
|
||||
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* Class ChartRequest
|
||||
*/
|
||||
class ChartRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
return [
|
||||
'start' => $this->convertDateTime('start')?->startOfDay(),
|
||||
'end' => $this->convertDateTime('end')?->endOfDay(),
|
||||
'preselected' => $this->convertString('preselected', 'empty'),
|
||||
'period' => $this->convertString('period', '1M'),
|
||||
'accounts' => $this->arrayFromValue($this->get('accounts')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before_or_equal:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after_or_equal:start',
|
||||
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
static function (Validator $validator): void {
|
||||
// validate transaction query data.
|
||||
$data = $validator->getData();
|
||||
if (!array_key_exists('accounts', $data)) {
|
||||
// $validator->errors()->add('accounts', trans('validation.filled', ['attribute' => 'accounts']));
|
||||
return;
|
||||
}
|
||||
if (!is_array($data['accounts'])) {
|
||||
$validator->errors()->add('accounts', trans('validation.filled', ['attribute' => 'accounts']));
|
||||
}
|
||||
}
|
||||
);
|
||||
if ($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
62
app/Api/V1/Requests/Generic/DateRequest.php
Normal file
62
app/Api/V1/Requests/Generic/DateRequest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DateRequest.php
|
||||
* Copyright (c) 2021 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\Generic;
|
||||
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Request class for end points that require date parameters.
|
||||
*
|
||||
* Class DateRequest
|
||||
*/
|
||||
class DateRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
|
||||
/**
|
||||
* Get all data from the request.
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return [
|
||||
'start' => $this->getCarbonDate('start')->startOfDay(),
|
||||
'end' => $this->getCarbonDate('end')->endOfDay(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
];
|
||||
}
|
||||
}
|
||||
59
app/Api/V1/Requests/Generic/SingleDateRequest.php
Normal file
59
app/Api/V1/Requests/Generic/SingleDateRequest.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DateRequest.php
|
||||
* Copyright (c) 2021 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\Generic;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Request class for end points that require a date parameter.
|
||||
*
|
||||
* Class SingleDateRequest
|
||||
*/
|
||||
class SingleDateRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
|
||||
/**
|
||||
* Get all data from the request.
|
||||
*/
|
||||
public function getDate(): Carbon
|
||||
{
|
||||
return $this->getCarbonDate('date');
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ use Carbon\Exceptions\InvalidDateException;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\AbstractTransformer;
|
||||
use FireflyIII\Transformers\AbstractTransformer;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
@@ -167,7 +167,7 @@ class Controller extends BaseController
|
||||
|
||||
// the transformer, at this point, needs to collect information that ALL items in the collection
|
||||
// require, like meta-data and stuff like that, and save it for later.
|
||||
$objects = $transformer->collectMetaData($objects);
|
||||
// $objects = $transformer->collectMetaData($objects);
|
||||
$paginator->setCollection($objects);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
@@ -188,7 +188,7 @@ class Controller extends BaseController
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$transformer->collectMetaData(new Collection([$object]));
|
||||
// $transformer->collectMetaData(new Collection([$object]));
|
||||
|
||||
$resource = new Item($object, $transformer, $key);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\IndexRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\UpdateRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UpdateController extends Controller
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Bill;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\BillTransformer;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\BillTransformer;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Budget;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\BudgetTransformer;
|
||||
use FireflyIII\Transformers\BudgetTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Generic\DateRequest;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\BudgetTransformer;
|
||||
use FireflyIII\Transformers\BudgetTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\BudgetLimit;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\BudgetLimitTransformer;
|
||||
use FireflyIII\Transformers\BudgetLimitTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Currency;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\CurrencyTransformer;
|
||||
use FireflyIII\Transformers\CurrencyTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\PiggyBank;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\PiggyBankTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Transaction;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ShowController extends Controller
|
||||
|
||||
@@ -32,7 +32,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Rules\IsDuplicateTransaction;
|
||||
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
@@ -31,7 +31,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\TransactionCurrency\IndexRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\CurrencyTransformer;
|
||||
use FireflyIII\Transformers\CurrencyTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\CurrencyTransformer;
|
||||
use FireflyIII\Transformers\CurrencyTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V2\Controllers\System;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Transformers\V2\PreferenceTransformer;
|
||||
use FireflyIII\Transformers\PreferenceTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,7 @@ use FireflyIII\Api\V2\Request\Model\Transaction\ListRequest;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\ListRequest;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\UserGroup;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\IndexRequest;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\UserGroupTransformer;
|
||||
use FireflyIII\Transformers\UserGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\UserGroup;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\UserGroupTransformer;
|
||||
use FireflyIII\Transformers\UserGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\UserGroup;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\UserGroup\StoreRequest;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\UserGroupTransformer;
|
||||
use FireflyIII\Transformers\UserGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,7 @@ use FireflyIII\Api\V2\Request\UserGroup\UpdateMembershipRequest;
|
||||
use FireflyIII\Api\V2\Request\UserGroup\UpdateRequest;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\UserGroupTransformer;
|
||||
use FireflyIII\Transformers\UserGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
|
||||
67
app/Console/Commands/System/RecalculatesRunningBalance.php
Normal file
67
app/Console/Commands/System/RecalculatesRunningBalance.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* RecalculatesRunningBalance.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/.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RecalculatesRunningBalance extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:refresh-running-balance {--F|force : Force the execution of this command.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Refreshes all running balances. May take a long time to run if forced.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (true === config('firefly.feature_flags.running_balance_column')) {
|
||||
$this->friendlyInfo('Will recalculate account balances. This may take a LONG time. Please be patient.');
|
||||
$this->correctBalanceAmounts($this->option('force'));
|
||||
$this->friendlyInfo('Done recalculating account balances.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->friendlyWarning('This command has been disabled.');
|
||||
}
|
||||
|
||||
private function correctBalanceAmounts(bool $forced): void
|
||||
{
|
||||
AccountBalanceCalculator::recalculateAll($forced);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Factory;
|
||||
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
@@ -237,20 +238,46 @@ class PiggyBankFactory
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @var array $info */
|
||||
foreach ($accounts as $info) {
|
||||
$account = $this->accountRepository->find((int) ($info['account_id'] ?? 0));
|
||||
if (null === $account) {
|
||||
Log::debug(sprintf('Account #%d not found, skipping.', (int) ($info['account_id'] ?? 0)));
|
||||
|
||||
continue;
|
||||
}
|
||||
if (array_key_exists('current_amount', $info) && null !== $info['current_amount']) {
|
||||
// an amount is set, first check out if there is a difference with the previous amount.
|
||||
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
|
||||
$diff = bcsub($info['current_amount'], $previous);
|
||||
|
||||
// create event for difference.
|
||||
if (0 !== bccomp($diff, '0')) {
|
||||
Log::debug(sprintf('[a] Will save event for difference %s (previous value was %s)', $diff, $previous));
|
||||
event(new ChangedAmount($piggyBank, $diff, null, null));
|
||||
}
|
||||
|
||||
$toBeLinked[$account->id] = ['current_amount' => $info['current_amount']];
|
||||
Log::debug(sprintf('[a] Will link account #%d with amount %s', $account->id, $info['current_amount']));
|
||||
}
|
||||
if (array_key_exists('current_amount', $info) && null === $info['current_amount']) {
|
||||
// an amount is set, first check out if there is a difference with the previous amount.
|
||||
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
|
||||
$diff = bcsub('0', $previous);
|
||||
|
||||
// create event for difference.
|
||||
if (0 !== bccomp($diff, '0')) {
|
||||
Log::debug(sprintf('[b] Will save event for difference %s (previous value was %s)', $diff, $previous));
|
||||
event(new ChangedAmount($piggyBank, $diff, null, null));
|
||||
}
|
||||
|
||||
// no amount set, use previous amount or go to ZERO.
|
||||
$toBeLinked[$account->id] = ['current_amount' => $toBeLinked[$account->id]['current_amount'] ?? '0'];
|
||||
Log::debug(sprintf('[b] Will link account #%d with amount %s', $account->id, $toBeLinked[$account->id]['current_amount'] ?? '0'));
|
||||
|
||||
// create event:
|
||||
Log::debug('linkToAccountIds: Trigger change for positive amount [b].');
|
||||
event(new ChangedAmount($piggyBank, $toBeLinked[$account->id]['current_amount'], null, null));
|
||||
}
|
||||
if (!array_key_exists('current_amount', $info)) {
|
||||
$toBeLinked[$account->id] ??= [];
|
||||
@@ -258,6 +285,11 @@ class PiggyBankFactory
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Link information: %s', json_encode($toBeLinked)));
|
||||
$piggyBank->accounts()->sync($toBeLinked);
|
||||
if (0 !== count($toBeLinked)) {
|
||||
$piggyBank->accounts()->sync($toBeLinked);
|
||||
}
|
||||
if (0 === count($toBeLinked)) {
|
||||
Log::warning('No accounts to link to piggy bank, will not change whatever is there now.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,13 @@ class PiggyBankEventObserver
|
||||
|
||||
private function updateNativeAmount(PiggyBankEvent $event): void
|
||||
{
|
||||
if (!Amount::convertToNative($event->piggyBank->accounts()->first()->user)) {
|
||||
$user = $event->piggyBank->accounts()->first()?->user;
|
||||
if (null === $user) {
|
||||
Log::warning('Piggy bank seems to have no accounts. Break.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (!Amount::convertToNative($user)) {
|
||||
return;
|
||||
}
|
||||
$userCurrency = app('amount')->getNativeCurrencyByUserGroup($event->piggyBank->accounts()->first()->user->userGroup);
|
||||
|
||||
@@ -55,6 +55,7 @@ trait CollectorProperties
|
||||
private HasMany $query;
|
||||
private ?int $startRow;
|
||||
private array $stringFields;
|
||||
private array $booleanFields;
|
||||
/*
|
||||
* This array is used to collect ALL tags the user may search for (using 'setTags').
|
||||
* This way the user can call 'setTags' multiple times and get a joined result.
|
||||
|
||||
@@ -82,6 +82,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$this->hasJoinedAttTables = false;
|
||||
$this->expandGroupSearch = false;
|
||||
$this->hasJoinedMetaTables = false;
|
||||
$this->booleanFields = ['balance_dirty'];
|
||||
$this->integerFields = [
|
||||
'transaction_group_id',
|
||||
'user_id',
|
||||
@@ -100,7 +101,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
'category_id',
|
||||
'budget_id',
|
||||
];
|
||||
$this->stringFields = ['amount', 'foreign_amount', 'native_amount', 'native_foreign_amount'];
|
||||
$this->stringFields = ['amount', 'foreign_amount', 'native_amount', 'native_foreign_amount', 'source_balance_after', 'destination_balance_after'];
|
||||
$this->total = 0;
|
||||
$this->fields = [
|
||||
// group
|
||||
@@ -131,6 +132,8 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
// currency info:
|
||||
'source.amount as amount',
|
||||
'source.balance_after as source_balance_after',
|
||||
'source.balance_dirty as balance_dirty',
|
||||
'source.native_amount as native_amount',
|
||||
'source.transaction_currency_id as currency_id',
|
||||
'currency.code as currency_code',
|
||||
@@ -149,6 +152,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
// destination account info (always present)
|
||||
'destination.account_id as destination_account_id',
|
||||
'destination.balance_after as destination_balance_after',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -596,6 +600,9 @@ class GroupCollector implements GroupCollectorInterface
|
||||
// convert values to integers:
|
||||
$result = $this->convertToInteger($result);
|
||||
|
||||
// convert to boolean
|
||||
$result = $this->convertToBoolean($result);
|
||||
|
||||
// convert back to strings because SQLite is dumb like that.
|
||||
$result = $this->convertToStrings($result);
|
||||
|
||||
@@ -653,6 +660,15 @@ class GroupCollector implements GroupCollectorInterface
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function convertToBoolean(array $array): array
|
||||
{
|
||||
foreach ($this->booleanFields as $field) {
|
||||
$array[$field] = array_key_exists($field, $array) ? (bool) $array[$field] : null;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function convertToStrings(array $array): array
|
||||
{
|
||||
foreach ($this->stringFields as $field) {
|
||||
|
||||
@@ -145,6 +145,7 @@ class ShowController extends Controller
|
||||
$collector->setExpandGroupSearch(true);
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
|
||||
|
||||
Log::debug('End collect transactions');
|
||||
Timer::stop('collection');
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ class IndexController extends Controller
|
||||
$array['end_date'] = $entry->end_date;
|
||||
|
||||
// spent in period:
|
||||
$spentArr = $this->opsRepository->sumExpenses($entry->start_date, $entry->end_date, null, null, $entry->transactionCurrency);
|
||||
$spentArr = $this->opsRepository->sumExpenses($entry->start_date, $entry->end_date, null, null, $entry->transactionCurrency, false);
|
||||
$array['spent'] = $spentArr[$entry->transaction_currency_id]['sum'] ?? '0';
|
||||
$array['native_spent'] = $this->convertToNative && $entry->transaction_currency_id !== $this->defaultCurrency->id ? $converter->convert($entry->transactionCurrency, $this->defaultCurrency, $entry->start_date, $array['spent']) : null;
|
||||
// budgeted in period:
|
||||
@@ -235,7 +235,7 @@ class IndexController extends Controller
|
||||
|
||||
/** @var TransactionCurrency $currency */
|
||||
foreach ($currencies as $currency) {
|
||||
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$current]), $currency);
|
||||
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$current]), $currency, false);
|
||||
if (array_key_exists($currency->id, $spentArr) && array_key_exists('sum', $spentArr[$currency->id])) {
|
||||
$array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum'];
|
||||
$array['spent'][$currency->id]['currency_id'] = $currency->id;
|
||||
|
||||
@@ -175,9 +175,10 @@ class ShowController extends Controller
|
||||
throw new FireflyException('This budget limit is not part of this budget.');
|
||||
}
|
||||
|
||||
$page = (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
$subTitle = trans(
|
||||
$currencySymbol = $budgetLimit->transactionCurrency->symbol;
|
||||
$page = (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
$subTitle = trans(
|
||||
'firefly.budget_in_period',
|
||||
[
|
||||
'name' => $budget->name,
|
||||
@@ -186,23 +187,26 @@ class ShowController extends Controller
|
||||
'currency' => $budgetLimit->transactionCurrency->name,
|
||||
]
|
||||
);
|
||||
if ($this->convertToNative) {
|
||||
$currencySymbol = $this->defaultCurrency->symbol;
|
||||
}
|
||||
|
||||
// collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->withAccountInformation()
|
||||
->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation()->withCategoryInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath(route('budgets.show.limit', [$budget->id, $budgetLimit->id]));
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = session('first', today(config('app.timezone'))->startOfYear());
|
||||
$end = today(config('app.timezone'));
|
||||
$attachments = $this->repository->getAttachments($budget);
|
||||
$limits = $this->getLimits($budget, $start, $end);
|
||||
$start = session('first', today(config('app.timezone'))->startOfYear());
|
||||
$end = today(config('app.timezone'));
|
||||
$attachments = $this->repository->getAttachments($budget);
|
||||
$limits = $this->getLimits($budget, $start, $end);
|
||||
|
||||
return view('budgets.show', compact('limits', 'attachments', 'budget', 'budgetLimit', 'groups', 'subTitle'));
|
||||
return view('budgets.show', compact('limits', 'attachments', 'budget', 'budgetLimit', 'groups', 'subTitle', 'currencySymbol'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,13 +82,16 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function expenseAccounts(): JsonResponse
|
||||
{
|
||||
Log::debug('RevenueAccounts');
|
||||
Log::debug('ExpenseAccounts');
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
@@ -97,7 +100,6 @@ class AccountController extends Controller
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
$start->subDay();
|
||||
|
||||
// prep some vars:
|
||||
$currencies = [];
|
||||
@@ -557,6 +559,10 @@ class AccountController extends Controller
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
@@ -565,7 +571,6 @@ class AccountController extends Controller
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
$start->subDay();
|
||||
|
||||
// prep some vars:
|
||||
$currencies = [];
|
||||
|
||||
@@ -179,7 +179,7 @@ class BillController extends Controller
|
||||
}
|
||||
$amount = bcmul($journal['amount'], '-1');
|
||||
if ($this->convertToNative && $currencyId !== $journal['currency_id']) {
|
||||
$amount = bcmul($journal['native_amount'], '-1');
|
||||
$amount = bcmul($journal['native_amount'] ?? '0', '-1');
|
||||
}
|
||||
if ($this->convertToNative && $currencyId === $journal['foreign_currency_id']) {
|
||||
$amount = bcmul($journal['foreign_amount'], '-1');
|
||||
|
||||
@@ -172,13 +172,13 @@ class BudgetController extends Controller
|
||||
$budgetCollection = new Collection([$budget]);
|
||||
$currency = $budgetLimit->transactionCurrency;
|
||||
if ($this->convertToNative) {
|
||||
$amount = $budgetLimit->native_amount ?? '0';
|
||||
$amount = $budgetLimit->native_amount ?? $amount;
|
||||
$currency = $this->defaultCurrency;
|
||||
}
|
||||
|
||||
while ($start <= $end) {
|
||||
$current = clone $start;
|
||||
$expenses = $this->opsRepository->sumExpenses($current, $current, null, $budgetCollection, $budgetLimit->transactionCurrency);
|
||||
$expenses = $this->opsRepository->sumExpenses($current, $current, null, $budgetCollection, $budgetLimit->transactionCurrency, $this->convertToNative);
|
||||
$spent = $expenses[$currency->id]['sum'] ?? '0';
|
||||
$amount = bcadd($amount, $spent);
|
||||
$format = $start->isoFormat((string) trans('config.month_and_day_js', [], $locale));
|
||||
@@ -191,6 +191,7 @@ class BudgetController extends Controller
|
||||
$data['datasets'][0]['currency_symbol'] = $currency->symbol;
|
||||
$data['datasets'][0]['currency_code'] = $currency->code;
|
||||
$cache->store($data);
|
||||
// var_dump($data);exit;
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
@@ -214,7 +215,7 @@ class BudgetController extends Controller
|
||||
if (null !== $budgetLimit) {
|
||||
$start = $budgetLimit->start_date;
|
||||
$end = $budgetLimit->end_date;
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setCurrency($budgetLimit->transactionCurrency);
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setNormalCurrency($budgetLimit->transactionCurrency);
|
||||
}
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
@@ -296,7 +297,7 @@ class BudgetController extends Controller
|
||||
if (null !== $budgetLimit) {
|
||||
$start = $budgetLimit->start_date;
|
||||
$end = $budgetLimit->end_date;
|
||||
$collector->setCurrency($budgetLimit->transactionCurrency);
|
||||
$collector->setNormalCurrency($budgetLimit->transactionCurrency);
|
||||
}
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
@@ -378,7 +379,7 @@ class BudgetController extends Controller
|
||||
if (null !== $budgetLimit) {
|
||||
$start = $budgetLimit->start_date;
|
||||
$end = $budgetLimit->end_date;
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setCurrency($budgetLimit->transactionCurrency);
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setNormalCurrency($budgetLimit->transactionCurrency);
|
||||
}
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
|
||||
@@ -32,6 +32,7 @@ use FireflyIII\Support\Export\ExportDataGenerator;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Response as LaravelResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
@@ -71,6 +72,7 @@ class IndexController extends Controller
|
||||
|
||||
return redirect(route('export.index'));
|
||||
}
|
||||
Log::debug('Will export from the UI.');
|
||||
|
||||
/** @var ExportDataGenerator $generator */
|
||||
$generator = app(ExportDataGenerator::class);
|
||||
@@ -83,6 +85,7 @@ class IndexController extends Controller
|
||||
$firstDate->subYear();
|
||||
$journal = $this->journalRepository->firstNull();
|
||||
if (null !== $journal) {
|
||||
Log::debug('First journal is NULL, using today() - 1 year.');
|
||||
$firstDate = clone $journal->date;
|
||||
}
|
||||
$generator->setStart($firstDate);
|
||||
|
||||
@@ -78,13 +78,14 @@ class EditController extends Controller
|
||||
$startDate = $piggyBank->start_date?->format('Y-m-d');
|
||||
|
||||
$preFilled = [
|
||||
'name' => $piggyBank->name,
|
||||
'target_amount' => app('steam')->bcround($piggyBank->target_amount, $piggyBank->transactionCurrency->decimal_places),
|
||||
'target_date' => $targetDate,
|
||||
'start_date' => $startDate,
|
||||
'accounts' => [],
|
||||
'object_group' => null !== $piggyBank->objectGroups->first() ? $piggyBank->objectGroups->first()->title : '',
|
||||
'notes' => null === $note ? '' : $note->text,
|
||||
'name' => $piggyBank->name,
|
||||
'transaction_currency_id' => (int) $piggyBank->transaction_currency_id,
|
||||
'target_amount' => app('steam')->bcround($piggyBank->target_amount, $piggyBank->transactionCurrency->decimal_places),
|
||||
'target_date' => $targetDate,
|
||||
'start_date' => $startDate,
|
||||
'accounts' => [],
|
||||
'object_group' => null !== $piggyBank->objectGroups->first() ? $piggyBank->objectGroups->first()->title : '',
|
||||
'notes' => null === $note ? '' : $note->text,
|
||||
];
|
||||
foreach ($piggyBank->accounts as $account) {
|
||||
$preFilled['accounts'][] = $account->id;
|
||||
|
||||
@@ -135,7 +135,7 @@ class EditController extends Controller
|
||||
|
||||
// get rule trigger for update / store-journal:
|
||||
$primaryTrigger = $this->ruleRepos->getPrimaryTrigger($rule);
|
||||
$subTitle = (string) trans('firefly.edit_rule', ['title' => $rule->title]);
|
||||
$subTitle = (string) trans('firefly.edit_rule', ['nr' => $rule->order, 'title' => $rule->title]);
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (true !== session('rules.edit.fromUpdate')) {
|
||||
|
||||
@@ -32,11 +32,11 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
// After...
|
||||
protected $headers
|
||||
= Request::HEADER_X_FORWARDED_FOR
|
||||
protected $headers = Request::HEADER_X_FORWARDED_FOR
|
||||
| Request::HEADER_X_FORWARDED_HOST
|
||||
| Request::HEADER_X_FORWARDED_PORT
|
||||
| Request::HEADER_X_FORWARDED_PROTO
|
||||
| Request::HEADER_X_FORWARDED_PREFIX
|
||||
| Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,12 +49,13 @@ class PiggyBankUpdateRequest extends FormRequest
|
||||
{
|
||||
$accounts = $this->get('accounts');
|
||||
$data = [
|
||||
'name' => $this->convertString('name'),
|
||||
'start_date' => $this->getCarbonDate('start_date'),
|
||||
'target_amount' => trim($this->convertString('target_amount')),
|
||||
'target_date' => $this->getCarbonDate('target_date'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
'object_group_title' => $this->convertString('object_group'),
|
||||
'name' => $this->convertString('name'),
|
||||
'start_date' => $this->getCarbonDate('start_date'),
|
||||
'target_amount' => trim($this->convertString('target_amount')),
|
||||
'target_date' => $this->getCarbonDate('target_date'),
|
||||
'transaction_currency_id' => $this->convertInteger('transaction_currency_id'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
'object_group_title' => $this->convertString('object_group'),
|
||||
];
|
||||
if (!is_array($accounts)) {
|
||||
$accounts = [];
|
||||
@@ -75,15 +76,16 @@ class PiggyBankUpdateRequest extends FormRequest
|
||||
$piggy = $this->route()->parameter('piggyBank');
|
||||
|
||||
return [
|
||||
'name' => sprintf('required|min:1|max:255|uniquePiggyBankForUser:%d', $piggy->id),
|
||||
'accounts' => 'required|array',
|
||||
'accounts.*' => 'required|belongsToUser:accounts',
|
||||
'target_amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'start_date' => 'date',
|
||||
'target_date' => 'date|nullable',
|
||||
'order' => 'integer|max:32768|min:1',
|
||||
'object_group' => 'min:0|max:255',
|
||||
'notes' => 'min:1|max:32768|nullable',
|
||||
'name' => sprintf('required|min:1|max:255|uniquePiggyBankForUser:%d', $piggy->id),
|
||||
'accounts' => 'required|array',
|
||||
'accounts.*' => 'required|belongsToUser:accounts',
|
||||
'target_amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'start_date' => 'date',
|
||||
'transaction_currency_id' => 'exists:transaction_currencies,id',
|
||||
'target_date' => 'date|nullable',
|
||||
'order' => 'integer|max:32768|min:1',
|
||||
'object_group' => 'min:0|max:255',
|
||||
'notes' => 'min:1|max:32768|nullable',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace FireflyIII\Notifications\Admin;
|
||||
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
|
||||
use FireflyIII\Notifications\ReturnsAvailableChannels;
|
||||
use FireflyIII\Notifications\ReturnsSettings;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@@ -79,9 +80,10 @@ class UnknownUserLoginAttempt extends Notification
|
||||
{
|
||||
$settings = ReturnsSettings::getSettings('ntfy', 'owner', null);
|
||||
$message = new Message();
|
||||
$ip = Request::ip();
|
||||
$message->topic($settings['ntfy_topic']);
|
||||
$message->title((string) trans('email.unknown_user_subject'));
|
||||
$message->body((string) trans('email.unknown_user_message', ['address' => $this->address]));
|
||||
$message->body((string) trans('email.unknown_user_message', ['address' => $this->address, 'ip' => $ip]));
|
||||
|
||||
return $message;
|
||||
}
|
||||
@@ -91,7 +93,9 @@ class UnknownUserLoginAttempt extends Notification
|
||||
*/
|
||||
public function toPushover(OwnerNotifiable $notifiable): PushoverMessage
|
||||
{
|
||||
return PushoverMessage::create((string) trans('email.unknown_user_message', ['address' => $this->address]))
|
||||
$ip = Request::ip();
|
||||
|
||||
return PushoverMessage::create((string) trans('email.unknown_user_message', ['address' => $this->address, 'ip' => $ip]))
|
||||
->title((string) trans('email.unknown_user_subject'))
|
||||
;
|
||||
}
|
||||
@@ -101,8 +105,10 @@ class UnknownUserLoginAttempt extends Notification
|
||||
*/
|
||||
public function toSlack(OwnerNotifiable $notifiable): SlackMessage
|
||||
{
|
||||
$ip = Request::ip();
|
||||
|
||||
return new SlackMessage()->content(
|
||||
(string) trans('email.unknown_user_body', ['address' => $this->address])
|
||||
(string) trans('email.unknown_user_body', ['address' => $this->address, 'ip' => $ip])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,6 +117,12 @@ class UnknownUserLoginAttempt extends Notification
|
||||
*/
|
||||
public function via(OwnerNotifiable $notifiable): array
|
||||
{
|
||||
return ReturnsAvailableChannels::returnChannels('owner');
|
||||
$channels = ReturnsAvailableChannels::returnChannels('owner');
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', false)->data;
|
||||
if (true === $isDemoSite) {
|
||||
return array_diff($channels, ['mail']);
|
||||
}
|
||||
|
||||
return $channels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Notifications\Security;
|
||||
|
||||
use FireflyIII\Notifications\ReturnsAvailableChannels;
|
||||
use FireflyIII\Notifications\ReturnsSettings;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@@ -103,6 +104,12 @@ class UserFailedLoginAttempt extends Notification
|
||||
*/
|
||||
public function via(User $notifiable): array
|
||||
{
|
||||
return ReturnsAvailableChannels::returnChannels('user', $notifiable);
|
||||
$channels = ReturnsAvailableChannels::returnChannels('user', $notifiable);
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', false)->data;
|
||||
if (true === $isDemoSite) {
|
||||
return array_diff($channels, ['mail']);
|
||||
}
|
||||
|
||||
return $channels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Notifications\Test;
|
||||
|
||||
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
@@ -63,6 +64,11 @@ class OwnerTestNotificationEmail extends Notification
|
||||
*/
|
||||
public function via(OwnerNotifiable $notifiable): array
|
||||
{
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', false)->data;
|
||||
if (true === $isDemoSite) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ['mail'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Notifications\Test;
|
||||
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@@ -60,6 +61,11 @@ class UserTestNotificationEmail extends Notification
|
||||
*/
|
||||
public function via(User $notifiable): array
|
||||
{
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', false)->data;
|
||||
if (true === $isDemoSite) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ['mail'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Notifications\User;
|
||||
|
||||
use FireflyIII\Notifications\ReturnsAvailableChannels;
|
||||
use FireflyIII\Notifications\ReturnsSettings;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@@ -101,6 +102,12 @@ class NewAccessToken extends Notification
|
||||
*/
|
||||
public function via(User $notifiable): array
|
||||
{
|
||||
return ReturnsAvailableChannels::returnChannels('user', $notifiable);
|
||||
$channels = ReturnsAvailableChannels::returnChannels('user', $notifiable);
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', false)->data;
|
||||
if (true === $isDemoSite) {
|
||||
return array_diff($channels, ['mail']);
|
||||
}
|
||||
|
||||
return $channels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ use FireflyIII\Repositories\Currency\CurrencyRepository;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepository as GroupCurrencyRepository;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface as GroupCurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepository;
|
||||
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
@@ -72,5 +74,19 @@ class CurrencyServiceProvider extends ServiceProvider
|
||||
}
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
ExchangeRateRepositoryInterface::class,
|
||||
static function (Application $app) {
|
||||
/** @var ExchangeRateRepository $repository */
|
||||
$repository = app(ExchangeRateRepository::class);
|
||||
// phpstan does not get the reference to auth
|
||||
if ($app->auth->check()) { // @phpstan-ignore-line
|
||||
$repository->setUserGroup(auth()->user()->userGroup);
|
||||
}
|
||||
|
||||
return $repository;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ use FireflyIII\TransactionRules\Engine\SearchRuleEngine;
|
||||
use FireflyIII\TransactionRules\Expressions\ActionExpressionLanguageProvider;
|
||||
use FireflyIII\Validation\FireflyValidator;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
@@ -88,7 +89,7 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
\Validator::resolver(
|
||||
Validator::resolver(
|
||||
static function ($translator, $data, $rules, $messages) {
|
||||
return new FireflyValidator($translator, $data, $rules, $messages);
|
||||
}
|
||||
|
||||
@@ -676,11 +676,12 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
|
||||
'foreign_currencies.code as foreign_currency_code',
|
||||
'foreign_currencies.name as foreign_currency_name',
|
||||
'foreign_currencies.symbol as foreign_currency_symbol',
|
||||
'foreign_currencies.decimal_places as foreign_decimal_places',
|
||||
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
|
||||
|
||||
// fields
|
||||
'transaction_journals.date', 'transaction_types.type', 'transaction_journals.transaction_currency_id', 'transactions.amount'])
|
||||
->toArray();
|
||||
->toArray()
|
||||
;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,8 +528,8 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
|
||||
foreach ($bills as $bill) {
|
||||
|
||||
/** @var Collection $set */
|
||||
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
|
||||
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
|
||||
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
|
||||
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
|
||||
$return[(int) $currency->id] ??= [
|
||||
'id' => (string) $currency->id,
|
||||
'name' => $currency->name,
|
||||
@@ -538,18 +538,39 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
|
||||
'decimal_places' => $currency->decimal_places,
|
||||
'sum' => '0',
|
||||
];
|
||||
$setAmount = '0';
|
||||
$setAmount = '0';
|
||||
|
||||
/** @var TransactionJournal $transactionJournal */
|
||||
foreach ($set as $transactionJournal) {
|
||||
$setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal));
|
||||
// grab currency from transaction.
|
||||
$transactionCurrency = $transactionJournal->transactionCurrency;
|
||||
$return[(int) $transactionCurrency->id] ??= [
|
||||
'id' => (string) $transactionCurrency->id,
|
||||
'name' => $transactionCurrency->name,
|
||||
'symbol' => $transactionCurrency->symbol,
|
||||
'code' => $transactionCurrency->code,
|
||||
'decimal_places' => $transactionCurrency->decimal_places,
|
||||
'sum' => '0',
|
||||
];
|
||||
|
||||
// get currency from transaction as well.
|
||||
$return[(int) $transactionCurrency->id]['sum'] = bcadd($return[(int) $transactionCurrency->id]['sum'], Amount::getAmountFromJournalObject($transactionJournal));
|
||||
// $setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal));
|
||||
}
|
||||
// Log::debug(sprintf('Bill #%d ("%s") with %d transaction(s) and sum %s %s', $bill->id, $bill->name, $set->count(), $currency->code, $setAmount));
|
||||
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $setAmount);
|
||||
// $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $setAmount);
|
||||
// Log::debug(sprintf('Total sum is now %s', $return[$currency->id]['sum']));
|
||||
}
|
||||
// remove empty sets
|
||||
$final = [];
|
||||
foreach ($return as $entry) {
|
||||
if (0 === bccomp($entry['sum'], '0')) {
|
||||
continue;
|
||||
}
|
||||
$final[] = $entry;
|
||||
}
|
||||
|
||||
return $return;
|
||||
return $final;
|
||||
}
|
||||
|
||||
public function getActiveBills(): Collection
|
||||
|
||||
@@ -138,7 +138,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
|
||||
->where('end_date', $end->format('Y-m-d'))->get()
|
||||
;
|
||||
|
||||
Log::debug(sprintf('Found %d available budgets', $availableBudgets->count()));
|
||||
Log::debug(sprintf('Found %d available budgets (already converted)', $availableBudgets->count()));
|
||||
|
||||
// use native amount if necessary?
|
||||
$convertToNative = Amount::convertToNative($this->user);
|
||||
@@ -149,8 +149,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
|
||||
$currencyId = $convertToNative && $availableBudget->transaction_currency_id !== $default->id ? $default->id : $availableBudget->transaction_currency_id;
|
||||
$field = $convertToNative && $availableBudget->transaction_currency_id !== $default->id ? 'native_amount' : 'amount';
|
||||
$return[$currencyId] ??= '0';
|
||||
$return[$currencyId] = bcadd($return[$currencyId], $availableBudget->{$field});
|
||||
Log::debug(sprintf('Add #%d %s (%s) for a total of %s', $currencyId, $availableBudget->{$field}, $field, $return[$currencyId]));
|
||||
$amount = '' === (string) $availableBudget->{$field} ? '0' : (string) $availableBudget->{$field};
|
||||
$return[$currencyId] = bcadd($return[$currencyId], $amount);
|
||||
Log::debug(sprintf('Add #%d %s (%s) for a total of %s', $currencyId, $amount, $field, $return[$currencyId]));
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
||||
@@ -201,9 +201,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $budgets = null,
|
||||
?TransactionCurrency $currency = null
|
||||
?TransactionCurrency $currency = null,
|
||||
bool $convertToNative = false
|
||||
): array {
|
||||
Log::debug(sprintf('Start of %s.', __METHOD__));
|
||||
Log::debug(sprintf('Start of %s(date, date, array, array, "%s", "%s").', __METHOD__, $currency?->code, var_export($convertToNative, true)));
|
||||
// this collector excludes all transfers TO liabilities (which are also withdrawals)
|
||||
// because those expenses only become expenses once they move from the liability to the friend.
|
||||
// 2024-12-24 disable the exclusion for now.
|
||||
@@ -235,10 +236,12 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$budgets = $this->getBudgets();
|
||||
}
|
||||
if (null !== $currency) {
|
||||
Log::debug(sprintf('Limit to currency %s', $currency->code));
|
||||
$collector->setCurrency($currency);
|
||||
Log::debug(sprintf('Limit to normal currency %s', $currency->code));
|
||||
$collector->setNormalCurrency($currency);
|
||||
}
|
||||
if ($budgets->count() > 0) {
|
||||
$collector->setBudgets($budgets);
|
||||
}
|
||||
$collector->setBudgets($budgets);
|
||||
$journals = $collector->getExtractedJournals();
|
||||
|
||||
// same but for transactions in the foreign currency:
|
||||
@@ -246,7 +249,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
Log::debug('STOP looking for transactions in the foreign currency.');
|
||||
}
|
||||
$summarizer = new TransactionSummarizer($this->user);
|
||||
// 2025-04-21 overrule "convertToNative" because in this particular view, we never want to do this.
|
||||
$summarizer->setConvertToNative($convertToNative);
|
||||
|
||||
return $summarizer->groupByCurrencyId($journals);
|
||||
return $summarizer->groupByCurrencyId($journals, 'negative', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +77,9 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][0]['transaction_journals'][$journalId]
|
||||
= [
|
||||
'amount' => app('steam')->negative($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
'amount' => app('steam')->negative($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
}
|
||||
|
||||
return $array;
|
||||
@@ -123,9 +123,9 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][0]['transaction_journals'][$journalId]
|
||||
= [
|
||||
'amount' => app('steam')->positive($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
'amount' => app('steam')->positive($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
}
|
||||
|
||||
return $array;
|
||||
|
||||
@@ -55,6 +55,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
if (null !== $accounts && $accounts->count() > 0) {
|
||||
$collector->setAccounts($accounts);
|
||||
$collector->excludeDestinationAccounts($accounts); // to exclude withdrawals to liabilities.
|
||||
}
|
||||
if (null !== $categories && $categories->count() > 0) {
|
||||
$collector->setCategories($categories);
|
||||
@@ -132,6 +133,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::DEPOSIT->value]);
|
||||
if (null !== $accounts && $accounts->count() > 0) {
|
||||
$collector->setAccounts($accounts);
|
||||
$collector->excludeSourceAccounts($accounts); // to prevent income from liabilities.
|
||||
}
|
||||
if (null !== $categories && $categories->count() > 0) {
|
||||
$collector->setCategories($categories);
|
||||
|
||||
@@ -31,6 +31,7 @@ use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
@@ -223,6 +224,8 @@ trait ModifiesPiggyBanks
|
||||
$factory = new PiggyBankFactory();
|
||||
$factory->user = $this->user;
|
||||
|
||||
// the piggy bank currency is set or updated FIRST, if it exists.
|
||||
|
||||
$factory->linkToAccountIds($piggyBank, $data['accounts'] ?? []);
|
||||
|
||||
|
||||
@@ -280,6 +283,13 @@ trait ModifiesPiggyBanks
|
||||
if (array_key_exists('name', $data) && '' !== $data['name']) {
|
||||
$piggyBank->name = $data['name'];
|
||||
}
|
||||
if (array_key_exists('transaction_currency_id', $data) && is_int($data['transaction_currency_id'])) {
|
||||
$currency = TransactionCurrency::find($data['transaction_currency_id']);
|
||||
if (null !== $currency) {
|
||||
$piggyBank->transaction_currency_id = $currency->id;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('target_amount', $data) && '' !== $data['target_amount']) {
|
||||
$piggyBank->target_amount = $data['target_amount'];
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Rules\UniqueIban;
|
||||
use FireflyIII\Support\NullArrayObject;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Trait JournalServiceTrait
|
||||
@@ -53,7 +55,7 @@ trait JournalServiceTrait
|
||||
protected function getAccount(string $transactionType, string $direction, array $data): ?Account
|
||||
{
|
||||
// some debug logging:
|
||||
app('log')->debug(sprintf('Now in getAccount(%s)', $direction), $data);
|
||||
Log::debug(sprintf('Now in getAccount(%s)', $direction), $data);
|
||||
|
||||
// expected type of source account, in order of preference
|
||||
/** @var array $array */
|
||||
@@ -63,7 +65,7 @@ trait JournalServiceTrait
|
||||
|
||||
// and now try to find it, based on the type of transaction.
|
||||
$message = 'Transaction = %s, %s account should be in: %s. Direction is %s.';
|
||||
app('log')->debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType] ?? ['UNKNOWN']), $direction));
|
||||
Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType] ?? ['UNKNOWN']), $direction));
|
||||
|
||||
$result = $this->findAccountById($data, $expectedTypes[$transactionType]);
|
||||
$result = $this->findAccountByIban($result, $data, $expectedTypes[$transactionType]);
|
||||
@@ -77,7 +79,7 @@ trait JournalServiceTrait
|
||||
// this account. In such a case, the name search must be retried with a new name.
|
||||
if (null !== $nameResult && null === $numberResult && null === $ibanResult && '' !== (string) $data['iban'] && '' !== (string) $nameResult->iban) {
|
||||
$data['name'] = sprintf('%s (%s)', $data['name'], $data['iban']);
|
||||
app('log')->debug(sprintf('Search again using the new name, "%s".', $data['name']));
|
||||
Log::debug(sprintf('Search again using the new name, "%s".', $data['name']));
|
||||
$result = $this->findAccountByName(null, $data, $expectedTypes[$transactionType]);
|
||||
}
|
||||
|
||||
@@ -87,7 +89,7 @@ trait JournalServiceTrait
|
||||
// if the result is NULL but the ID is set, an account could exist of the wrong type.
|
||||
// that data can be used to create a new account of the right type.
|
||||
if (null === $result && null !== $data['id'] && null !== $creatableType) {
|
||||
app('log')->debug(sprintf('Account #%d may exist and be of the wrong type, use data to create one of the right type.', $data['id']));
|
||||
Log::debug(sprintf('Account #%d may exist and be of the wrong type, use data to create one of the right type.', $data['id']));
|
||||
$temp = $this->findAccountById(['id' => $data['id']], []);
|
||||
if (null !== $temp) {
|
||||
$tempData = [
|
||||
@@ -100,11 +102,11 @@ trait JournalServiceTrait
|
||||
}
|
||||
}
|
||||
if (null === $result && null !== $creatableType) {
|
||||
app('log')->debug('If nothing is found, create it.');
|
||||
Log::debug('If nothing is found, create it.');
|
||||
$result = $this->createAccount($result, $data, $creatableType);
|
||||
}
|
||||
if (null === $result) {
|
||||
app('log')->debug('If cant be created, return cash account.');
|
||||
Log::debug('If cant be created, return cash account.');
|
||||
$result = $this->getCashAccount($result, $data, $expectedTypes[$transactionType]);
|
||||
}
|
||||
|
||||
@@ -117,21 +119,21 @@ trait JournalServiceTrait
|
||||
if (null !== $data['id']) {
|
||||
$search = $this->accountRepository->find((int) $data['id']);
|
||||
if (null !== $search && in_array($search->accountType->type, $types, true)) {
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf('Found "account_id" object: #%d, "%s" of type %s (1)', $search->id, $search->name, $search->accountType->type)
|
||||
);
|
||||
|
||||
return $search;
|
||||
}
|
||||
if (null !== $search && 0 === count($types)) {
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf('Found "account_id" object: #%d, "%s" of type %s (2)', $search->id, $search->name, $search->accountType->type)
|
||||
);
|
||||
|
||||
return $search;
|
||||
}
|
||||
}
|
||||
app('log')->debug(sprintf('Found no account by ID #%d of types', $data['id']), $types);
|
||||
Log::debug(sprintf('Found no account by ID #%d of types', $data['id']), $types);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -139,12 +141,12 @@ trait JournalServiceTrait
|
||||
private function findAccountByIban(?Account $account, array $data, array $types): ?Account
|
||||
{
|
||||
if (null !== $account) {
|
||||
app('log')->debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name));
|
||||
Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name));
|
||||
|
||||
return $account;
|
||||
}
|
||||
if (null === $data['iban'] || '' === $data['iban']) {
|
||||
app('log')->debug('IBAN is empty, will not search for IBAN.');
|
||||
Log::debug('IBAN is empty, will not search for IBAN.');
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -154,11 +156,11 @@ trait JournalServiceTrait
|
||||
$source ??= $this->accountRepository->findByIbanNull($data['iban'], $types);
|
||||
|
||||
if (null !== $source) {
|
||||
app('log')->debug(sprintf('Found "account_iban" object: #%d, %s', $source->id, $source->name));
|
||||
Log::debug(sprintf('Found "account_iban" object: #%d, %s', $source->id, $source->name));
|
||||
|
||||
return $source;
|
||||
}
|
||||
app('log')->debug(sprintf('Found no account with IBAN "%s" of expected types', $data['iban']), $types);
|
||||
Log::debug(sprintf('Found no account with IBAN "%s" of expected types', $data['iban']), $types);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -166,12 +168,12 @@ trait JournalServiceTrait
|
||||
private function findAccountByNumber(?Account $account, array $data, array $types): ?Account
|
||||
{
|
||||
if (null !== $account) {
|
||||
app('log')->debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name));
|
||||
Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name));
|
||||
|
||||
return $account;
|
||||
}
|
||||
if (null === $data['number'] || '' === $data['number']) {
|
||||
app('log')->debug('Account number is empty, will not search for account number.');
|
||||
Log::debug('Account number is empty, will not search for account number.');
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -182,12 +184,12 @@ trait JournalServiceTrait
|
||||
$source ??= $this->accountRepository->findByAccountNumber((string) $data['number'], $types);
|
||||
|
||||
if (null !== $source) {
|
||||
app('log')->debug(sprintf('Found account: #%d, %s', $source->id, $source->name));
|
||||
Log::debug(sprintf('Found account: #%d, %s', $source->id, $source->name));
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Found no account with account number "%s" of expected types', $data['number']), $types);
|
||||
Log::debug(sprintf('Found no account with account number "%s" of expected types', $data['number']), $types);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -195,12 +197,12 @@ trait JournalServiceTrait
|
||||
private function findAccountByName(?Account $account, array $data, array $types): ?Account
|
||||
{
|
||||
if (null !== $account) {
|
||||
app('log')->debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name));
|
||||
Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name));
|
||||
|
||||
return $account;
|
||||
}
|
||||
if (null === $data['name'] || '' === $data['name']) {
|
||||
app('log')->debug('Account name is empty, will not search for account name.');
|
||||
Log::debug('Account name is empty, will not search for account name.');
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -212,11 +214,11 @@ trait JournalServiceTrait
|
||||
$source ??= $this->accountRepository->findByName($data['name'], $types);
|
||||
|
||||
if (null !== $source) {
|
||||
app('log')->debug(sprintf('Found "account_name" object: #%d, %s', $source->id, $source->name));
|
||||
Log::debug(sprintf('Found "account_name" object: #%d, %s', $source->id, $source->name));
|
||||
|
||||
return $source;
|
||||
}
|
||||
app('log')->debug(sprintf('Found no account with account name "%s" of expected types', $data['name']), $types);
|
||||
Log::debug(sprintf('Found no account with account name "%s" of expected types', $data['name']), $types);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -243,10 +245,10 @@ trait JournalServiceTrait
|
||||
*/
|
||||
private function createAccount(?Account $account, array $data, string $preferredType): ?Account
|
||||
{
|
||||
app('log')->debug('Now in createAccount()', $data);
|
||||
Log::debug('Now in createAccount()', $data);
|
||||
// return new account.
|
||||
if (null !== $account) {
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Was given %s account #%d ("%s") so will simply return that.',
|
||||
$account->accountType->type,
|
||||
@@ -262,23 +264,32 @@ trait JournalServiceTrait
|
||||
}
|
||||
// fix name of account if only IBAN is given:
|
||||
if ('' === (string) $data['name'] && '' !== (string) $data['iban']) {
|
||||
app('log')->debug(sprintf('Account name is now IBAN ("%s")', $data['iban']));
|
||||
Log::debug(sprintf('Account name is now IBAN ("%s")', $data['iban']));
|
||||
$data['name'] = $data['iban'];
|
||||
}
|
||||
// fix name of account if only number is given:
|
||||
if ('' === (string) $data['name'] && '' !== (string) $data['number']) {
|
||||
app('log')->debug(sprintf('Account name is now account number ("%s")', $data['number']));
|
||||
Log::debug(sprintf('Account name is now account number ("%s")', $data['number']));
|
||||
$data['name'] = $data['number'];
|
||||
}
|
||||
// if name is still NULL, return NULL.
|
||||
if ('' === (string) $data['name']) {
|
||||
app('log')->debug('Account name is still NULL, return NULL.');
|
||||
Log::debug('Account name is still NULL, return NULL.');
|
||||
|
||||
return null;
|
||||
}
|
||||
// 2025-04-19 sanity check on IBAN.
|
||||
$validator = new UniqueIban(null, $preferredType);
|
||||
if ('' !== (string) $data['iban'] && !$validator->passes('iban', $data['iban'])) {
|
||||
Log::warning(sprintf('IBAN "%s" is already in use, quietly ignore it.', $data['iban']));
|
||||
$data['iban'] = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// $data['name'] = $data['name'] ?? '(no name)';
|
||||
|
||||
$account = $this->accountRepository->store(
|
||||
$account = $this->accountRepository->store(
|
||||
[
|
||||
'account_type_id' => null,
|
||||
'account_type_name' => $preferredType,
|
||||
@@ -314,7 +325,7 @@ trait JournalServiceTrait
|
||||
&& in_array(AccountTypeEnum::CASH->value, $types, true)) {
|
||||
$account = $this->accountRepository->getCashAccount();
|
||||
}
|
||||
app('log')->debug('Cannot return cash account, return input instead.');
|
||||
Log::debug('Cannot return cash account, return input instead.');
|
||||
|
||||
return $account;
|
||||
}
|
||||
@@ -327,7 +338,7 @@ trait JournalServiceTrait
|
||||
if ('' === $amount) {
|
||||
throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount));
|
||||
}
|
||||
app('log')->debug(sprintf('Now in getAmount("%s")', $amount));
|
||||
Log::debug(sprintf('Now in getAmount("%s")', $amount));
|
||||
if (0 === bccomp('0', $amount)) {
|
||||
throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount));
|
||||
}
|
||||
@@ -338,21 +349,21 @@ trait JournalServiceTrait
|
||||
protected function getForeignAmount(?string $amount): ?string
|
||||
{
|
||||
if (null === $amount) {
|
||||
app('log')->debug('No foreign amount info in array. Return NULL');
|
||||
Log::debug('No foreign amount info in array. Return NULL');
|
||||
|
||||
return null;
|
||||
}
|
||||
if ('' === $amount) {
|
||||
app('log')->debug('Foreign amount is empty string, return NULL.');
|
||||
Log::debug('Foreign amount is empty string, return NULL.');
|
||||
|
||||
return null;
|
||||
}
|
||||
if (0 === bccomp('0', $amount)) {
|
||||
app('log')->debug('Foreign amount is 0.0, return NULL.');
|
||||
Log::debug('Foreign amount is 0.0, return NULL.');
|
||||
|
||||
return null;
|
||||
}
|
||||
app('log')->debug(sprintf('Foreign amount is %s', $amount));
|
||||
Log::debug(sprintf('Foreign amount is %s', $amount));
|
||||
|
||||
return $amount;
|
||||
}
|
||||
@@ -366,7 +377,7 @@ trait JournalServiceTrait
|
||||
}
|
||||
$budget = $this->budgetRepository->findBudget($data['budget_id'], $data['budget_name']);
|
||||
if (null !== $budget) {
|
||||
app('log')->debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id));
|
||||
Log::debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id));
|
||||
$journal->budgets()->sync([$budget->id]);
|
||||
|
||||
return;
|
||||
@@ -379,7 +390,7 @@ trait JournalServiceTrait
|
||||
{
|
||||
$category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']);
|
||||
if (null !== $category) {
|
||||
app('log')->debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id));
|
||||
Log::debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id));
|
||||
$journal->categories()->sync([$category->id]);
|
||||
|
||||
return;
|
||||
@@ -399,7 +410,7 @@ trait JournalServiceTrait
|
||||
}
|
||||
$note->text = $notes;
|
||||
$note->save();
|
||||
app('log')->debug(sprintf('Stored notes for journal #%d', $journal->id));
|
||||
Log::debug(sprintf('Stored notes for journal #%d', $journal->id));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -412,18 +423,18 @@ trait JournalServiceTrait
|
||||
*/
|
||||
protected function storeTags(TransactionJournal $journal, ?array $tags): void
|
||||
{
|
||||
app('log')->debug('Now in storeTags()', $tags ?? []);
|
||||
Log::debug('Now in storeTags()', $tags ?? []);
|
||||
$this->tagFactory->setUser($journal->user);
|
||||
$set = [];
|
||||
if (!is_array($tags)) {
|
||||
app('log')->debug('Tags is not an array, break.');
|
||||
Log::debug('Tags is not an array, break.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->debug('Start of loop.');
|
||||
Log::debug('Start of loop.');
|
||||
foreach ($tags as $string) {
|
||||
$string = (string) $string;
|
||||
app('log')->debug(sprintf('Now at tag "%s"', $string));
|
||||
Log::debug(sprintf('Now at tag "%s"', $string));
|
||||
if ('' !== $string) {
|
||||
$tag = $this->tagFactory->findOrCreate($string);
|
||||
if (null !== $tag) {
|
||||
@@ -432,8 +443,8 @@ trait JournalServiceTrait
|
||||
}
|
||||
}
|
||||
$set = array_unique($set);
|
||||
app('log')->debug('End of loop.');
|
||||
app('log')->debug(sprintf('Total nr. of tags: %d', count($tags)), $tags);
|
||||
Log::debug('End of loop.');
|
||||
Log::debug(sprintf('Total nr. of tags: %d', count($tags)), $tags);
|
||||
$journal->tags()->sync($set);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class ChartData
|
||||
if (array_key_exists('native_currency_id', $data)) {
|
||||
$data['native_currency_id'] = (string) $data['native_currency_id'];
|
||||
}
|
||||
$required = ['start', 'date', 'end', 'entries', 'native_entries'];
|
||||
$required = ['start', 'date', 'end', 'entries'];
|
||||
foreach ($required as $field) {
|
||||
if (!array_key_exists($field, $data)) {
|
||||
throw new FireflyException(sprintf('Data-set is missing the "%s"-variable.', $field));
|
||||
|
||||
@@ -45,8 +45,8 @@ class UpdateCheckCronjob extends AbstractCronjob
|
||||
Log::debug('Update check is not enabled.');
|
||||
// get stuff from job:
|
||||
$this->jobFired = false;
|
||||
$this->jobErrored = true;
|
||||
$this->jobSucceeded = false;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'The update check is not enabled.';
|
||||
|
||||
return;
|
||||
@@ -61,8 +61,8 @@ class UpdateCheckCronjob extends AbstractCronjob
|
||||
if ($diff < 604800 && false === $this->force) {
|
||||
// get stuff from job:
|
||||
$this->jobFired = false;
|
||||
$this->jobErrored = true;
|
||||
$this->jobSucceeded = false;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = sprintf('Checked for updates less than a week ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data));
|
||||
|
||||
return;
|
||||
|
||||
@@ -57,6 +57,7 @@ use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Csv\CannotInsertRecord;
|
||||
use League\Csv\Exception;
|
||||
use League\Csv\Writer;
|
||||
@@ -731,6 +732,7 @@ class ExportDataGenerator
|
||||
*/
|
||||
private function exportTransactions(): string
|
||||
{
|
||||
Log::debug('Will now export transactions.');
|
||||
// TODO better place for keys?
|
||||
$header = ['user_id', 'group_id', 'journal_id', 'created_at', 'updated_at', 'group_title', 'type', 'currency_code', 'amount', 'foreign_currency_code', 'foreign_amount', 'native_currency_code', 'native_amount', 'native_foreign_amount', 'description', 'date', 'source_name', 'source_iban', 'source_type', 'destination_name', 'destination_iban', 'destination_type', 'reconciled', 'category', 'budget', 'bill', 'tags', 'notes'];
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Trait AugumentData
|
||||
@@ -167,6 +168,8 @@ trait AugumentData
|
||||
*/
|
||||
protected function getLimits(Budget $budget, Carbon $start, Carbon $end): Collection // get data + augment with info
|
||||
{
|
||||
Log::debug('In getLimits');
|
||||
|
||||
/** @var OperationsRepositoryInterface $opsRepository */
|
||||
$opsRepository = app(OperationsRepositoryInterface::class);
|
||||
|
||||
@@ -195,24 +198,30 @@ trait AugumentData
|
||||
|
||||
/** @var BudgetLimit $entry */
|
||||
foreach ($set as $entry) {
|
||||
$currency = $entry->transactionCurrency;
|
||||
Log::debug(sprintf('Now at budget limit #%d', $entry->id));
|
||||
$currency = $entry->transactionCurrency;
|
||||
if ($this->convertToNative) {
|
||||
// the sumExpenses method already handles this.
|
||||
$currency = $this->defaultCurrency;
|
||||
}
|
||||
|
||||
// clone because these objects change each other.
|
||||
$currentStart = clone $entry->start_date;
|
||||
$currentEnd = null === $entry->end_date ? null : clone $entry->end_date;
|
||||
$currentStart = clone $entry->start_date;
|
||||
$currentEnd = null === $entry->end_date ? null : clone $entry->end_date;
|
||||
|
||||
if (null === $currentEnd) {
|
||||
$currentEnd = clone $currentStart;
|
||||
$currentEnd->addMonth();
|
||||
}
|
||||
// native amount.
|
||||
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $entry->transactionCurrency, $this->convertToNative);
|
||||
$spent = $expenses[$currency->id]['sum'] ?? '0';
|
||||
$entry->native_spent = $spent;
|
||||
|
||||
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $currency);
|
||||
$spent = $expenses[$currency->id]['sum'] ?? '0';
|
||||
$entry->spent = $spent;
|
||||
// normal amount:
|
||||
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $entry->transactionCurrency, false);
|
||||
$spent = $expenses[$entry->transactionCurrency->id]['sum'] ?? '0';
|
||||
$entry->spent = $spent;
|
||||
|
||||
$limits->push($entry);
|
||||
}
|
||||
|
||||
@@ -109,14 +109,14 @@ trait PeriodOverview
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred_away' => $this->groupByCurrency($transferredAway),
|
||||
'transferred_in' => $this->groupByCurrency($transferredIn),
|
||||
];
|
||||
'title' => $title,
|
||||
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred_away' => $this->groupByCurrency($transferredAway),
|
||||
'transferred_in' => $this->groupByCurrency($transferredIn),
|
||||
];
|
||||
}
|
||||
$cache->store($entries);
|
||||
Timer::stop('account-period-total');
|
||||
@@ -603,13 +603,13 @@ 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),
|
||||
];
|
||||
}
|
||||
|
||||
return $entries;
|
||||
|
||||
@@ -89,6 +89,7 @@ class AccountBalanceCalculator
|
||||
}
|
||||
|
||||
$set = $query->get(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount']);
|
||||
Log::debug(sprintf('Counted %d transaction(s)', $set->count()));
|
||||
|
||||
// the balance value is an array.
|
||||
// first entry is the balance, second is the date.
|
||||
|
||||
@@ -49,9 +49,9 @@ class TransactionSummarizer
|
||||
$this->convertToNative = Amount::convertToNative($user);
|
||||
}
|
||||
|
||||
public function groupByCurrencyId(array $journals, string $method = 'negative'): array
|
||||
public function groupByCurrencyId(array $journals, string $method = 'negative', bool $includeForeign = true): array
|
||||
{
|
||||
Log::debug(sprintf('Now in groupByCurrencyId(array, "%s")', $method));
|
||||
Log::debug(sprintf('Now in groupByCurrencyId([%d journals], "%s")', count($journals), $method));
|
||||
$array = [];
|
||||
foreach ($journals as $journal) {
|
||||
$field = 'amount';
|
||||
@@ -71,6 +71,7 @@ class TransactionSummarizer
|
||||
$foreignCurrencyDecimalPlaces = null;
|
||||
|
||||
if ($this->convertToNative) {
|
||||
Log::debug('convertToNative is true.');
|
||||
// if convert to native, use the native amount yes or no?
|
||||
$useNative = $this->default->id !== (int) $journal['currency_id'];
|
||||
$useForeign = $this->default->id === (int) $journal['foreign_currency_id'];
|
||||
@@ -94,10 +95,11 @@ class TransactionSummarizer
|
||||
}
|
||||
}
|
||||
if (!$this->convertToNative) {
|
||||
Log::debug(sprintf('Journal #%d also includes foreign amount (foreign is %s)', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
|
||||
Log::debug('convertToNative is false.');
|
||||
// use foreign amount?
|
||||
$foreignCurrencyId = (int) $journal['foreign_currency_id'];
|
||||
if (0 !== $foreignCurrencyId) {
|
||||
Log::debug(sprintf('Journal #%d also includes foreign amount (foreign is "%s")', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
|
||||
$foreignCurrencyName = $journal['foreign_currency_name'];
|
||||
$foreignCurrencySymbol = $journal['foreign_currency_symbol'];
|
||||
$foreignCurrencyCode = $journal['foreign_currency_code'];
|
||||
@@ -124,7 +126,7 @@ class TransactionSummarizer
|
||||
}
|
||||
|
||||
// then process foreign amount, if it exists.
|
||||
if (0 !== $foreignCurrencyId) {
|
||||
if (0 !== $foreignCurrencyId && true === $includeForeign) {
|
||||
$amount = (string) ($journal['foreign_amount'] ?? '0');
|
||||
$array[$foreignCurrencyId] ??= [
|
||||
'sum' => '0',
|
||||
@@ -224,4 +226,10 @@ class TransactionSummarizer
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function setConvertToNative(bool $convertToNative): void
|
||||
{
|
||||
Log::debug(sprintf('Overrule convertToNative to become %s', var_export($convertToNative, true)));
|
||||
$this->convertToNative = $convertToNative;
|
||||
}
|
||||
}
|
||||
|
||||
70
app/Transformers/ExchangeRateTransformer.php
Normal file
70
app/Transformers/ExchangeRateTransformer.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* ExchangeRateTransformer.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\Transformers;
|
||||
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ExchangeRateTransformer extends AbstractTransformer
|
||||
{
|
||||
/**
|
||||
* This method collects meta-data for one or all accounts in the transformer's collection.
|
||||
*/
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the account.
|
||||
*/
|
||||
public function transform(CurrencyExchangeRate $rate): array
|
||||
{
|
||||
return [
|
||||
'id' => (string) $rate->id,
|
||||
'created_at' => $rate->created_at->toAtomString(),
|
||||
'updated_at' => $rate->updated_at->toAtomString(),
|
||||
|
||||
'from_currency_id' => (string) $rate->fromCurrency->id,
|
||||
'from_currency_code' => $rate->fromCurrency->code,
|
||||
'from_currency_symbol' => $rate->fromCurrency->symbol,
|
||||
'from_currency_decimal_places' => $rate->fromCurrency->decimal_places,
|
||||
|
||||
'to_currency_id' => (string) $rate->toCurrency->id,
|
||||
'to_currency_code' => $rate->toCurrency->code,
|
||||
'to_currency_symbol' => $rate->toCurrency->symbol,
|
||||
'to_currency_decimal_places' => $rate->toCurrency->decimal_places,
|
||||
|
||||
'rate' => $rate->rate,
|
||||
'date' => $rate->date->toAtomString(),
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
'uri' => sprintf('/exchange-rates/%s', $rate->id),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\GroupMembership;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Transformers\V2\AbstractTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
|
||||
/**
|
||||
* Class AbstractTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
abstract class AbstractTransformer extends TransformerAbstract
|
||||
{
|
||||
|
||||
@@ -36,6 +36,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AccountTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class AccountTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -40,6 +40,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BillTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class BillTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ use League\Fractal\Resource\Item;
|
||||
|
||||
/**
|
||||
* Class BudgetLimitTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class BudgetLimitTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
|
||||
/**
|
||||
* Class BudgetTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class BudgetTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -29,6 +29,8 @@ use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class CurrencyTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class CurrencyTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -29,6 +29,8 @@ use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class AccountTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class ExchangeRateTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -41,6 +41,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class PiggyBankTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class PiggyBankTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -29,6 +29,8 @@ use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class PreferenceTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class PreferenceTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -43,6 +43,8 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Class TransactionGroupTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class TransactionGroupTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -32,6 +32,8 @@ use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class UserGroupTransformer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class UserGroupTransformer extends AbstractTransformer
|
||||
{
|
||||
|
||||
@@ -250,7 +250,7 @@ class AccountValidator
|
||||
*/
|
||||
protected function findExistingAccount(array $validTypes, array $data, bool $inverse = false): ?Account
|
||||
{
|
||||
app('log')->debug('Now in findExistingAccount', $data);
|
||||
app('log')->debug('Now in findExistingAccount', [$validTypes, $data]);
|
||||
app('log')->debug('The search will be reversed!');
|
||||
$accountId = array_key_exists('id', $data) ? $data['id'] : null;
|
||||
$accountIban = array_key_exists('iban', $data) ? $data['iban'] : null;
|
||||
|
||||
64
changelog.md
64
changelog.md
@@ -3,6 +3,70 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 6.2.12 - 2025-04-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 9755](https://github.com/firefly-iii/firefly-iii/issues/9755) (Unable to create transactions with non-native currency accounts when "display amounts in native currency" is enabled) reported by @dicksonleong
|
||||
- [Issue 9867](https://github.com/firefly-iii/firefly-iii/issues/9867) (Transactions from Jan 31 being counted in February) reported by @edbingo
|
||||
- [Issue 9878](https://github.com/firefly-iii/firefly-iii/issues/9878) (Piggy bank currency - wrong setting displayed or setting not saved) reported by @dethegeek
|
||||
- [Issue 10068](https://github.com/firefly-iii/firefly-iii/issues/10068) (Export Data isn't exporting all transactions in the data) reported by @firsttiger
|
||||
- [Discussion 10162](https://github.com/orgs/firefly-iii/discussions/10162) (Reverse proxy and `X-Forwarded-Prefix` header) started by @frenchu
|
||||
|
||||
## 6.2.11 - 2025-04-21
|
||||
|
||||
### Added
|
||||
|
||||
- Support for Persian (`fa_IR`)
|
||||
- Add expiry details for personal access tokens
|
||||
|
||||
### Changed
|
||||
|
||||
- [PR 10039](https://github.com/firefly-iii/firefly-iii/pull/10039) (update check: consider cron succesfull when disabled or too frequent) reported by @ovv
|
||||
- Update currency list and update exchange rates
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 9398](https://github.com/firefly-iii/firefly-iii/issues/9398) (Expand email settings to allow self-signed certificates) reported by @SoulSeekkor
|
||||
- [Issue 9858](https://github.com/firefly-iii/firefly-iii/issues/9858) (Homepage "left to spend" count two times an expense with "foreign amount") reported by @M4xS0ch
|
||||
- [Issue 10015](https://github.com/firefly-iii/firefly-iii/issues/10015) ("It looks like this IBAN is already in use." when editing asset account.) reported by @wolph
|
||||
- [Issue 10025](https://github.com/firefly-iii/firefly-iii/issues/10025) (Liabilities not counted in income and expenses) reported by @BhasherBEL
|
||||
- [Issue 10068](https://github.com/firefly-iii/firefly-iii/issues/10068) (Export Data isn't exporting all transactions in the data) reported by @firsttiger
|
||||
- [Issue 10069](https://github.com/firefly-iii/firefly-iii/issues/10069) (Undefined array key "foreign_currency_decimal_places") reported by @akong-carbon6
|
||||
- [Issue 10114](https://github.com/firefly-iii/firefly-iii/issues/10114) (Budget for foreign currency not getting updated when a transaction refers to it) reported by @srikakulamts
|
||||
- [Issue 10150](https://github.com/firefly-iii/firefly-iii/issues/10150) (Mass deletion of initial balance throws 404 and doesn't delete) reported by @Tyler-Angell
|
||||
|
||||
## 6.2.10 - 2025-03-22
|
||||
|
||||
### Added
|
||||
|
||||
- [PR 9903](https://github.com/firefly-iii/firefly-iii/pull/9903) (Add support for PHP function `strpos` in expressions) reported by @lompi
|
||||
|
||||
### Changed
|
||||
|
||||
- [Issue 9972](https://github.com/firefly-iii/firefly-iii/issues/9972) (Important optimization improvement: enable opcache) reported by @jgoclawski
|
||||
- Greatly improved speed of account overview
|
||||
- Better validation of environment variables
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 9770](https://github.com/firefly-iii/firefly-iii/issues/9770) (User ntfy notification forbidden 403) reported by @qck4fun
|
||||
- [Issue 9895](https://github.com/firefly-iii/firefly-iii/issues/9895) (Account Balance not updating in Transaction tab) reported by @StoicaRemus
|
||||
- [Issue 9906](https://github.com/firefly-iii/firefly-iii/issues/9906) (404 Not Found when deleting rule group) reported by @EnochPrime
|
||||
- [Issue 9908](https://github.com/firefly-iii/firefly-iii/issues/9908) (HTTP 500 on tags) reported by @wuvs
|
||||
- [Issue 9914](https://github.com/firefly-iii/firefly-iii/issues/9914) (adding money to piggy bank via API broken) reported by @4e868df3
|
||||
- [Issue 9941](https://github.com/firefly-iii/firefly-iii/issues/9941) (has_any_subscription search filter not valid) reported by @PhilWun
|
||||
- [Issue 9948](https://github.com/firefly-iii/firefly-iii/issues/9948) (typo in docker example env file) reported by @amenekowo
|
||||
- [Issue 9954](https://github.com/firefly-iii/firefly-iii/issues/9954) (Creating Bills via API is broken (optional fields are required)) reported by @jsegido
|
||||
- [Discussion 9970](https://github.com/orgs/firefly-iii/discussions/9970) (Category report with tags?) started by @luddeluddis
|
||||
- [Issue 9876](https://github.com/firefly-iii/firefly-iii/issues/9876) (data/bulkUpdateTransactions POST should not requires Content-Type header in request as request body must be empty) reported by @bouil
|
||||
- [Issue 10007](https://github.com/firefly-iii/firefly-iii/issues/10007) (Wrong currency showed in dashboard for "Subscriptions to pay" when subscription paid with different currency) reported by @Astro1247
|
||||
|
||||
### API
|
||||
|
||||
- [Issue 9902](https://github.com/firefly-iii/firefly-iii/issues/9902) (BIC number no longer returned by the /accounts API endpoint) reported by @dawid-czarnecki
|
||||
|
||||
|
||||
## 6.2.9 - 2025-02-22
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
"bacon/bacon-qr-code": "^2",
|
||||
"diglactic/laravel-breadcrumbs": "^10",
|
||||
"gdbots/query-parser": "^3.0",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"jc5/google2fa-laravel": "^2.0",
|
||||
"jc5/recovery": "^2",
|
||||
"laravel-notification-channels/pushover": "^4.0",
|
||||
|
||||
677
composer.lock
generated
677
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -28,48 +28,53 @@ return [
|
||||
'download_enabled' => env('ENABLE_EXTERNAL_RATES', false),
|
||||
|
||||
// if currencies are added, default rates must be added as well!
|
||||
// last exchange rate update: 2024-12-30
|
||||
// source: https://www.xe.com/currencyconverter/
|
||||
'date' => '2024-12-30',
|
||||
'date' => '2025-04-15',
|
||||
|
||||
// all rates are from EUR to $currency:
|
||||
'rates' => [
|
||||
// europa
|
||||
'EUR' => 1,
|
||||
'HUF' => 410.79798,
|
||||
'GBP' => 0.82858703,
|
||||
'UAH' => 43.485934,
|
||||
'PLN' => 4.2708542,
|
||||
'TRY' => 36.804124,
|
||||
'GBP' => 0.86003261,
|
||||
'UAH' => 46.867455,
|
||||
'PLN' => 4.2802098,
|
||||
'TRY' => 43.180054,
|
||||
'DKK' => 7.4591,
|
||||
'RON' => 4.9768699,
|
||||
'RON' => 7.4648336,
|
||||
|
||||
// Americas
|
||||
'USD' => 1.0430046,
|
||||
'BRL' => 6.4639113,
|
||||
'CAD' => 1.5006908,
|
||||
'MXN' => 21.249542,
|
||||
'USD' => 1.1349044,
|
||||
'BRL' => 6.6458518,
|
||||
'CAD' => 1.575105,
|
||||
'MXN' => 22.805278,
|
||||
|
||||
// Oceania currencies
|
||||
'IDR' => 16860.057,
|
||||
'AUD' => 1.6705648,
|
||||
'NZD' => 1.8436945,
|
||||
'IDR' => 19070.382,
|
||||
'AUD' => 1.787202,
|
||||
'NZD' => 1.9191078,
|
||||
|
||||
// africa
|
||||
'EGP' => 53.038174,
|
||||
'MAD' => 10.521629,
|
||||
'ZAR' => 19.460263,
|
||||
'EGP' => 57.874172,
|
||||
'MAD' => 10.549438,
|
||||
'ZAR' => 21.444356,
|
||||
|
||||
// asia
|
||||
'JPY' => 164.74767,
|
||||
'RMB' => 7.6138994,
|
||||
'CNY' => 7.6138994,
|
||||
'RUB' => 108.56771,
|
||||
'INR' => 89.157391,
|
||||
'JPY' => 162.47195,
|
||||
'RMB' => 8.2849977,
|
||||
'CNY' => 8.2849977,
|
||||
'RUB' => 93.34423,
|
||||
'INR' => 97.572815,
|
||||
|
||||
// int
|
||||
'ILS' => 3.8428028,
|
||||
'CHF' => 0.94044969,
|
||||
'ILS' => 4.1801786,
|
||||
'CHF' => 0.92683126,
|
||||
'HRK' => 7.5345, // replaced by EUR
|
||||
|
||||
'ISK' => 145.10532,
|
||||
'NOK' => 11.980824,
|
||||
'SEK' => 11.08809,
|
||||
'HKD' => 8.8046322,
|
||||
'CZK' => 25.092213,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -29,12 +29,10 @@ use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Support\Binder\Date;
|
||||
use FireflyIII\TransactionRules\Actions\AddTag;
|
||||
use FireflyIII\TransactionRules\Actions\ClearBudget;
|
||||
use FireflyIII\TransactionRules\Actions\ClearCategory;
|
||||
@@ -57,7 +55,6 @@ use FireflyIII\TransactionRules\Actions\SetSourceAccount;
|
||||
use FireflyIII\TransactionRules\Actions\SetSourceToCashAccount;
|
||||
use FireflyIII\TransactionRules\Actions\SwitchAccounts;
|
||||
use FireflyIII\TransactionRules\Actions\UpdatePiggyBank;
|
||||
use FireflyIII\User;
|
||||
|
||||
/*
|
||||
* DO NOT EDIT THIS FILE. IT IS AUTO GENERATED.
|
||||
@@ -81,7 +78,7 @@ return [
|
||||
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2025-03-14',
|
||||
'version' => 'develop/2025-04-27',
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 25,
|
||||
|
||||
@@ -132,7 +129,7 @@ return [
|
||||
'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish'],
|
||||
'ca_ES' => ['name_locale' => 'Català (Espanya)', 'name_english' => 'Catalan (Spain)'],
|
||||
// 'et_EE' => ['name_locale' => 'Estonian', 'name_english' => 'Estonian'],
|
||||
// 'fa_IR' => ['name_locale' => 'فارسی', 'name_english' => 'Persian'],
|
||||
'fa_IR' => ['name_locale' => 'فارسی', 'name_english' => 'Persian'],
|
||||
'fi_FI' => ['name_locale' => 'Suomi', 'name_english' => 'Finnish'],
|
||||
'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French'],
|
||||
// 'he_IL' => ['name_locale' => 'Hebrew', 'name_english' => 'Hebrew'],
|
||||
@@ -159,7 +156,7 @@ return [
|
||||
'sv_SE' => ['name_locale' => 'Svenska', 'name_english' => 'Swedish'],
|
||||
// // 'tlh_AA' => ['name_locale' => 'tlhIngan Hol', 'name_english' => 'Klingon'],
|
||||
'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'],
|
||||
'uk_UA' => ['name_locale' => 'Ukranian', 'name_english' => 'Ukranian'],
|
||||
'uk_UA' => ['name_locale' => 'Українська', 'name_english' => 'Ukrainian'],
|
||||
'vi_VN' => ['name_locale' => 'Tiếng Việt', 'name_english' => 'Vietnamese'],
|
||||
'zh_TW' => ['name_locale' => 'Chinese Traditional', 'name_english' => 'Chinese Traditional'],
|
||||
'zh_CN' => ['name_locale' => 'Chinese Simplified', 'name_english' => 'Chinese Simplified'],
|
||||
@@ -187,6 +184,7 @@ return [
|
||||
'currencyPreference' => 'EUR',
|
||||
'language' => 'en_US',
|
||||
'locale' => 'equal',
|
||||
'convertToNative' => false,
|
||||
],
|
||||
'default_currency' => 'EUR',
|
||||
'default_language' => envNonEmpty('DEFAULT_LANGUAGE', 'en_US'),
|
||||
|
||||
@@ -37,14 +37,19 @@ return [
|
||||
|
||||
'mailers' => [
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => envNonEmpty('MAIL_HOST', 'smtp.mailtrap.io'),
|
||||
'port' => (int) env('MAIL_PORT', 2525),
|
||||
'encryption' => envNonEmpty('MAIL_ENCRYPTION', 'tls'),
|
||||
'username' => envNonEmpty('MAIL_USERNAME', 'user@example.com'),
|
||||
'password' => envNonEmpty('MAIL_PASSWORD', 'password'),
|
||||
'timeout' => null,
|
||||
'verify_peer' => null !== env('MAIL_ENCRYPTION'),
|
||||
'transport' => 'smtp',
|
||||
'host' => envNonEmpty('MAIL_HOST', 'smtp.mailtrap.io'),
|
||||
'port' => (int) env('MAIL_PORT', 2525),
|
||||
'encryption' => envNonEmpty('MAIL_ENCRYPTION', 'tls'),
|
||||
'username' => envNonEmpty('MAIL_USERNAME', 'user@example.com'),
|
||||
'password' => envNonEmpty('MAIL_PASSWORD', 'password'),
|
||||
'timeout' => null,
|
||||
'scheme' => env('MAIL_SCHEME'),
|
||||
'url' => env('MAIL_URL'),
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||
'verify_peer' => env('MAIL_VERIFY_PEER', true),
|
||||
'allow_self_signed' => env('MAIL_ALLOW_SELF_SIGNED', false),
|
||||
'verify_peer_name' => env('MAIL_VERIFY_PEER_NAME', true),
|
||||
],
|
||||
'mailersend' => [
|
||||
'transport' => 'mailersend',
|
||||
|
||||
@@ -139,6 +139,7 @@ return [
|
||||
'firefly' => [
|
||||
'administrations_page_title',
|
||||
'administrations_index_menu',
|
||||
'expires_at',
|
||||
'temp_administrations_introduction',
|
||||
'administration_currency_form_help',
|
||||
'administrations_page_edit_sub_title_js',
|
||||
@@ -339,6 +340,7 @@ return [
|
||||
'en_US',
|
||||
'es_ES',
|
||||
'ca_ES',
|
||||
'fa_IR',
|
||||
'fi_FI',
|
||||
'fr_FR',
|
||||
'hu_HU',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user