mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-28 22:21:22 +00:00
Compare commits
187 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33668a3688 | ||
|
|
5414a70abb | ||
|
|
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,7 @@ 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
|
||||
- =
|
||||
- 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,6 +39,8 @@ 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\Facades\Log;
|
||||
@@ -97,10 +99,9 @@ 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 = [];
|
||||
@@ -122,6 +123,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 +132,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 +243,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 +292,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 +323,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 +429,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 +470,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'],
|
||||
@@ -388,11 +540,12 @@ class BasicController extends Controller
|
||||
];
|
||||
}
|
||||
if (0 === count($return)) {
|
||||
$currency = $this->nativeCurrency;
|
||||
$return[] = [
|
||||
$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,
|
||||
@@ -408,7 +561,7 @@ class BasicController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -247,6 +247,6 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
}
|
||||
$summarizer = new TransactionSummarizer($this->user);
|
||||
|
||||
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'];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -49,7 +49,7 @@ 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));
|
||||
$array = [];
|
||||
@@ -94,10 +94,10 @@ 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']));
|
||||
// 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 +124,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',
|
||||
|
||||
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",
|
||||
|
||||
645
composer.lock
generated
645
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-21',
|
||||
'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'],
|
||||
@@ -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',
|
||||
|
||||
@@ -69,10 +69,14 @@ class TransactionCurrencySeeder extends Seeder
|
||||
$currencies[] = ['code' => 'RUB', 'name' => 'Russian ruble', 'symbol' => '₽', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'INR', 'name' => 'Indian rupee', 'symbol' => '₹', 'decimal_places' => 2];
|
||||
|
||||
// PLEASE ADD NEW CURRENCIES BELOW THIS LINE
|
||||
// ALL NEW CURRENCIES BELOW THIS LINE
|
||||
$currencies[] = ['code' => 'ILS', 'name' => 'Israeli new shekel', 'symbol' => '₪', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'CHF', 'name' => 'Swiss franc', 'symbol' => 'CHF', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'HRK', 'name' => 'Croatian kuna', 'symbol' => 'kn', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'HKD', 'name' => 'Hong Kong dollar', 'symbol' => 'HK$', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'CHF', 'name' => 'Swiss franc', 'symbol' => 'CHF', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'NOK', 'name' => 'Norwegian krone', 'symbol' => 'kr.', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'CZK', 'name' => 'Czech koruna', 'symbol' => 'Kč', 'decimal_places' => 2];
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
if (null === TransactionCurrency::where('code', $currency['code'])->first()) {
|
||||
|
||||
795
package-lock.json
generated
795
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -80,11 +80,18 @@ function drawChart() {
|
||||
|
||||
// left to spend
|
||||
if (key.substring(0, 17) === 'left-to-spend-in-') {
|
||||
left_to_spend_top.push(data[key].value_parsed);
|
||||
left_to_spend_bottom.push(data[key].sub_title);
|
||||
if (parseFloat(data[key].monetary_value) > 0) {
|
||||
if(true === data[key].no_available_budgets) {
|
||||
left_to_spend_top.push('---');
|
||||
left_to_spend_bottom.push('---');
|
||||
keepGreen = true;
|
||||
}
|
||||
if(false === data[key].no_available_budgets) {
|
||||
left_to_spend_top.push(data[key].value_parsed);
|
||||
left_to_spend_bottom.push(data[key].sub_title);
|
||||
if (parseFloat(data[key].monetary_value) > 0) {
|
||||
keepGreen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// net worth
|
||||
|
||||
@@ -16,7 +16,8 @@ The different alpha and beta builds will be compiled from their corresponding ta
|
||||
### Minor Release Support Matrix
|
||||
| Version | Supported |
|
||||
|----------------------------------|--------------------|
|
||||
| Firefly III v6.1.x | :white_check_mark: |
|
||||
| Firefly III v6.2.x | :white_check_mark: |
|
||||
| Firefly III v6.1.x | :x: |
|
||||
| Firefly III v6.0.x | :x: |
|
||||
| Firefly III v5.8.x | :x: |
|
||||
| Firefly III v5.7.x | :x: |
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ $t('firefly.name') }}</th>
|
||||
<th scope="col">{{ $t('firefly.expires_at') }}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -57,6 +58,10 @@
|
||||
<td style="vertical-align: middle;">
|
||||
{{ token.name }}
|
||||
</td>
|
||||
<!-- expires at -->
|
||||
<td style="vertical-align: middle;">
|
||||
{{ new Date(token.expires_at).toLocaleString() }}
|
||||
</td>
|
||||
|
||||
<!-- Delete Button -->
|
||||
<td style="vertical-align: middle;">
|
||||
@@ -232,7 +237,7 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the available scopes.
|
||||
* Get all the available scopes.
|
||||
*/
|
||||
getScopes() {
|
||||
axios.get('./oauth/scopes')
|
||||
|
||||
@@ -216,7 +216,7 @@ export default {
|
||||
}
|
||||
},
|
||||
selectedItem: function (e) {
|
||||
console.log('In SelectedItem()');
|
||||
// console.log('In SelectedItem()');
|
||||
if (typeof this.name === 'undefined') {
|
||||
// console.log('Is undefined');
|
||||
return;
|
||||
|
||||
@@ -540,12 +540,18 @@ export default {
|
||||
allowed_types: window.expectedSourceTypes.destination[this.ucFirst(transaction.type)]
|
||||
}
|
||||
};
|
||||
// console.log('Destination currency id is ' + result.destination_account.currency_id);
|
||||
// if transaction type is transfer, the destination currency_id etc. MUST match the actual account currency info.
|
||||
if ('transfer' === transaction.type && null !== transaction.foreign_currency_code) {
|
||||
// OR if the transaction type is a withdrawal, and the destination account is a liability account, same as above.
|
||||
if (
|
||||
('transfer' === transaction.type && null !== transaction.foreign_currency_code) ||
|
||||
('withdrawal' === transaction.type && ['Loan', 'Debt', 'Mortgage'].includes(transaction.destination_type) && null !== transaction.foreign_currency_code)
|
||||
) {
|
||||
result.destination_account.currency_id = transaction.foreign_currency_id;
|
||||
result.destination_account.currency_name = transaction.foreign_currency_name;
|
||||
result.destination_account.currency_code = transaction.foreign_currency_code;
|
||||
result.destination_account.currency_decimal_places = transaction.foreign_currency_decimal_places;
|
||||
// console.log('Set destination currency_id to ' + result.destination_account.currency_id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -137,7 +137,6 @@ export default {
|
||||
// lock dropdown list on currencyID of destination.
|
||||
for (const key in this.currencies) {
|
||||
if (this.currencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
|
||||
|
||||
if (
|
||||
parseInt(this.currencies[key].id) === parseInt(this.destination.currency_id)
|
||||
) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"firefly": {
|
||||
"administrations_page_title": "Financial administrations",
|
||||
"administrations_index_menu": "Financial administrations",
|
||||
"expires_at": "Expires at",
|
||||
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its native currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
|
||||
"administration_currency_form_help": "It may take a long time for the page to load if you change the native currency because transaction may need to be converted to your (new) native currency.",
|
||||
"administrations_page_edit_sub_title_js": "Edit financial administration \"{title}\"",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"firefly": {
|
||||
"administrations_page_title": "Financial administrations",
|
||||
"administrations_index_menu": "Financial administrations",
|
||||
"expires_at": "Expires at",
|
||||
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its native currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
|
||||
"administration_currency_form_help": "It may take a long time for the page to load if you change the native currency because transaction may need to be converted to your (new) native currency.",
|
||||
"administrations_page_edit_sub_title_js": "Edit financial administration \"{title}\"",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"firefly": {
|
||||
"administrations_page_title": "Financial administrations",
|
||||
"administrations_index_menu": "Financial administrations",
|
||||
"expires_at": "Expires at",
|
||||
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its native currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
|
||||
"administration_currency_form_help": "It may take a long time for the page to load if you change the native currency because transaction may need to be converted to your (new) native currency.",
|
||||
"administrations_page_edit_sub_title_js": "Edit financial administration \"{title}\"",
|
||||
@@ -51,7 +52,7 @@
|
||||
"notes": "Pozn\u00e1mky",
|
||||
"external_url": "Extern\u00ed URL adresa",
|
||||
"update_transaction": "Aktualizovat transakci",
|
||||
"after_update_create_another": "After updating, return here to continue editing.",
|
||||
"after_update_create_another": "Po aktualizaci se vr\u00e1tit sem pro pokra\u010dov\u00e1n\u00ed v \u00faprav\u00e1ch.",
|
||||
"store_as_new": "Store as a new transaction instead of updating.",
|
||||
"split_title_help": "Pokud vytvo\u0159\u00edte roz\u00fa\u010dtov\u00e1n\u00ed, je t\u0159eba, aby zde byl celkov\u00fd popis pro v\u0161echna roz\u00fa\u010dtov\u00e1n\u00ed dan\u00e9 transakce.",
|
||||
"none_in_select_list": "(\u017e\u00e1dn\u00e9)",
|
||||
@@ -103,7 +104,7 @@
|
||||
"profile_oauth_confidential": "Soukrom\u00e1 aplikace",
|
||||
"profile_oauth_confidential_help": "Po\u017eadovat aby se klienti autorizovali. Soukrom\u00e9 aplikace mohou bezpe\u010dn\u011b pracovat s p\u0159\u00edstupov\u00fdmi \u00fadaji bez toho aby je zve\u0159ejnily. Ve\u0159ejn\u00e9 aplikace, nativn\u00ed nebo JavaScriptov\u00e9 SPA, toho schopn\u00e9 nejsou.",
|
||||
"multi_account_warning_unknown": "Depending on the type of transaction you create, the source and\/or destination account of subsequent splits may be overruled by whatever is defined in the first split of the transaction.",
|
||||
"multi_account_warning_withdrawal": "Zdrojov\u00fd \u00fa\u010del v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed je ovl\u00e1dan\u00fd zdrojov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.",
|
||||
"multi_account_warning_withdrawal": "Zdrojov\u00fd \u00fa\u010det cel\u00e9 transakce je ovl\u00e1dan\u00fd prvn\u00edm rozd\u011blen\u00edm.",
|
||||
"multi_account_warning_deposit": "C\u00edlov\u00fd \u00fa\u010del v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed je ovl\u00e1dan\u00fd c\u00edlov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.",
|
||||
"multi_account_warning_transfer": "Zdrojov\u00fd i c\u00edlov\u00fd \u00fa\u010det v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed jsou ovl\u00e1d\u00e1ny zdrojov\u00fdm a c\u00edlov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.",
|
||||
"webhook_trigger_STORE_TRANSACTION": "Po vytvo\u0159en\u00ed transakce",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user