Compare commits

...

106 Commits

Author SHA1 Message Date
github-actions
41c0e6fe2d Auto commit for release 'develop' on 2025-02-05 2025-02-05 07:43:04 +01:00
James Cole
c07914e733 Update changelog 2025-02-05 07:39:53 +01:00
James Cole
52e2302f4f Fix search code. 2025-02-05 07:37:22 +01:00
github-actions
0a6b34b4f2 Auto commit for release 'v6.2.3' on 2025-02-05 2025-02-05 07:25:30 +01:00
github-actions
1e06b4dd0b Merge branch 'develop' 2025-02-05 06:23:14 +00:00
James Cole
5701f95e0b Expand changelog 2025-02-05 07:22:00 +01:00
James Cole
60d3572d37 Expand changelog 2025-02-05 07:21:31 +01:00
github-actions
ffa6e6a571 Auto commit for release 'develop' on 2025-02-05 2025-02-05 06:50:00 +01:00
James Cole
d6453cd735 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-02-05 06:46:35 +01:00
James Cole
fd79f9df44 Expand changelog, fix migration. 2025-02-05 06:46:27 +01:00
github-actions
4587340293 Auto commit for release 'develop' on 2025-02-05 2025-02-05 06:41:08 +01:00
James Cole
90bfdc7573 Fix #9754 2025-02-05 06:36:51 +01:00
James Cole
eca12f661f Fix #9327 2025-02-05 06:31:15 +01:00
James Cole
f85878b843 Final tweaks in account balance logic. 2025-02-05 06:29:07 +01:00
James Cole
6499b5eaab Rename variable for consistency. 2025-02-05 06:03:57 +01:00
James Cole
7e4fece63d add some checks and balances to migrations. 2025-02-05 05:58:53 +01:00
James Cole
512eddf8be Fix #9736 2025-02-05 05:51:22 +01:00
github-actions
f0fa93a811 Auto commit for release 'develop' on 2025-02-04 2025-02-04 21:38:20 +01:00
James Cole
3c8de21709 Fix balance and native balance lists. 2025-02-04 21:34:46 +01:00
James Cole
81173e8340 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Support/Steam.php
2025-02-04 21:28:43 +01:00
James Cole
35a8fa5f02 Fix balance in range. 2025-02-04 21:27:24 +01:00
James Cole
443036936d Update changelog. 2025-02-04 21:26:53 +01:00
James Cole
ac88007593 Replace steam call. 2025-02-04 21:26:40 +01:00
github-actions
2297589dca Auto commit for release 'develop' on 2025-02-04 2025-02-04 19:48:38 +01:00
James Cole
6ada5fa560 Fix #9751 2025-02-04 19:43:53 +01:00
Sander Dorigo
8f6eefb5e7 Fix #9745 2025-02-04 08:45:08 +01:00
Sander Dorigo
b36a50381b Fix #9747 2025-02-04 08:42:54 +01:00
Sander Dorigo
51f84b3060 Fix #9762 2025-02-04 08:37:02 +01:00
Sander Dorigo
72132a19b0 Fix #9736 2025-02-04 08:30:27 +01:00
Sander Dorigo
065d165211 Fix #9713 2025-02-04 08:28:43 +01:00
James Cole
cabedf39b2 Merge pull request #9750 from firefly-iii/dependabot/github_actions/aws-actions/closed-issue-message-2
Bump aws-actions/closed-issue-message from 1 to 2
2025-02-03 08:03:16 +01:00
dependabot[bot]
5d3806fcd4 Bump aws-actions/closed-issue-message from 1 to 2
Bumps [aws-actions/closed-issue-message](https://github.com/aws-actions/closed-issue-message) from 1 to 2.
- [Release notes](https://github.com/aws-actions/closed-issue-message/releases)
- [Commits](https://github.com/aws-actions/closed-issue-message/compare/v1...v2)

---
updated-dependencies:
- dependency-name: aws-actions/closed-issue-message
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:34:11 +00:00
github-actions
01695b3342 Auto commit for release 'develop' on 2025-02-03 2025-02-03 04:08:56 +01:00
James Cole
71fb5fe077 Fix changelog. 2025-02-02 16:24:10 +01:00
github-actions
3bec106840 Auto commit for release 'v6.2.2' on 2025-02-02 2025-02-02 16:20:11 +01:00
James Cole
fb01c36be1 Update changelog and manifest [skip ci] 2025-02-02 16:14:32 +01:00
James Cole
26d851e69e Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-02-02 16:14:09 +01:00
James Cole
28c18c046b Fix #9744 2025-02-02 16:13:56 +01:00
github-actions
318cef7e3b Auto commit for release 'develop' on 2025-02-02 2025-02-02 16:06:22 +01:00
James Cole
e8dc8f25be Update changelog [skip ci] 2025-02-02 16:02:45 +01:00
James Cole
10ccc30240 Fix #9738 2025-02-02 16:02:17 +01:00
James Cole
5adc877d5e Merge pull request #9743 from mansehr/feature-nordic-currencies
Feature nordic currencies
2025-02-02 15:23:05 +01:00
SoftBrix
30923afb2b Added ISK 2025-02-02 14:48:01 +01:00
SoftBrix
4eb6813b43 Added NOK 2025-02-02 14:46:26 +01:00
James Cole
7521a31619 Expand changelog. 2025-02-02 06:48:57 +01:00
James Cole
fc05beb452 Fix #9727 2025-02-02 06:24:10 +01:00
James Cole
1103428a83 Smaller issue replies. 2025-02-02 05:41:54 +01:00
James Cole
d06d521bf0 Merge branch 'main' into develop 2025-02-02 05:39:41 +01:00
James Cole
8f64f1c0eb Expand explanation. 2025-02-02 05:39:33 +01:00
James Cole
d11c232171 Fix https://github.com/firefly-iii/firefly-iii/issues/9736 2025-02-02 05:38:47 +01:00
SoftBrix
93c73248de Added SEK 2025-02-02 00:03:31 +01:00
James Cole
5bed081ab9 Fix https://github.com/firefly-iii/firefly-iii/issues/9731 2025-02-01 21:14:28 +01:00
James Cole
c5188c503e Fix https://github.com/firefly-iii/firefly-iii/issues/9713 2025-02-01 21:06:06 +01:00
James Cole
98ffcac7b6 Fix https://github.com/firefly-iii/firefly-iii/issues/9729 2025-02-01 20:58:35 +01:00
James Cole
df1e81d611 Fix https://github.com/firefly-iii/firefly-iii/issues/9730 2025-02-01 20:52:41 +01:00
James Cole
9711170b08 Fix https://github.com/firefly-iii/firefly-iii/issues/9732 2025-02-01 20:43:20 +01:00
github-actions
e43264bdce Auto commit for release 'develop' on 2025-02-01 2025-02-01 19:12:18 +01:00
github-actions
e0643bed7a Auto commit for release 'v6.2.1' on 2025-02-01 2025-02-01 19:06:02 +01:00
James Cole
7f0eb3b064 Expand changelog. 2025-02-01 19:01:17 +01:00
James Cole
f38e510526 Fix https://github.com/firefly-iii/firefly-iii/issues/9714 2025-02-01 18:59:55 +01:00
James Cole
25f99b23b2 Merge branch 'main' into develop 2025-02-01 18:40:00 +01:00
James Cole
44281fc8a0 Respond to "fixed" label. 2025-02-01 18:39:51 +01:00
James Cole
eed3902cb7 Fix https://github.com/firefly-iii/firefly-iii/issues/9726 2025-02-01 18:37:26 +01:00
James Cole
94a3bb0443 Limit the number of recurring transactions returned. 2025-02-01 15:39:34 +01:00
James Cole
8dcc36880e Fix https://github.com/firefly-iii/firefly-iii/issues/9722 2025-02-01 13:03:19 +01:00
James Cole
695bb31894 Fix https://github.com/firefly-iii/firefly-iii/issues/9723 2025-02-01 13:01:13 +01:00
James Cole
f8ded66869 Fix https://github.com/firefly-iii/firefly-iii/issues/9721 2025-02-01 12:53:34 +01:00
James Cole
8e4bdbc584 Fix https://github.com/firefly-iii/firefly-iii/issues/9720 2025-02-01 12:50:48 +01:00
James Cole
f7b14b01bc Fix https://github.com/firefly-iii/firefly-iii/issues/9717 2025-02-01 12:01:50 +01:00
James Cole
705b9bf0f2 Fix https://github.com/firefly-iii/firefly-iii/issues/9719 2025-02-01 11:44:56 +01:00
James Cole
f0226dbc54 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-02-01 11:29:21 +01:00
James Cole
1b1dfb0d7b Fix https://github.com/firefly-iii/firefly-iii/issues/9718 2025-02-01 11:28:39 +01:00
github-actions
8ed5092a76 Auto commit for release 'develop' on 2025-02-01 2025-02-01 06:23:31 +01:00
James Cole
d609821be6 Expand debug table. 2025-02-01 06:18:58 +01:00
github-actions
cd0201074c Auto commit for release 'v6.2.0' on 2025-01-31 2025-01-31 20:35:34 +01:00
James Cole
1d8feec7bc Update changelog. 2025-01-31 20:28:17 +01:00
James Cole
d941472c84 Merge branch 'main' into develop 2025-01-29 20:12:19 +01:00
James Cole
674a118fac Merge pull request #9709 from firefly-iii/dependabot/composer/composer-aeff1b7291
Bump twig/twig from 3.18.0 to 3.19.0 in the composer group across 1 directory
2025-01-29 20:11:54 +01:00
dependabot[bot]
1334d793f6 Bump twig/twig in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [twig/twig](https://github.com/twigphp/Twig).


Updates `twig/twig` from 3.18.0 to 3.19.0
- [Changelog](https://github.com/twigphp/Twig/blob/3.x/CHANGELOG)
- [Commits](https://github.com/twigphp/Twig/compare/v3.18.0...v3.19.0)

---
updated-dependencies:
- dependency-name: twig/twig
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-29 18:44:41 +00:00
James Cole
60354c0202 Fix https://github.com/firefly-iii/firefly-iii/issues/9704 2025-01-29 08:40:16 +01:00
James Cole
22081d3f0a Add skip help text 2025-01-29 07:52:16 +01:00
James Cole
4b3f8fc78d Remove logging [skip ci] 2025-01-29 07:48:43 +01:00
github-actions
4bd1aab86d Auto commit for release 'develop' on 2025-01-27 2025-01-27 04:08:46 +01:00
James Cole
60362cb60c Remove debug header, does not work. 2025-01-26 17:07:45 +01:00
github-actions
27f740bf98 Auto commit for release 'v6.2.0-beta.1' on 2025-01-26 2025-01-26 14:41:24 +01:00
github-actions
5e15747a5b Auto commit for release 'develop' on 2025-01-26 2025-01-26 14:33:19 +01:00
James Cole
8a6eaad2bb Fix vite 2025-01-26 14:29:11 +01:00
James Cole
5ab52d9f66 Fix vite 2025-01-26 14:26:17 +01:00
James Cole
21a327bf08 Merge branch 'main' into develop 2025-01-26 14:10:30 +01:00
James Cole
15dd175394 Add date [skip ci] 2025-01-26 14:10:09 +01:00
James Cole
3f35305beb Update changelog. 2025-01-26 14:09:49 +01:00
James Cole
768d8b1515 Fix currency exchange rates 2025-01-26 10:09:09 +01:00
James Cole
1c19428a12 Catch API endpoint errors. 2025-01-26 07:44:41 +01:00
James Cole
c204533195 Fix various API 500 errors. 2025-01-26 06:30:38 +01:00
James Cole
6d89485792 Fix endpoints, validate dates. 2025-01-25 09:17:21 +01:00
James Cole
949d818bad Cleanup code. 2025-01-25 04:51:09 +01:00
James Cole
a12e200a0a Put query count in debug header 2025-01-25 04:50:36 +01:00
James Cole
b4039b1f13 Report if the version is a dev version 2025-01-25 04:50:26 +01:00
James Cole
32921e15b1 Remove errors from handler 2025-01-25 04:50:15 +01:00
James Cole
4f7cc7d53b More strict date validation 2025-01-25 04:49:28 +01:00
James Cole
0986bfbc34 Rename warnings to notifications 2025-01-25 04:49:08 +01:00
James Cole
663202bfc6 Return correct headers 2025-01-25 04:48:51 +01:00
James Cole
6f63ddf5b0 [chore] Fix https://github.com/firefly-iii/firefly-iii/issues/9683 and rename default to native. 2025-01-22 05:24:12 +01:00
James Cole
a9805b144a Merge pull request #9684 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-545022be4d 2025-01-22 04:37:48 +01:00
dependabot[bot]
e1b8b9b3ae Bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.0.7 to 6.0.9
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.0.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-21 21:02:17 +00:00
James Cole
d57327fd11 [chore] various code cleanup. 2025-01-21 06:34:39 +01:00
163 changed files with 1916 additions and 1386 deletions

View File

@@ -406,16 +406,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.68.1",
"version": "v3.68.5",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff"
"reference": "7bedb718b633355272428c60736dc97fb96daf27"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b9db2b2ea3cdba7201067acee46f984ef2397cff",
"reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7bedb718b633355272428c60736dc97fb96daf27",
"reference": "7bedb718b633355272428c60736dc97fb96daf27",
"shasum": ""
},
"require": {
@@ -497,7 +497,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.68.1"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.68.5"
},
"funding": [
{
@@ -505,7 +505,7 @@
"type": "github"
}
],
"time": "2025-01-17T09:20:36+00:00"
"time": "2025-01-30T17:00:50+00:00"
},
{
"name": "psr/container",

View File

@@ -1,16 +1,29 @@
# Configuration for Label Actions - https://github.com/dessant/label-actions
# The `feature` label is added to issues
fixed:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |
Hi there!
This is an automatic reply. `Share and enjoy`
This issue has been marked as fixed. Thanks for reporting! A new version will be released in due time. Unfortunately, [I cannot give an estimate](https://docs.firefly-iii.org/references/faq/firefly-iii/general/#when-will-you-release-version-the-next-version), but [the roadmap](https://roadmap.firefly-iii.org/) is available for your reading pleasure.
There is no need to close the issue. It will be closed automatically.
Thank you for your contributions.
feature:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
unlabel: feature
comment: |
Hi there!
Hi there!
This is an automatic reply. `Share and enjoy`
This issue has been marked as a feature request. The requested (new) feature will become a part of Firefly III or the data importer in due course.
This issue has been marked as a feature request.
If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates.
@@ -20,13 +33,13 @@ epic:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |
Hi there!
Hi there!
This is an automatic reply. `Share and enjoy`
This issue has been marked as an epic. In epics, large amounts of works are collected that will be part of a major new feature. If you have more ideas that could be a part of this epic, feel free to reply.
*However*, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted.
*However*, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted.
If you are merely interested in this epic's progress, you can subscribe to this issue to get updates.
@@ -37,11 +50,11 @@ enhancement:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |
Hi there!
Hi there!
This is an automatic reply. `Share and enjoy`
This issue has been marked as an enhancement. The requested enhancement to an existing feature will become a part of Firefly III or the data importer in due course.
This issue has been marked as an enhancement.
If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates.
@@ -51,7 +64,7 @@ triage:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |
Hi there!
Hi there!
This is an automatic reply. `Share and enjoy`
@@ -62,7 +75,7 @@ triage:
needs-moar-debug:
issues:
comment: |
Hi there!
Hi there!
This is an automatic reply. `Share and enjoy`
@@ -80,7 +93,7 @@ needs-moar-debug:
needs-moar-logs:
issues:
comment: |
Hi there!
Hi there!
This is an automatic reply. `Share and enjoy`
@@ -96,7 +109,7 @@ needs-moar-logs:
v2-layout-issue:
issues:
comment: |
Hi there!
Hi there!
This is an automatic reply. `Share and enjoy`

View File

@@ -8,7 +8,7 @@ jobs:
command_and_close:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/closed-issue-message@v1
- uses: aws-actions/closed-issue-message@v2
with:
message: |
Hi there! This is an automatic reply. `Share and enjoy`

View File

@@ -3,6 +3,9 @@
Over time, many people have contributed to Firefly III. Their efforts are not always visible, but always remembered and appreciated.
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
- SoftBrix
## 2024
- Sobuno
- TasneemTantawy

View File

@@ -41,6 +41,8 @@ use Illuminate\Http\JsonResponse;
class AccountController extends Controller
{
use AccountFilter;
// this array only exists to test if the constructor will use it properly.
protected array $accepts = ['application/json', 'application/vnd.api+json'];
/** @var array<int, string> */
private array $balanceTypes;
@@ -81,15 +83,18 @@ class AccountController extends Controller
$return = [];
$result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit'));
// set date to end-of-day for account balance.
$date->endOfDay();
/** @var Account $account */
foreach ($result as $account) {
$nameWithBalance = $account->name;
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
$useCurrency = $currency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
$balance = Steam::finalAccountBalance($account, $date);
$key = $this->convertToNative && $currency->id !== $this->defaultCurrency->id ? 'native_balance' : 'balance';
$useCurrency = $this->convertToNative && $currency->id !== $this->defaultCurrency->id ? $this->defaultCurrency : $currency;
$key = $this->convertToNative && $currency->id !== $this->nativeCurrency->id ? 'native_balance' : 'balance';
$useCurrency = $this->convertToNative && $currency->id !== $this->nativeCurrency->id ? $this->nativeCurrency : $currency;
$amount = $balance[$key] ?? '0';
$nameWithBalance = sprintf(
'%s (%s)',
@@ -123,6 +128,6 @@ class AccountController extends Controller
}
);
return response()->json($return);
return response()->api($return);
}
}

View File

@@ -74,6 +74,6 @@ class BillController extends Controller
}
);
return response()->json($filtered->toArray());
return response()->api($filtered->toArray());
}
}

View File

@@ -73,6 +73,6 @@ class BudgetController extends Controller
}
);
return response()->json($filtered);
return response()->api($filtered->toArray());
}
}

View File

@@ -73,6 +73,6 @@ class CategoryController extends Controller
}
);
return response()->json($filtered);
return response()->api($filtered->toArray());
}
}

View File

@@ -77,7 +77,7 @@ class CurrencyController extends Controller
];
}
return response()->json($result);
return response()->api($result);
}
/**
@@ -103,6 +103,6 @@ class CurrencyController extends Controller
];
}
return response()->json($result);
return response()->api($result);
}
}

View File

@@ -75,6 +75,6 @@ class ObjectGroupController extends Controller
];
}
return response()->json($return);
return response()->api($return);
}
}

View File

@@ -87,7 +87,7 @@ class PiggyBankController extends Controller
];
}
return response()->json($response);
return response()->api($response);
}
/**
@@ -124,6 +124,6 @@ class PiggyBankController extends Controller
];
}
return response()->json($response);
return response()->api($response);
}
}

View File

@@ -73,6 +73,6 @@ class RecurrenceController extends Controller
];
}
return response()->json($response);
return response()->api($response);
}
}

View File

@@ -72,6 +72,6 @@ class RuleController extends Controller
];
}
return response()->json($response);
return response()->api($response);
}
}

View File

@@ -72,6 +72,6 @@ class RuleGroupController extends Controller
];
}
return response()->json($response);
return response()->api($response);
}
}

View File

@@ -75,6 +75,6 @@ class TagController extends Controller
];
}
return response()->json($array);
return response()->api($array);
}
}

View File

@@ -84,7 +84,7 @@ class TransactionController extends Controller
];
}
return response()->json($array);
return response()->api($array);
}
/**
@@ -122,6 +122,6 @@ class TransactionController extends Controller
];
}
return response()->json($array);
return response()->api($array);
}
}

View File

@@ -72,6 +72,6 @@ class TransactionTypeController extends Controller
];
}
return response()->json($array);
return response()->api($array);
}
}

View File

@@ -32,6 +32,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Preference;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ApiSupport;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
@@ -80,6 +81,10 @@ class AccountController extends Controller
/** @var Carbon $end */
$end = $dates['end'];
// set dates to end of day + start of day:
$start->startOfDay();
$end->endOfDay();
// user's preferences
$defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
@@ -97,8 +102,8 @@ class AccountController extends Controller
/** @var Account $account */
foreach ($accounts as $account) {
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
$field = $this->convertToNative && $currency->id !== $this->defaultCurrency->id ? 'native_balance' : 'balance';
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
$field = $this->convertToNative && $currency->id !== $this->nativeCurrency->id ? 'native_balance' : 'balance';
$currentSet = [
'label' => $account->name,
'currency_id' => (string) $currency->id,
@@ -113,7 +118,7 @@ class AccountController extends Controller
];
// TODO this code is also present in the V2 chart account controller so this method is due to be deprecated.
$currentStart = clone $start;
$range = app('steam')->finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
$previous = array_values($range)[0][$field];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Controllers;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Exceptions\BadHttpHeaderException;
use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
@@ -59,13 +60,15 @@ abstract class Controller extends BaseController
use DispatchesJobs;
use ValidatesRequests;
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected const string JSON_CONTENT_TYPE = 'application/json';
/** @var array<int, string> */
protected array $allowedSort;
protected ParameterBag $parameters;
protected bool $convertToNative = false;
protected TransactionCurrency $defaultCurrency;
protected bool $convertToNative = false;
protected array $accepts = ['application/json', 'application/vnd.api+json'];
protected TransactionCurrency $nativeCurrency;
/**
* Controller constructor.
@@ -80,11 +83,17 @@ abstract class Controller extends BaseController
if (auth()->check()) {
$language = Steam::getLanguage();
$this->convertToNative = Amount::convertToNative();
$this->defaultCurrency = Amount::getNativeCurrency();
$this->nativeCurrency = Amount::getNativeCurrency();
app()->setLocale($language);
}
// filter down what this endpoint accepts.
if (!$request->accepts($this->accepts)) {
throw new BadHttpHeaderException(sprintf('Sorry, Accept header "%s" is not something this endpoint can provide.', $request->header('Accept')));
}
return $next($request);
}
);
@@ -148,7 +157,15 @@ abstract class Controller extends BaseController
$value = null;
}
if (null !== $value) {
$bag->set($integer, (int) $value);
$value = (int) $value;
if ($value < 1) {
$value = 1;
}
if ($value > 2 ** 16) {
$value = 2 ** 16;
}
$bag->set($integer, $value);
}
if (null === $value
&& 'limit' === $integer // @phpstan-ignore-line

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\Budget;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Budget;
@@ -208,6 +209,8 @@ class ListController extends Controller
$collector = app(GroupCollectorInterface::class);
$collector
->setUser($admin)
// withdrawals only
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
// filter on budget.
->withoutBudget()
// all info needed for the API:

View File

@@ -2,7 +2,7 @@
/*
* DestroyController.php
* Copyright (c) 2024 james@firefly-iii.org.
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,10 +22,13 @@
declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Model\ExchangeRate\DestroyRequest;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\DestroyRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\UserGroups\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
@@ -36,6 +39,8 @@ class DestroyController extends Controller
{
use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::OWNER];
public const string RESOURCE_KEY = 'exchange-rates';
private ExchangeRateRepositoryInterface $repository;
@@ -56,6 +61,9 @@ class DestroyController extends Controller
public function destroy(DestroyRequest $request, TransactionCurrency $from, TransactionCurrency $to): JsonResponse
{
$date = $request->getDate();
if (null === $date) {
throw new ValidationException('Date is required');
}
$rate = $this->repository->getSpecificRateOnDate($from, $to, $date);
if (null === $rate) {
throw new NotFoundHttpException();
@@ -64,4 +72,11 @@ class DestroyController extends Controller
return response()->json([], 204);
}
public function destroySingle(CurrencyExchangeRate $exchangeRate): JsonResponse
{
$this->repository->deleteRate($exchangeRate);
return response()->json([], 204);
}
}

View File

@@ -1,8 +1,8 @@
<?php
/*
* ShowController.php
* Copyright (c) 2023 james@firefly-iii.org
* IndexController.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,12 +17,12 @@
* 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/>.
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Repositories\UserGroups\ExchangeRate\ExchangeRateRepositoryInterface;
@@ -38,7 +38,7 @@ class IndexController extends Controller
{
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'exchange-rates';
public const string RESOURCE_KEY = 'currency_exchange_rates';
private ExchangeRateRepositoryInterface $repository;
@@ -57,14 +57,11 @@ class IndexController extends Controller
public function index(): JsonResponse
{
$piggies = $this->repository->getAll();
$entries = $this->repository->getAll();
$pageSize = $this->parameters->get('limit');
$count = $piggies->count();
$piggies = $piggies->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($piggies, $count, $pageSize, $this->parameters->get('page'));
var_dump('here we are');
$count = $entries->count();
$entries = $entries->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($entries, $count, $pageSize, $this->parameters->get('page'));
$transformer = new ExchangeRateTransformer();
$transformer->setParameters($this->parameters); // give params to transformer

View File

@@ -2,7 +2,7 @@
/*
* ShowController.php
* Copyright (c) 2023 james@firefly-iii.org
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,14 +17,15 @@
* 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/>.
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\UserGroups\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
@@ -73,4 +74,15 @@ class ShowController extends Controller
->header('Content-Type', self::CONTENT_TYPE)
;
}
public function showSingle(CurrencyExchangeRate $exchangeRate): JsonResponse
{
$transformer = new ExchangeRateTransformer();
$transformer->setParameters($this->parameters);
return response()
->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))
->header('Content-Type', self::CONTENT_TYPE)
;
}
}

View File

@@ -1,8 +1,8 @@
<?php
/*
* DestroyController.php
* Copyright (c) 2024 james@firefly-iii.org.
* StoreController.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,10 +22,10 @@
declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreRequest;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Model\ExchangeRate\StoreRequest;
use FireflyIII\Repositories\UserGroups\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Transformers\V2\ExchangeRateTransformer;

View File

@@ -1,8 +1,8 @@
<?php
/*
* DestroyController.php
* Copyright (c) 2024 james@firefly-iii.org.
* UpdateController.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,10 +22,10 @@
declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\UpdateRequest;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Model\ExchangeRate\UpdateRequest;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Repositories\UserGroups\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;

View File

@@ -72,13 +72,6 @@ class UpdateController extends Controller
{
app('log')->debug('Now in update routine for transaction group');
$data = $request->getAll();
// Fixes 8750.
$transactions = $data['transactions'] ?? [];
foreach ($transactions as $index => $info) {
unset($data['transactions'][$index]['type']);
}
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$manager = $this->getManager();

View File

@@ -236,7 +236,7 @@ class ListController extends Controller
// get list of budgets. Count it and split it.
/** @var RecurringRepositoryInterface $recurringRepos */
$recurringRepos = app(RecurringRepositoryInterface::class);
$unfiltered = $recurringRepos->getAll();
$unfiltered = $recurringRepos->get();
// filter selection
$collection = $unfiltered->filter(

View File

@@ -107,7 +107,7 @@ class ShowController extends Controller
/** @var User $user */
$user = auth()->user();
$manager = $this->getManager();
$this->parameters->set('defaultCurrency', $this->defaultCurrency);
$this->parameters->set('nativeCurrency', $this->nativeCurrency);
// update fields with user info.
$currency->refreshForUser($user);
@@ -123,7 +123,7 @@ class ShowController extends Controller
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/getDefaultCurrency
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/getNativeCurrency
*
* Show a currency.
*
@@ -134,7 +134,7 @@ class ShowController extends Controller
/** @var User $user */
$user = auth()->user();
$manager = $this->getManager();
$currency = $this->defaultCurrency;
$currency = $this->nativeCurrency;
// update fields with user info.
$currency->refreshForUser($user);

View File

@@ -100,7 +100,7 @@ class UpdateController extends Controller
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/defaultCurrency
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/nativeCurrency
*
* Make the currency a default currency.
*

View File

@@ -58,7 +58,7 @@ class AboutController extends Controller
'driver' => $currentDriver,
];
return response()->api(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
return response()->api(['data' => $data])->header('Content-Type', self::JSON_CONTENT_TYPE);
}
/**

View File

@@ -86,7 +86,7 @@ class ConfigurationController extends Controller
];
}
return response()->json($return);
return response()->api($return);
}
/**
@@ -142,7 +142,7 @@ class ConfigurationController extends Controller
];
}
return response()->json(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
return response()->api(['data' => $data])->header('Content-Type', self::JSON_CONTENT_TYPE);
}
/**
@@ -173,6 +173,6 @@ class ConfigurationController extends Controller
'editable' => true,
];
return response()->json(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
return response()->api(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
}
}

View File

@@ -52,8 +52,8 @@ class CronController extends Controller
if (true === config('cer.download_enabled')) {
$return['exchange_rates'] = $this->exchangeRatesCronJob($config['force'], $config['date']);
}
$return['bill_warnings'] = $this->billWarningCronJob($config['force'], $config['date']);
$return['bill_notifications'] = $this->billWarningCronJob($config['force'], $config['date']);
return response()->json($return);
return response()->api($return);
}
}

View File

@@ -32,7 +32,6 @@ use FireflyIII\Models\Preference;
use FireflyIII\Transformers\PreferenceTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
@@ -86,7 +85,7 @@ class PreferencesController extends Controller
$manager = $this->getManager();
if ('currencyPreference' === $preference->name) {
throw new FireflyException('Please use api/v1/currencies/default instead.');
throw new FireflyException('Please use api/v1/currencies/native instead.');
}
/** @var PreferenceTransformer $transformer */
@@ -98,34 +97,6 @@ class PreferencesController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}
/**
* TODO This endpoint is not documented.
*
* Return a single preference by name.
*
* @param Collection<int, Preference> $collection
*/
public function showList(Collection $collection): JsonResponse
{
$manager = $this->getManager();
$count = $collection->count();
$pageSize = $this->parameters->get('limit');
$preferences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// make paginator:
$paginator = new LengthAwarePaginator($preferences, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.preferences.show-list').$this->buildParams());
/** @var PreferenceTransformer $transformer */
$transformer = app(PreferenceTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($preferences, $transformer, self::RESOURCE_KEY);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/preferences/storePreference
@@ -161,7 +132,7 @@ class PreferencesController extends Controller
public function update(PreferenceUpdateRequest $request, Preference $preference): JsonResponse
{
if ('currencyPreference' === $preference->name) {
throw new FireflyException('Please use api/v1/currencies/default instead.');
throw new FireflyException('Please use api/v1/currencies/native instead.');
}
$manager = $this->getManager();

View File

@@ -58,6 +58,7 @@ class AutocompleteRequest extends FormRequest
public function rules(): array
{
return [
'date' => 'date|after:1900-01-01|before:2099-12-31',
];
}
}

View File

@@ -24,7 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Data;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -49,7 +49,7 @@ class DateRequest extends FormRequest
$start->startOfDay();
$end->endOfDay();
if ($start->diffInYears($end, true) > 5) {
throw new FireflyException('Date range out of range.');
throw new ValidationException('Date range out of range.');
}
return [

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Account;
use FireflyIII\Models\Account;
use FireflyIII\Models\Location;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Rules\UniqueAccountNumber;
use FireflyIII\Rules\UniqueIban;
@@ -33,6 +34,8 @@ use FireflyIII\Support\Request\AppendsLocationData;
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 UpdateRequest
@@ -112,4 +115,36 @@ class UpdateRequest extends FormRequest
return Location::requestRules($rules);
}
/**
* Configure the validator instance with special rules for after the basic validation rules.
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator): void {
// validate start before end only if both are there.
$data = $validator->getData();
/** @var Account $account */
$account = $this->route()->parameter('account');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$currency = $repository->getAccountCurrency($account);
// how many piggies are attached?
$piggyBanks = $account->piggyBanks()->count();
if ($piggyBanks > 0 && array_key_exists('currency_code', $data) && $data['currency_code'] !== $currency->code) {
$validator->errors()->add('currency_code', (string) trans('validation.piggy_no_change_currency'));
}
if ($piggyBanks > 0 && array_key_exists('currency_id', $data) && (int) $data['currency_id'] !== $currency->id) {
$validator->errors()->add('currency_id', (string) trans('validation.piggy_no_change_currency'));
}
}
);
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
}

View File

@@ -66,8 +66,8 @@ class Request extends FormRequest
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'amount' => ['nullable', new IsValidPositiveAmount()],
'start' => 'date',
'end' => 'date',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after:1900-01-01|before:2099-12-31',
];
}

View File

@@ -78,9 +78,9 @@ class StoreRequest extends FormRequest
'amount_max' => ['required', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'date' => 'date|required',
'end_date' => 'nullable|date|after:date',
'extension_date' => 'nullable|date|after:date',
'date' => 'date|required|after:1900-01-01|before:2099-12-31',
'end_date' => 'nullable|date|after:date|after:1900-01-01|before:2099-12-31',
'extension_date' => 'nullable|date|after:date|after:1900-01-01|before:2099-12-31',
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly|required',
'skip' => 'min:0|max:31|numeric',
'active' => [new IsBoolean()],
@@ -95,16 +95,40 @@ class StoreRequest extends FormRequest
{
$validator->after(
static function (Validator $validator): void {
$data = $validator->getData();
$min = (string) ($data['amount_min'] ?? '0');
$max = (string) ($data['amount_max'] ?? '0');
$data = $validator->getData();
$min = $data['amount_min'] ?? '0';
$max = $data['amount_max'] ?? '0';
if (1 === bccomp($min, $max)) {
if (is_array($min) || is_array($max)) {
$validator->errors()->add('amount_min', (string) trans('validation.generic_invalid'));
$validator->errors()->add('amount_max', (string) trans('validation.generic_invalid'));
$min = '0';
$max = '0';
}
$result = false;
try {
$result = bccomp($min, $max);
} catch (\ValueError $e) {
Log::error($e->getMessage());
$validator->errors()->add('amount_min', (string) trans('validation.generic_invalid'));
$validator->errors()->add('amount_max', (string) trans('validation.generic_invalid'));
}
if (1 === $result) {
$validator->errors()->add('amount_min', (string) trans('validation.amount_min_over_max'));
}
}
);
if ($validator->fails()) {
$failed = false;
try {
$failed = $validator->fails();
} catch (\TypeError $e) {
Log::error($e->getMessage());
$failed = false;
}
if ($failed) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -81,9 +81,9 @@ class UpdateRequest extends FormRequest
'amount_max' => ['nullable', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'date' => 'date',
'end_date' => 'date|after:date',
'extension_date' => 'date|after:date',
'date' => 'date|after:1900-01-01|before:2099-12-31',
'end_date' => 'date|after:date|after:1900-01-01|before:2099-12-31',
'extension_date' => 'date|after:date|after:1900-01-01|before:2099-12-31',
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly',
'skip' => 'min:0|max:31|numeric',
'active' => [new IsBoolean()],

View File

@@ -67,8 +67,8 @@ class UpdateRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after:1900-01-01|before:2099-12-31',
'amount' => ['nullable', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',

View File

@@ -2,7 +2,7 @@
/*
* DestroyRequest.php
* Copyright (c) 2024 james@firefly-iii.org.
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,7 +22,7 @@
declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate;
use Carbon\Carbon;
use FireflyIII\Support\Request\ChecksLogin;
@@ -34,7 +34,7 @@ class DestroyRequest extends FormRequest
use ChecksLogin;
use ConvertsDataTypes;
public function getDate(): Carbon
public function getDate(): ?Carbon
{
return $this->getCarbonDate('date');
}

View File

@@ -1,8 +1,8 @@
<?php
/*
* DestroyRequest.php
* Copyright (c) 2024 james@firefly-iii.org.
* StoreRequest.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,7 +22,7 @@
declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate;
use Carbon\Carbon;
use FireflyIII\Models\TransactionCurrency;

View File

@@ -1,8 +1,8 @@
<?php
/*
* DestroyRequest.php
* Copyright (c) 2024 james@firefly-iii.org.
* UpdateRequest.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,7 +22,7 @@
declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Model\ExchangeRate;
namespace FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate;
use Carbon\Carbon;
use FireflyIII\Support\Request\ChecksLogin;

View File

@@ -154,7 +154,7 @@ class UpdateRequest extends FormRequest
return [
'title' => sprintf('min:1|max:255|uniqueObjectForUser:recurrences,title,%d', $recurrence->id),
'description' => 'min:1|max:32768',
'first_date' => 'date',
'first_date' => 'date|after:1900-01-01|before:2099-12-31',
'apply_rules' => [new IsBoolean()],
'active' => [new IsBoolean()],
'repeat_until' => 'nullable|date',

View File

@@ -71,8 +71,8 @@ class TestRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date|after_or_equal:start',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
'accounts' => '',
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
];

View File

@@ -65,8 +65,8 @@ class TriggerRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date|after_or_equal:start',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
'accounts' => '',
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
];

View File

@@ -65,8 +65,8 @@ class TestRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date|after_or_equal:start',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
'accounts' => '',
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
];

View File

@@ -69,8 +69,8 @@ class TriggerRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date|after_or_equal:start',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
];
}
}

View File

@@ -62,7 +62,7 @@ class StoreRequest extends FormRequest
$rules = [
'tag' => 'required|min:1|uniqueObjectForUser:tags,tag|max:1024',
'description' => 'min:1|nullable|max:32768',
'date' => 'date|nullable',
'date' => 'date|nullable|after:1900-01-01|before:2099-12-31',
];
return Location::requestRules($rules);

View File

@@ -63,11 +63,10 @@ class UpdateRequest extends FormRequest
{
/** @var Tag $tag */
$tag = $this->route()->parameter('tagOrId');
// TODO check if uniqueObjectForUser is obsolete
$rules = [
'tag' => 'min:1|max:1024|uniqueObjectForUser:tags,tag,'.$tag->id,
'description' => 'min:1|nullable|max:32768',
'date' => 'date|nullable',
'date' => 'date|nullable|after:1900-01-01|before:2099-12-31',
];
return Location::requestRules($rules);

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\Transaction;
use FireflyIII\Models\Location;
use FireflyIII\Rules\BelongsUser;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Rules\IsDateOrTime;
@@ -82,82 +83,87 @@ class StoreRequest extends FormRequest
foreach ($this->get('transactions') as $transaction) {
$object = new NullArrayObject($transaction);
$return[] = [
'type' => $this->clearString($object['type']),
'date' => $this->dateFromValue($object['date']),
'order' => $this->integerFromValue((string) $object['order']),
'type' => $this->clearString($object['type']),
'date' => $this->dateFromValue($object['date']),
'order' => $this->integerFromValue((string) $object['order']),
'currency_id' => $this->integerFromValue((string) $object['currency_id']),
'currency_code' => $this->clearString((string) $object['currency_code']),
'currency_id' => $this->integerFromValue((string) $object['currency_id']),
'currency_code' => $this->clearString((string) $object['currency_code']),
// location
'latitude' => $this->floatFromValue((string) $object['latitude']),
'longitude' => $this->floatFromValue((string) $object['longitude']),
'zoom_level' => $this->integerFromValue((string) $object['zoom_level']),
// foreign currency info:
'foreign_currency_id' => $this->integerFromValue((string) $object['foreign_currency_id']),
'foreign_currency_code' => $this->clearString((string) $object['foreign_currency_code']),
'foreign_currency_id' => $this->integerFromValue((string) $object['foreign_currency_id']),
'foreign_currency_code' => $this->clearString((string) $object['foreign_currency_code']),
// amount and foreign amount. Cannot be 0.
'amount' => $this->clearString((string) $object['amount']),
'foreign_amount' => $this->clearString((string) $object['foreign_amount']),
'amount' => $this->clearString((string) $object['amount']),
'foreign_amount' => $this->clearString((string) $object['foreign_amount']),
// description.
'description' => $this->clearString($object['description']),
'description' => $this->clearString($object['description']),
// source of transaction. If everything is null, assume cash account.
'source_id' => $this->integerFromValue((string) $object['source_id']),
'source_name' => $this->clearString((string) $object['source_name']),
'source_iban' => $this->clearIban((string) $object['source_iban']),
'source_number' => $this->clearString((string) $object['source_number']),
'source_bic' => $this->clearString((string) $object['source_bic']),
'source_id' => $this->integerFromValue((string) $object['source_id']),
'source_name' => $this->clearString((string) $object['source_name']),
'source_iban' => $this->clearIban((string) $object['source_iban']),
'source_number' => $this->clearString((string) $object['source_number']),
'source_bic' => $this->clearString((string) $object['source_bic']),
// destination of transaction. If everything is null, assume cash account.
'destination_id' => $this->integerFromValue((string) $object['destination_id']),
'destination_name' => $this->clearString((string) $object['destination_name']),
'destination_iban' => $this->clearIban((string) $object['destination_iban']),
'destination_number' => $this->clearString((string) $object['destination_number']),
'destination_bic' => $this->clearString((string) $object['destination_bic']),
'destination_id' => $this->integerFromValue((string) $object['destination_id']),
'destination_name' => $this->clearString((string) $object['destination_name']),
'destination_iban' => $this->clearIban((string) $object['destination_iban']),
'destination_number' => $this->clearString((string) $object['destination_number']),
'destination_bic' => $this->clearString((string) $object['destination_bic']),
// budget info
'budget_id' => $this->integerFromValue((string) $object['budget_id']),
'budget_name' => $this->clearString((string) $object['budget_name']),
'budget_id' => $this->integerFromValue((string) $object['budget_id']),
'budget_name' => $this->clearString((string) $object['budget_name']),
// category info
'category_id' => $this->integerFromValue((string) $object['category_id']),
'category_name' => $this->clearString((string) $object['category_name']),
'category_id' => $this->integerFromValue((string) $object['category_id']),
'category_name' => $this->clearString((string) $object['category_name']),
// journal bill reference. Optional. Will only work for withdrawals
'bill_id' => $this->integerFromValue((string) $object['bill_id']),
'bill_name' => $this->clearString((string) $object['bill_name']),
'bill_id' => $this->integerFromValue((string) $object['bill_id']),
'bill_name' => $this->clearString((string) $object['bill_name']),
// piggy bank reference. Optional. Will only work for transfers
'piggy_bank_id' => $this->integerFromValue((string) $object['piggy_bank_id']),
'piggy_bank_name' => $this->clearString((string) $object['piggy_bank_name']),
'piggy_bank_id' => $this->integerFromValue((string) $object['piggy_bank_id']),
'piggy_bank_name' => $this->clearString((string) $object['piggy_bank_name']),
// some other interesting properties
'reconciled' => $this->convertBoolean((string) $object['reconciled']),
'notes' => $this->clearStringKeepNewlines((string) $object['notes']),
'tags' => $this->arrayFromValue($object['tags']),
'reconciled' => $this->convertBoolean((string) $object['reconciled']),
'notes' => $this->clearStringKeepNewlines((string) $object['notes']),
'tags' => $this->arrayFromValue($object['tags']),
// all custom fields:
'internal_reference' => $this->clearString((string) $object['internal_reference']),
'external_id' => $this->clearString((string) $object['external_id']),
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->clearString((string) $object['bunq_payment_id']),
'external_url' => $this->clearString((string) $object['external_url']),
'internal_reference' => $this->clearString((string) $object['internal_reference']),
'external_id' => $this->clearString((string) $object['external_id']),
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->clearString((string) $object['bunq_payment_id']),
'external_url' => $this->clearString((string) $object['external_url']),
'sepa_cc' => $this->clearString((string) $object['sepa_cc']),
'sepa_ct_op' => $this->clearString((string) $object['sepa_ct_op']),
'sepa_ct_id' => $this->clearString((string) $object['sepa_ct_id']),
'sepa_db' => $this->clearString((string) $object['sepa_db']),
'sepa_country' => $this->clearString((string) $object['sepa_country']),
'sepa_ep' => $this->clearString((string) $object['sepa_ep']),
'sepa_ci' => $this->clearString((string) $object['sepa_ci']),
'sepa_batch_id' => $this->clearString((string) $object['sepa_batch_id']),
'sepa_cc' => $this->clearString((string) $object['sepa_cc']),
'sepa_ct_op' => $this->clearString((string) $object['sepa_ct_op']),
'sepa_ct_id' => $this->clearString((string) $object['sepa_ct_id']),
'sepa_db' => $this->clearString((string) $object['sepa_db']),
'sepa_country' => $this->clearString((string) $object['sepa_country']),
'sepa_ep' => $this->clearString((string) $object['sepa_ep']),
'sepa_ci' => $this->clearString((string) $object['sepa_ci']),
'sepa_batch_id' => $this->clearString((string) $object['sepa_batch_id']),
// custom date fields. Must be Carbon objects. Presence is optional.
'interest_date' => $this->dateFromValue($object['interest_date']),
'book_date' => $this->dateFromValue($object['book_date']),
'process_date' => $this->dateFromValue($object['process_date']),
'due_date' => $this->dateFromValue($object['due_date']),
'payment_date' => $this->dateFromValue($object['payment_date']),
'invoice_date' => $this->dateFromValue($object['invoice_date']),
'interest_date' => $this->dateFromValue($object['interest_date']),
'book_date' => $this->dateFromValue($object['book_date']),
'process_date' => $this->dateFromValue($object['process_date']),
'due_date' => $this->dateFromValue($object['due_date']),
'payment_date' => $this->dateFromValue($object['payment_date']),
'invoice_date' => $this->dateFromValue($object['invoice_date']),
];
}
@@ -171,6 +177,7 @@ class StoreRequest extends FormRequest
{
app('log')->debug('Collect rules of TransactionStoreRequest');
$validProtocols = config('firefly.valid_url_protocols');
$locationRules = Location::requestRules([]);
return [
// basic fields for group:
@@ -178,6 +185,11 @@ class StoreRequest extends FormRequest
'error_if_duplicate_hash' => [new IsBoolean()],
'apply_rules' => [new IsBoolean()],
// location rules
'transactions.*.latitude' => $locationRules['latitude'],
'transactions.*.longitude' => $locationRules['longitude'],
'transactions.*.zoom_level' => $locationRules['zoom_level'],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime()],

View File

@@ -57,6 +57,10 @@ class CronRequest extends FormRequest
if ($this->has('date')) {
$data['date'] = $this->getCarbonDate('date');
}
// catch NULL.
if (null === $data['date']) {
$data['date'] = today(config('app.timezone'));
}
return $data;
}
@@ -68,7 +72,7 @@ class CronRequest extends FormRequest
{
return [
'force' => 'in:true,false',
'date' => 'date',
'date' => 'nullable|date|after:1900-01-01|before:2099-12-31',
];
}
}

View File

@@ -31,6 +31,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Chart\ChartData;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
@@ -118,7 +119,7 @@ class AccountController extends Controller
'native_entries' => [],
];
$currentStart = clone $params['start'];
$range = app('steam')->finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
$previous = array_values($range)[0]['balance'];
$previousNative = array_values($range)[0]['native_balance'];

View File

@@ -109,8 +109,8 @@ class InfiniteListRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date|after:start',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after:start|after:1900-01-01|before:2099-12-31',
'start_row' => 'integer|min:0|max:4294967296',
'end_row' => 'integer|min:0|max:4294967296|gt:start_row',
];

View File

@@ -84,8 +84,8 @@ class ListRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date|after:start',
'start' => 'date|after:1900-01-01|before:2099-12-31',
'end' => 'date|after:start|after:1900-01-01|before:2099-12-31',
];
}
}

View File

@@ -102,7 +102,14 @@ class CorrectsNativeAmounts extends Command
{
$set = $userGroup->accounts()->where(function (EloquentBuilder $q): void {
$q->whereNotNull('virtual_balance');
$q->orWhere('virtual_balance', '!=', '');
// this needs a different piece of code for postgres.
if ('pgsql' === config('database.default')) {
$q->orWhere(DB::raw('CAST(virtual_balance AS TEXT)'), '!=', '');
}
if ('pgsql' !== config('database.default')) {
$q->orWhere('virtual_balance', '!=', '');
}
})->get();
/** @var Account $account */
@@ -218,7 +225,6 @@ class CorrectsNativeAmounts extends Command
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Exceptions;
use Brick\Math\Exception\NumberFormatException;
use FireflyIII\Jobs\MailError;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
@@ -124,7 +125,7 @@ class Handler extends ExceptionHandler
if ($e instanceof BadRequestHttpException) {
app('log')->debug('Return JSON BadRequestHttpException.');
return response()->json(['message' => $e->getMessage(), 'exception' => 'BadRequestHttpException'], 400);
return response()->json(['message' => $e->getMessage(), 'exception' => 'HttpException'], 400);
}
if ($e instanceof BadHttpHeaderException) {
@@ -133,6 +134,14 @@ class Handler extends ExceptionHandler
return response()->json(['message' => $e->getMessage(), 'exception' => 'BadHttpHeaderException'], $e->statusCode);
}
if (($e instanceof ValidationException || $e instanceof NumberFormatException) && $expectsJson) {
$errorCode = 422;
return response()->json(
['message' => sprintf('Validation exception: %s', $e->getMessage()), 'errors' => ['field' => 'Field is invalid']],
$errorCode
);
}
if ($expectsJson) {
$errorCode = 500;
@@ -156,7 +165,7 @@ class Handler extends ExceptionHandler
app('log')->debug(sprintf('Return JSON %s.', get_class($e)));
return response()->json(
['message' => sprintf('Internal Firefly III Exception: %s', $e->getMessage()), 'exception' => get_class($e)],
['message' => sprintf('Internal Firefly III Exception: %s', $e->getMessage()), 'exception' => 'UndisclosedException'],
$errorCode
);
}

View File

@@ -576,7 +576,7 @@ class TransactionJournalFactory
private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void
{
if (true === $data['store_location']) {
if (null !== $data['longitude'] && null !== $data['latitude'] && null !== $data['zoom_level']) {
$location = new Location();
$location->longitude = $data['longitude'];
$location->latitude = $data['latitude'];

View File

@@ -54,6 +54,9 @@ class MonthReportGenerator implements ReportGeneratorInterface
$dayBefore = clone $this->start;
$dayBefore->subDay();
// move to end of day
$dayBefore->endOfDay();
/** @var Account $account */
foreach ($this->accounts as $account) {
// balance the day before:

View File

@@ -61,8 +61,12 @@ class PreferencesEventHandler
$this->resetTransactions($event->userGroup);
// fire laravel command to recalculate them all.
if (Amount::convertToNative()) {
Log::debug('Will now convert to native.');
Artisan::call('correction:recalculate-native-amounts');
return;
}
Log::debug('Will NOT convert to native.');
}
private function resetPiggyBanks(UserGroup $userGroup): void

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Account;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
@@ -52,6 +53,7 @@ class AccountObserver
$currency = $repository->getAccountCurrency($account);
if (null !== $currency && $currency->id !== $userCurrency->id && '' !== (string) $account->virtual_balance && 0 !== bccomp($account->virtual_balance, '0')) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($account->user->userGroup);
$converter->setIgnoreSettings(true);
$account->native_virtual_balance = $converter->convert($currency, $userCurrency, today(), $account->virtual_balance);
@@ -72,12 +74,15 @@ class AccountObserver
// app('log')->debug('Observe "deleting" of an account.');
$account->accountMeta()->delete();
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($account->user);
/** @var PiggyBank $piggy */
foreach ($account->piggyBanks()->get() as $piggy) {
$piggy->accounts()->detach($account);
}
foreach ($account->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
foreach ($account->transactions()->get() as $transaction) {
$transaction->delete();

View File

@@ -52,6 +52,7 @@ class AutoBudgetObserver
$autoBudget->native_amount = null;
if ($autoBudget->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($autoBudget->budget->user->userGroup);
$converter->setIgnoreSettings(true);
$autoBudget->native_amount = $converter->convert($autoBudget->transactionCurrency, $userCurrency, today(), $autoBudget->amount);
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
@@ -41,9 +42,12 @@ class BillObserver
public function deleting(Bill $bill): void
{
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($bill->user);
// app('log')->debug('Observe "deleting" of a bill.');
foreach ($bill->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
$bill->notes()->delete();
}
@@ -64,6 +68,7 @@ class BillObserver
$bill->native_amount_max = null;
if ($bill->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($bill->user->userGroup);
$converter->setIgnoreSettings(true);
$bill->native_amount_min = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_min);
$bill->native_amount_max = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_max);

View File

@@ -54,6 +54,7 @@ class BudgetLimitObserver
$budgetLimit->native_amount = null;
if ($budgetLimit->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($budgetLimit->budget->user->userGroup);
$converter->setIgnoreSettings(true);
$budgetLimit->native_amount = $converter->convert($budgetLimit->transactionCurrency, $userCurrency, today(), $budgetLimit->amount);
}

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
/**
* Class BudgetObserver
@@ -34,8 +35,12 @@ class BudgetObserver
public function deleting(Budget $budget): void
{
app('log')->debug('Observe "deleting" of a budget.');
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($budget->user);
foreach ($budget->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
$budgetLimits = $budget->budgetlimits()->get();

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
/**
* Class CategoryObserver
@@ -33,8 +34,12 @@ class CategoryObserver
public function deleting(Category $category): void
{
app('log')->debug('Observe "deleting" of a category.');
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($category->user);
foreach ($category->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
$category->notes()->delete();
}

View File

@@ -52,6 +52,7 @@ class PiggyBankEventObserver
$event->native_amount = null;
if ($event->piggyBank->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($event->piggyBank->accounts()->first()->user->userGroup);
$converter->setIgnoreSettings(true);
$event->native_amount = $converter->convert($event->piggyBank->transactionCurrency, $userCurrency, today(), $event->amount);
}

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
@@ -46,8 +47,11 @@ class PiggyBankObserver
{
app('log')->debug('Observe "deleting" of a piggy bank.');
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($piggyBank->accounts()->first()->user);
foreach ($piggyBank->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
$piggyBank->piggyBankEvents()->delete();

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
/**
* Class RecurrenceObserver
@@ -33,8 +34,12 @@ class RecurrenceObserver
public function deleting(Recurrence $recurrence): void
{
app('log')->debug('Observe "deleting" of a recurrence.');
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($recurrence->user);
foreach ($recurrence->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
$recurrence->recurrenceRepetitions()->delete();

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
/**
* Class TagObserver
@@ -34,8 +35,11 @@ class TagObserver
{
app('log')->debug('Observe "deleting" of a tag.');
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($tag->user);
foreach ($tag->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
$tag->locations()->delete();

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
/**
* Class TransactionJournalObserver
@@ -34,6 +35,10 @@ class TransactionJournalObserver
{
app('log')->debug('Observe "deleting" of a transaction journal.');
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($transactionJournal->user);
// to make sure the listener doesn't get back to use and loop
TransactionJournal::withoutEvents(static function () use ($transactionJournal): void {
foreach ($transactionJournal->transactions()->get() as $transaction) {
@@ -41,7 +46,7 @@ class TransactionJournalObserver
}
});
foreach ($transactionJournal->attachments()->get() as $attachment) {
$attachment->delete();
$repository->destroy($attachment);
}
$transactionJournal->locations()->delete();
$transactionJournal->sourceJournalLinks()->delete();

View File

@@ -100,7 +100,7 @@ class GroupCollector implements GroupCollectorInterface
'category_id',
'budget_id',
];
$this->stringFields = ['amount', 'foreign_amount'];
$this->stringFields = ['amount', 'foreign_amount', 'native_amount', 'native_foreign_amount'];
$this->total = 0;
$this->fields = [
// group
@@ -710,10 +710,13 @@ class GroupCollector implements GroupCollectorInterface
foreach ($groups as $groudId => $group) {
/** @var array $transaction */
foreach ($group['transactions'] as $transaction) {
$currencyId = (int) $transaction['currency_id'];
$currencyId = (int) $transaction['currency_id'];
if (null === $transaction['amount']) {
throw new FireflyException(sprintf('Amount is NULL for a transaction in group #%d, please investigate.', $groudId));
}
$nativeAmount = (string) ('' === $transaction['native_amount'] ? '0' : $transaction['native_amount']);
$nativeForeignAmount = (string) ('' === $transaction['native_foreign_amount'] ? '0' : $transaction['native_foreign_amount']);
$foreignAmount = (string) ('' === $transaction['foreign_amount'] ? '0' : $transaction['foreign_amount']);
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
@@ -722,11 +725,13 @@ class GroupCollector implements GroupCollectorInterface
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
$groups[$groudId]['sums'][$currencyId]['native_amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount']);
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount']);
$groups[$groudId]['sums'][$currencyId]['native_amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['native_amount'], $nativeAmount);
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$currencyId = (int) $transaction['foreign_currency_id'];
$currencyId = (int) $transaction['foreign_currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
@@ -735,8 +740,10 @@ class GroupCollector implements GroupCollectorInterface
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
$groups[$groudId]['sums'][$currencyId]['native_amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['foreign_amount']);
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $foreignAmount);
$groups[$groudId]['sums'][$currencyId]['native_amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $nativeForeignAmount);
}
}
}

View File

@@ -106,6 +106,7 @@ class IndexController extends Controller
$account->interestPeriod = (string) trans(sprintf('firefly.interest_calc_%s', $this->repository->getMetaValue($account, 'interest_period')));
$account->accountTypeString = (string) trans(sprintf('firefly.account_type_%s', $account->accountType->type));
$account->current_debt = '0';
$account->currency = $currency ?? $this->defaultCurrency;
$account->iban = implode(' ', str_split((string) $account->iban, 4));
}
);

View File

@@ -108,11 +108,14 @@ class ReconcileController extends Controller
if ($end->lt($start)) {
[$start, $end] = [$end, $start];
}
// move dates to end of day and start of day:
$start->startOfDay();
$end->endOfDay();
$startDate = clone $start;
$startDate->subDay();
$startBalance = Steam::finalAccountBalance($account, $startDate)['balance'];
$endBalance = Steam::finalAccountBalance($account, $end)['balance'];
$startDate->subDay()->endOfDay();
$startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
$endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
$subTitle = (string) trans('firefly.reconcile_account', ['account' => $account->name]);

View File

@@ -93,6 +93,11 @@ class ShowController extends Controller
if ($end->lt($start)) {
[$start, $end] = [$end, $start];
}
// make sure dates are end of day and start of day:
$start->startOfDay();
$end->endOfDay();
$location = $this->repository->getLocation($account);
$attachments = $this->repository->getAttachments($account);
$today = today(config('app.timezone'));
@@ -181,6 +186,8 @@ class ShowController extends Controller
$subTitle = (string) trans('firefly.all_journals_for_account', ['name' => $account->name]);
$periods = new Collection();
$end->endOfDay();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation()->withCategoryInformation();

View File

@@ -109,8 +109,8 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts);
// grab all balances
$startBalances = app('steam')->finalAccountsBalance($accounts, $start);
$endBalances = app('steam')->finalAccountsBalance($accounts, $end);
$startBalances = Steam::finalAccountsBalance($accounts, $start);
$endBalances = Steam::finalAccountsBalance($accounts, $end);
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
@@ -139,6 +139,7 @@ class AccountController extends Controller
}
// Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
$searchCode = $this->convertToNative ? $this->defaultCurrency->code : $key;
$searchCode = 3 !== strlen($searchCode) ? $this->defaultCurrency->code : $searchCode;
// Log::debug(sprintf('Search code is %s', $searchCode));
// see if there is an accompanying start amount.
// grab the difference and find the currency.
@@ -416,7 +417,9 @@ class AccountController extends Controller
*/
public function period(Account $account, Carbon $start, Carbon $end): JsonResponse
{
Log::debug(sprintf('Now in period("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d')));
$start->startOfDay();
$end->endOfDay();
Log::debug(sprintf('Now in period("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
$chartData = [];
$cache = new CacheProperties();
$cache->addProperty('chart.account.period');
@@ -431,7 +434,7 @@ class AccountController extends Controller
// collect and filter balances for the entire period.
$step = $this->calculateStep($start, $end);
Log::debug(sprintf('Step is %s', $step));
$locale = app('steam')->getLocale();
$locale = Steam::getLocale();
$return = [];
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
@@ -442,11 +445,8 @@ class AccountController extends Controller
$format = (string) trans('config.month_and_day_js', [], $locale);
$accountCurrency = $this->accountRepository->getAccountCurrency($account);
Log::debug('One');
$range = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToNative);
Log::debug('Two');
$range = Steam::filterAccountBalances($range, $account, $this->convertToNative, $accountCurrency);
Log::debug('Three');
// temp, get end balance.
Log::debug('temp get end balance');
@@ -462,25 +462,32 @@ class AccountController extends Controller
Log::debug('Balances exist at:');
foreach ($range as $key => $value) {
$newRange[] = ['date' => $key, 'info' => $value];
Log::debug(sprintf(' - %s', $key));
Log::debug(sprintf('%d - %s (%s)', count($newRange) - 1, $key, json_encode($value)));
}
$carbon = Carbon::createFromFormat('Y-m-d', $newRange[0]['date']);
$carbon = Carbon::createFromFormat('Y-m-d', $newRange[0]['date'])->endOfDay();
Log::debug(sprintf('Start of loop, $carbon is %s', $carbon->format('Y-m-d H:i:s')));
while ($end->gte($current)) {
$momentBalance = $previous;
$theDate = $current->format('Y-m-d');
while ($carbon->lte($current) && array_key_exists($expectedIndex, $newRange)) {
$momentBalance = $newRange[$expectedIndex]['info'];
Log::debug(sprintf('Expected index is %d!, date is %s, current is %s', $expectedIndex, $carbon->format('Y-m-d'), $current->format('Y-m-d')));
$carbon = Carbon::createFromFormat('Y-m-d', $newRange[$expectedIndex]['date']);
++$expectedIndex;
}
// $theDate = $current->format('Y-m-d');
Log::debug(sprintf('Now at %s, with momentBalance %s', $current->format('Y-m-d H:i:s'), json_encode($momentBalance)));
// loop over the array with balances, find one that is earlier or on the same day.
while ($carbon->lte($current) && array_key_exists($expectedIndex, $newRange)) {
Log::debug(sprintf('[a] Expected index is %d, $carbon is %s, current is %s', $expectedIndex, $carbon->format('Y-m-d H:i:s'), $current->format('Y-m-d H:i:s')));
// grab the balance from that particular $expectedIndex
$momentBalance = $newRange[$expectedIndex]['info'];
++$expectedIndex;
// make new carbon based on the next found date. this should stop the loop.
if (array_key_exists($expectedIndex, $newRange)) {
$carbon = Carbon::createFromFormat('Y-m-d', $newRange[$expectedIndex]['date'])->endOfDay();
}
}
Log::debug(sprintf('momentBalance is now %s', json_encode($momentBalance)));
$return = $this->updateChartKeys($return, $momentBalance);
$previous = $momentBalance;
Log::debug(sprintf('Now at %s', $theDate), $momentBalance);
// process each balance thing.
foreach ($momentBalance as $key => $amount) {
$label = $current->isoFormat($format);
@@ -489,8 +496,6 @@ class AccountController extends Controller
$current = app('navigation')->addPeriod($current, $step, 0);
// here too, to fix #8041, the data is corrected to the end of the period.
$current = app('navigation')->endOfX($current, $step, null);
Log::debug(sprintf('Next moment is %s', $current->format('Y-m-d')));
}
Log::debug('End of chart loop.');
// second loop (yes) to create nice array with info! Yay!
@@ -567,8 +572,8 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts);
// grab all balances
$startBalances = app('steam')->finalAccountsBalance($accounts, $start);
$endBalances = app('steam')->finalAccountsBalance($accounts, $end);
$startBalances = Steam::finalAccountsBalance($accounts, $start);
$endBalances = Steam::finalAccountsBalance($accounts, $end);
// loop the accounts, then check for balance and currency info.

View File

@@ -95,17 +95,22 @@ abstract class Controller extends BaseController
// share is alpha, is beta
$isAlpha = false;
$isBeta = false;
$isDevelop = false;
if (str_contains(config('firefly.version'), 'alpha')) {
$isAlpha = true;
}
if (str_contains(config('firefly.version'), 'develop') || str_contains(config('firefly.version'), 'branch')) {
$isDevelop = true;
}
$isBeta = false;
if (str_contains(config('firefly.version'), 'beta')) {
$isBeta = true;
}
View::share('FF_IS_ALPHA', $isAlpha);
View::share('FF_IS_BETA', $isBeta);
View::share('FF_IS_DEVELOP', $isDevelop);
$this->middleware(
function ($request, $next): mixed {

View File

@@ -31,6 +31,9 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use FireflyIII\User;
@@ -61,7 +64,7 @@ class DebugController extends Controller
$this->middleware(IsDemoUser::class)->except(['displayError']);
}
public function routes(): never
public function routes(Request $request): never
{
if (!auth()->user()->hasRole('owner')) {
throw new NotFoundHttpException();
@@ -69,6 +72,46 @@ class DebugController extends Controller
/** @var iterable $routes */
$routes = Route::getRoutes();
if ('true' === $request->get('api')) {
$collection = [];
$i = 0;
echo 'PATHS="';
/** @var \Illuminate\Routing\Route $route */
foreach ($routes as $route) {
++$i;
// skip API and other routes.
if (!str_starts_with($route->uri(), 'api/v1')
) {
continue;
}
// skip non GET routes
if (!in_array('GET', $route->methods(), true)) {
continue;
}
// no name route:
if (null === $route->getName()) {
var_dump($route);
exit;
}
echo substr($route->uri(), 3);
if (0 === $i % 5) {
echo '"<br>PATHS="${PATHS},';
}
if (0 !== $i % 5) {
echo ',';
}
}
exit;
}
$return = [];
/** @var \Illuminate\Routing\Route $route */
@@ -153,7 +196,7 @@ class DebugController extends Controller
*/
public function flush(Request $request)
{
app('preferences')->mark();
Preferences::mark();
$request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range', 'temp-mfa-secret', 'temp-mfa-codes']);
Artisan::call('cache:clear');
@@ -223,8 +266,8 @@ class DebugController extends Controller
private function getSystemInformation(): array
{
$maxFileSize = app('steam')->phpBytes((string) ini_get('upload_max_filesize'));
$maxPostSize = app('steam')->phpBytes((string) ini_get('post_max_size'));
$maxFileSize = Steam::phpBytes((string) ini_get('upload_max_filesize'));
$maxPostSize = Steam::phpBytes((string) ini_get('post_max_size'));
$drivers = \DB::availableDrivers();
$currentDriver = \DB::getDriverName();
@@ -314,7 +357,7 @@ class DebugController extends Controller
];
}
private function getuserInfo(): array
private function getUserInfo(): array
{
$userFlags = $this->getUserFlags();
@@ -324,7 +367,7 @@ class DebugController extends Controller
// set languages, see what happens:
$original = setlocale(LC_ALL, '0');
$localeAttempts = [];
$parts = app('steam')->getLocaleArray(app('steam')->getLocale());
$parts = Steam::getLocaleArray(Steam::getLocale());
foreach ($parts as $code) {
$code = trim($code);
app('log')->debug(sprintf('Trying to set %s', $code));
@@ -334,14 +377,16 @@ class DebugController extends Controller
setlocale(LC_ALL, (string) $original);
return [
'user_id' => auth()->user()->id,
'user_count' => User::count(),
'user_flags' => $userFlags,
'user_agent' => $userAgent,
'locale_attempts' => $localeAttempts,
'locale' => app('steam')->getLocale(),
'language' => app('steam')->getLanguage(),
'view_range' => app('preferences')->get('viewRange', '1M')->data,
'user_id' => auth()->user()->id,
'user_count' => User::count(),
'user_flags' => $userFlags,
'user_agent' => $userAgent,
'native' => Amount::getNativeCurrency(),
'convert_to_native' => Amount::convertToNative(),
'locale_attempts' => $localeAttempts,
'locale' => Steam::getLocale(),
'language' => Steam::getLanguage(),
'view_range' => Preferences::get('viewRange', '1M')->data,
];
}

View File

@@ -27,6 +27,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Http\JsonResponse;
/**
@@ -41,12 +42,16 @@ class FrontpageController extends Controller
*/
public function piggyBanks(PiggyBankRepositoryInterface $repository): JsonResponse
{
$set = $repository->getPiggyBanks();
$info = [];
$set = $repository->getPiggyBanks();
$info = [];
$native = Amount::getNativeCurrency();
$convertToNative = Amount::convertToNative();
/** @var PiggyBank $piggyBank */
foreach ($set as $piggyBank) {
$amount = $repository->getCurrentAmount($piggyBank);
$amount = $repository->getCurrentAmount($piggyBank);
$nativeAmount = $repository->getCurrentNativeAmount($piggyBank);
if (1 === bccomp($amount, '0')) {
// percentage!
$pct = 0;
@@ -55,11 +60,19 @@ class FrontpageController extends Controller
}
$entry = [
'id' => $piggyBank->id,
'name' => $piggyBank->name,
'amount' => $amount,
'target' => $piggyBank->target_amount,
'percentage' => $pct,
'id' => $piggyBank->id,
'name' => $piggyBank->name,
'amount' => $amount,
'native_amount' => $nativeAmount,
'target' => $piggyBank->target_amount,
'native_target' => $piggyBank->native_target_amount,
'percentage' => $pct,
// currency:
'currency_symbol' => $piggyBank->transactionCurrency->symbol,
'currency_decimal_places' => $piggyBank->transactionCurrency->decimal_places,
'native_currency_symbol' => $native->symbol,
'native_currency_decimal_places' => $native->decimal_places,
];
$info[] = $entry;
@@ -74,11 +87,10 @@ class FrontpageController extends Controller
}
);
$html = '';
$html = '';
if (0 !== count($info)) {
try {
$html = view('json.piggy-banks', compact('info'))->render();
$html = view('json.piggy-banks', compact('info', 'convertToNative', 'native'))->render();
} catch (\Throwable $e) {
app('log')->error(sprintf('Cannot render json.piggy-banks: %s', $e->getMessage()));
app('log')->error($e->getTraceAsString());

View File

@@ -189,13 +189,14 @@ class ReconcileController extends Controller
if ($end->lt($start)) {
[$end, $start] = [$start, $end];
}
$start->startOfDay();
$end->endOfDay();
$startDate = clone $start;
$startDate->subDay();
$end->endOfDay();
$currency = $this->accountRepos->getAccountCurrency($account) ?? $this->defaultCurrency;
$startBalance = Steam::finalAccountBalance($account, $startDate)['balance'];
$endBalance = Steam::finalAccountBalance($account, $end)['balance'];
$startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
$endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
// get the transactions
$selectionStart = clone $start;

View File

@@ -68,14 +68,16 @@ class AmountController extends Controller
*/
public function add(PiggyBank $piggyBank)
{
/** @var Carbon $date */
$date = session('end', today(config('app.timezone')));
$accounts = [];
$total = '0';
$totalSaved = $this->piggyRepos->getCurrentAmount($piggyBank);
$leftToSave = bcsub($piggyBank->target_amount, $totalSaved);
foreach ($piggyBank->accounts as $account) {
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, today(config('app.timezone'))->endOfDay());
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, $date);
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank, $account);
$maxAmount = 0 === bccomp($piggyBank->target_amount, '0') ? $leftToSave : min($leftOnAccount, $leftToSave);
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$maxAmount = 0 === bccomp($piggyBank->target_amount, '0') ? $leftOnAccount : min($leftOnAccount, $leftToSave);
$accounts[] = [
'account' => $account,
'left_on_account' => $leftOnAccount,
@@ -105,12 +107,13 @@ class AmountController extends Controller
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, $date);
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank, $account);
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$maxAmount = 0 === bccomp($piggyBank->target_amount, '0') ? $leftOnAccount : min($leftOnAccount, $leftToSave);
$accounts[] = [
'account' => $account,
'left_on_account' => $leftOnAccount,
'saved_so_far' => $savedSoFar,
'left_to_save' => $leftToSave,
'max_amount' => 0 === bccomp($piggyBank->target_amount, '0') ? $leftOnAccount : min($leftOnAccount, $leftToSave),
'max_amount' => $maxAmount,
];
$total = bcadd($total, $leftOnAccount);
}

View File

@@ -31,6 +31,7 @@ use FireflyIII\Http\Requests\PreferencesRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\Preference;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
@@ -93,35 +94,35 @@ class PreferencesController extends Controller
/** @var array<int, int> $accountIds */
$accountIds = $accounts->pluck('id')->toArray();
$viewRange = app('navigation')->getViewRange(false);
$frontpageAccountsPref = app('preferences')->get('frontpageAccounts', $accountIds);
$frontpageAccountsPref = Preferences::get('frontpageAccounts', $accountIds);
$frontpageAccounts = $frontpageAccountsPref->data;
if (!is_array($frontpageAccounts)) {
$frontpageAccounts = $accountIds;
}
$language = app('steam')->getLanguage();
$languages = config('firefly.languages');
$locale = app('preferences')->get('locale', config('firefly.default_locale', 'equal'))->data;
$listPageSize = app('preferences')->get('listPageSize', 50)->data;
$darkMode = app('preferences')->get('darkMode', 'browser')->data;
$customFiscalYear = app('preferences')->get('customFiscalYear', 0)->data;
$fiscalYearStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data;
$locale = Preferences::get('locale', config('firefly.default_locale', 'equal'))->data;
$listPageSize = Preferences::get('listPageSize', 50)->data;
$darkMode = Preferences::get('darkMode', 'browser')->data;
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
$convertToNative = $this->convertToNative;
if (is_array($fiscalYearStartStr)) {
$fiscalYearStartStr = '01-01';
}
$fiscalYearStart = sprintf('%s-%s', date('Y'), (string) $fiscalYearStartStr);
$tjOptionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data;
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$availableDarkModes = config('firefly.available_dark_modes');
// notifications settings
$slackUrl = app('preferences')->getEncrypted('slack_webhook_url', '')->data;
$pushoverAppToken = (string) app('preferences')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string) app('preferences')->getEncrypted('pushover_user_token', '')->data;
$ntfyServer = app('preferences')->getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$ntfyTopic = (string) app('preferences')->getEncrypted('ntfy_topic', '')->data;
$ntfyAuth = app('preferences')->get('ntfy_auth', false)->data;
$ntfyUser = app('preferences')->getEncrypted('ntfy_user', '')->data;
$ntfyPass = (string) app('preferences')->getEncrypted('ntfy_pass', '')->data;
$slackUrl = Preferences::getEncrypted('slack_webhook_url', '')->data;
$pushoverAppToken = (string) Preferences::getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string) Preferences::getEncrypted('pushover_user_token', '')->data;
$ntfyServer = Preferences::getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$ntfyTopic = (string) Preferences::getEncrypted('ntfy_topic', '')->data;
$ntfyAuth = '1' === Preferences::get('ntfy_auth', false)->data;
$ntfyUser = Preferences::getEncrypted('ntfy_user', '')->data;
$ntfyPass = (string) Preferences::getEncrypted('ntfy_pass', '')->data;
$channels = config('notifications.channels');
$forcedAvailability = [];
@@ -131,7 +132,7 @@ class PreferencesController extends Controller
if (true === $info['enabled']) {
$notifications[$key]
= [
'enabled' => true === app('preferences')->get(sprintf('notification_%s', $key), true)->data,
'enabled' => true === Preferences::get(sprintf('notification_%s', $key), true)->data,
'configurable' => $info['configurable'],
];
}
@@ -221,7 +222,7 @@ class PreferencesController extends Controller
foreach ($request->get('frontpageAccounts') as $id) {
$frontpageAccounts[] = (int) $id;
}
app('preferences')->set('frontpageAccounts', $frontpageAccounts);
Preferences::set('frontpageAccounts', $frontpageAccounts);
}
// extract notifications:
@@ -229,15 +230,15 @@ class PreferencesController extends Controller
foreach (config('notifications.notifications.user') as $key => $info) {
$key = sprintf('notification_%s', $key);
if (array_key_exists($key, $all)) {
app('preferences')->set($key, true);
Preferences::set($key, true);
}
if (!array_key_exists($key, $all)) {
app('preferences')->set($key, false);
Preferences::set($key, false);
}
}
// view range:
app('preferences')->set('viewRange', $request->get('viewRange'));
Preferences::set('viewRange', $request->get('viewRange'));
// forget session values:
session()->forget('start');
session()->forget('end');
@@ -249,13 +250,13 @@ class PreferencesController extends Controller
$variables = ['slack_webhook_url', 'pushover_app_token', 'pushover_user_token', 'ntfy_server', 'ntfy_topic', 'ntfy_user', 'ntfy_pass'];
foreach ($variables as $variable) {
if ('' === $all[$variable]) {
app('preferences')->delete($variable);
Preferences::delete($variable);
}
if ('' !== $all[$variable]) {
app('preferences')->setEncrypted($variable, $all[$variable]);
Preferences::setEncrypted($variable, $all[$variable]);
}
}
app('preferences')->set('ntfy_auth', $all['ntfy_auth'] ?? false);
Preferences::set('ntfy_auth', $all['ntfy_auth'] ?? false);
}
// convert native
@@ -263,32 +264,33 @@ class PreferencesController extends Controller
if ($convertToNative && !$this->convertToNative) {
// set to true!
Log::debug('User sets convertToNative to true.');
Preferences::set('convert_to_native', $convertToNative);
event(new UserGroupChangedDefaultCurrency(auth()->user()->userGroup));
}
app('preferences')->set('convert_to_native', $convertToNative);
Preferences::set('convert_to_native', $convertToNative);
// custom fiscal year
$customFiscalYear = 1 === (int) $request->get('customFiscalYear');
$string = strtotime((string) $request->get('fiscalYearStart'));
if (false !== $string) {
$fiscalYearStart = date('m-d', $string);
app('preferences')->set('customFiscalYear', $customFiscalYear);
app('preferences')->set('fiscalYearStart', $fiscalYearStart);
Preferences::set('customFiscalYear', $customFiscalYear);
Preferences::set('fiscalYearStart', $fiscalYearStart);
}
// save page size:
app('preferences')->set('listPageSize', 50);
Preferences::set('listPageSize', 50);
$listPageSize = (int) $request->get('listPageSize');
if ($listPageSize > 0 && $listPageSize < 1337) {
app('preferences')->set('listPageSize', $listPageSize);
Preferences::set('listPageSize', $listPageSize);
}
// language:
/** @var Preference $currentLang */
$currentLang = app('preferences')->get('language', 'en_US');
$currentLang = Preferences::get('language', 'en_US');
$lang = $request->get('language');
if (array_key_exists($lang, config('firefly.languages'))) {
app('preferences')->set('language', $lang);
Preferences::set('language', $lang);
}
if ($currentLang->data !== $lang) {
// this string is untranslated on purpose.
@@ -299,7 +301,7 @@ class PreferencesController extends Controller
if (!auth()->user()->hasRole('demo')) {
$locale = (string) $request->get('locale');
$locale = '' === $locale ? null : $locale;
app('preferences')->set('locale', $locale);
Preferences::set('locale', $locale);
}
// optional fields for transactions:
@@ -318,16 +320,16 @@ class PreferencesController extends Controller
'location' => array_key_exists('location', $setOptions),
'links' => array_key_exists('links', $setOptions),
];
app('preferences')->set('transaction_journal_optional_fields', $optionalTj);
Preferences::set('transaction_journal_optional_fields', $optionalTj);
// dark mode
$darkMode = $request->get('darkMode') ?? 'browser';
if (in_array($darkMode, config('firefly.available_dark_modes'), true)) {
app('preferences')->set('darkMode', $darkMode);
Preferences::set('darkMode', $darkMode);
}
session()->flash('success', (string) trans('firefly.saved_preferences'));
app('preferences')->mark();
Preferences::mark();
return redirect(route('preferences.index'));
}

View File

@@ -83,6 +83,8 @@ class ReportController extends Controller
return view('error')->with('message', (string) trans('firefly.end_after_start_date'));
}
$this->repository->cleanupBudgets();
$start->startOfDay();
$end->endOfDay();
app('view')->share(
'subTitle',
@@ -114,6 +116,8 @@ class ReportController extends Controller
return view('error')->with('message', (string) trans('firefly.end_after_start_date'));
}
$this->repository->cleanupBudgets();
$start->startOfDay();
$end->endOfDay();
app('view')->share(
'subTitle',
@@ -146,6 +150,8 @@ class ReportController extends Controller
return view('error')->with('message', (string) trans('firefly.end_after_start_date'));
}
$this->repository->cleanupBudgets();
$start->startOfDay();
$end->endOfDay();
app('view')->share(
'subTitle',
@@ -179,6 +185,8 @@ class ReportController extends Controller
}
$this->repository->cleanupBudgets();
$start->startOfDay();
$end->endOfDay();
app('view')->share(
'subTitle',
@@ -211,6 +219,8 @@ class ReportController extends Controller
}
$this->repository->cleanupBudgets();
$start->startOfDay();
$end->endOfDay();
app('view')->share(
'subTitle',
@@ -367,6 +377,8 @@ class ReportController extends Controller
return view('error')->with('message', (string) trans('firefly.end_after_start_date'));
}
$this->repository->cleanupBudgets();
$start->startOfDay();
$end->endOfDay();
app('view')->share(
'subTitle',

View File

@@ -419,7 +419,9 @@ class CreateRecurringTransactions implements ShouldQueue
/** @var RecurrenceTransaction $transaction */
foreach ($transactions as $index => $transaction) {
$single = [
'type' => null === $transaction->transactionType->type ? strtolower($recurrence->transactionType->type) : strtolower($transaction->transactionType->type),
'type' => null === $transaction?->transactionType?->type ?
strtolower($recurrence->transactionType->type) :
strtolower($transaction->transactionType->type),
'date' => $date,
'user' => $recurrence->user_id,
'currency_id' => $transaction->transaction_currency_id,

View File

@@ -57,7 +57,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/
public function getCompleteSet(): Collection
{
return TransactionCurrency::orderBy('code', 'ASC')->get();
return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get();
}
/**

View File

@@ -308,6 +308,24 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
return $sum;
}
/**
* Get current amount saved in piggy bank.
*/
public function getCurrentNativeAmount(PiggyBank $piggyBank, ?Account $account = null): string
{
$sum = '0';
foreach ($piggyBank->accounts as $current) {
if (null !== $account && $account->id !== $current->id) {
continue;
}
$amount = (string) $current->pivot->native_current_amount;
$amount = '' === $amount ? '0' : $amount;
$sum = bcadd($sum, $amount);
}
return $sum;
}
public function getRepetition(PiggyBank $piggyBank, bool $overrule = false): ?PiggyBankRepetition
{
if (false === $overrule) {

View File

@@ -40,6 +40,8 @@ interface PiggyBankRepositoryInterface
{
public function addAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool;
public function getCurrentNativeAmount(PiggyBank $piggyBank, ?Account $account = null): string;
public function addAmountToPiggyBank(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): void;
public function canAddAmount(PiggyBank $piggyBank, Account $account, string $amount): bool;

View File

@@ -93,7 +93,7 @@ class RecurringRepository implements RecurringRepositoryInterface
}
/**
* Returns all of the user's recurring transactions.
* Returns all the user's recurring transactions.
*/
public function get(): Collection
{

View File

@@ -108,6 +108,6 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface
#[\Override]
public function getAll(): Collection
{
return $this->userGroup->currencyExchangeRates()->get();
return $this->userGroup->currencyExchangeRates()->orderBy('date', 'ASC')->get();
}
}

View File

@@ -38,6 +38,15 @@ class IsValidPositiveAmount implements ValidationRule
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if (is_array($value)) {
$fail('validation.numeric')->translate();
$message = sprintf('IsValidPositiveAmount: "%s" is not a number.', json_encode($value));
Log::debug($message);
Log::channel('audit')->info($message);
return;
}
$value = (string) $value;
// must not be empty:
if ($this->emptyString($value)) {

View File

@@ -79,6 +79,12 @@ class UniqueAccountNumber implements ValidationRule
if (null === $this->expectedType) {
return;
}
if (is_array($value)) {
$fail('validation.generic_invalid')->translate();
return;
}
$value = (string) $value;
$maxCounts = $this->getMaxOccurrences();
foreach ($maxCounts as $type => $max) {

View File

@@ -94,6 +94,10 @@ class UniqueIban implements ValidationRule
if (0 === count($this->expectedTypes)) {
return true;
}
if (is_array($value)) {
return false;
}
$value = (string) $value;
$maxCounts = $this->getMaxOccurrences();
foreach ($maxCounts as $type => $max) {

View File

@@ -48,14 +48,14 @@ class BillWarningCronjob extends AbstractCronjob
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
if (0 === $lastTime) {
app('log')->info('The bill warning cron-job has never fired before.');
app('log')->info('The bill notification cron-job has never fired before.');
}
// less than half a day ago:
if ($lastTime > 0 && $diff <= 43200) {
app('log')->info(sprintf('It has been %s since the bill warning cron-job has fired.', $diffForHumans));
app('log')->info(sprintf('It has been %s since the bill notification cron-job has fired.', $diffForHumans));
if (false === $this->force) {
app('log')->info('The cron-job will not fire now.');
$this->message = sprintf('It has been %s since the bill warning cron-job has fired. It will not fire now.', $diffForHumans);
$this->message = sprintf('It has been %s since the bill notification cron-job has fired. It will not fire now.', $diffForHumans);
$this->jobFired = false;
$this->jobErrored = false;
$this->jobSucceeded = false;
@@ -63,11 +63,11 @@ class BillWarningCronjob extends AbstractCronjob
return;
}
app('log')->info('Execution of the bill warning cron-job has been FORCED.');
app('log')->info('Execution of the bill notification cron-job has been FORCED.');
}
if ($lastTime > 0 && $diff > 43200) {
app('log')->info(sprintf('It has been %s since the bill warning cron-job has fired. It will fire now!', $diffForHumans));
app('log')->info(sprintf('It has been %s since the bill notification cron-job has fired. It will fire now!', $diffForHumans));
}
$this->fireWarnings();
@@ -77,7 +77,7 @@ class BillWarningCronjob extends AbstractCronjob
private function fireWarnings(): void
{
app('log')->info(sprintf('Will now fire bill warning job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
app('log')->info(sprintf('Will now fire bill notification job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
/** @var WarnAboutBills $job */
$job = app(WarnAboutBills::class);
@@ -89,10 +89,10 @@ class BillWarningCronjob extends AbstractCronjob
$this->jobFired = true;
$this->jobErrored = false;
$this->jobSucceeded = true;
$this->message = 'Bill warning cron job fired successfully.';
$this->message = 'Bill notification cron job fired successfully.';
app('fireflyconfig')->set('last_bw_job', (int) $this->date->format('U'));
app('log')->info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
app('log')->info('Done with bill warning cron job task.');
app('log')->info('Done with bill notification cron job task.');
}
}

View File

@@ -513,7 +513,7 @@ class ExportDataGenerator
'currency_code', 'foreign_currency_code', 'source_name', 'source_type', 'destination_name', 'destination_type', 'amount', 'foreign_amount', 'category', 'budget', 'piggy_bank', 'tags',
];
$records = [];
$recurrences = $recurringRepos->getAll();
$recurrences = $recurringRepos->get();
/** @var Recurrence $recurrence */
foreach ($recurrences as $recurrence) {

View File

@@ -90,7 +90,7 @@ trait ChartGeneration
$balance = $range[$format] ?? $previous;
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance[$field];
$currentSet['entries'][$label] = $balance[$field] ?? '0';
}
$chartData[] = $currentSet;
}

View File

@@ -203,11 +203,10 @@ trait PeriodOverview
$currencySymbol = $journal['currency_symbol'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$foreignCurrencyId = $journal['foreign_currency_id'];
$amount = $journal['amount'];
$amount = $journal['amount'] ?? '0';
if ($this->convertToNative && $currencyId !== $this->defaultCurrency->id && $foreignCurrencyId !== $this->defaultCurrency->id) {
$amount = $journal['native_amount'];
$amount = $journal['native_amount'] ?? '0';
$currencyId = $this->defaultCurrency->id;
$currencyCode = $this->defaultCurrency->code;
$currencyName = $this->defaultCurrency->name;
@@ -220,7 +219,7 @@ trait PeriodOverview
$currencyName = $journal['foreign_currency_name'];
$currencySymbol = $journal['foreign_currency_symbol'];
$currencyDecimalPlaces = $journal['foreign_currency_decimal_places'];
$amount = $journal['foreign_amount'];
$amount = $journal['foreign_amount'] ?? '0';
}
$return[$currencyId] ??= [
'amount' => '0',

View File

@@ -202,7 +202,7 @@ class Preferences
return null;
}
if ('' === $result->data) {
Log::warning(sprintf('Empty encrypted preference found: "%s"', $name));
// Log::warning(sprintf('Empty encrypted preference found: "%s"', $name));
return $result;
}
@@ -214,7 +214,7 @@ class Preferences
Log::debug('Set data to NULL');
$result->data = null;
}
Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
// Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
return $result;
}
@@ -226,7 +226,7 @@ class Preferences
{
$result = $this->getForUser($user, $name, $default);
if ('' === $result->data) {
Log::warning(sprintf('Empty encrypted preference found: "%s"', $name));
// Log::warning(sprintf('Empty encrypted preference found: "%s"', $name));
return $result;
}
@@ -238,7 +238,7 @@ class Preferences
Log::debug('Set data to NULL');
$result->data = null;
}
Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
// Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
return $result;
}

View File

@@ -30,6 +30,7 @@ use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Trait ConvertsDataTypes
@@ -359,10 +360,13 @@ trait ConvertsDataTypes
{
$result = null;
Log::debug(sprintf('Date string is "%s"', (string) $this->get($field)));
try {
$result = '' !== (string) $this->get($field) ? new Carbon((string) $this->get($field), config('app.timezone')) : null;
} catch (InvalidFormatException $e) {
// @ignoreException
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
}
if (null === $result) {
app('log')->debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
@@ -386,6 +390,18 @@ trait ConvertsDataTypes
return (int) $string;
}
protected function floatFromValue(?string $string): ?float
{
if (null === $string) {
return null;
}
if ('' === $string) {
return null;
}
return (float) $string;
}
/**
* Return integer value, or NULL when it's not set.
*/

View File

@@ -28,10 +28,11 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use FireflyIII\Support\Facades\Amount;
/**
* Class Steam.
@@ -75,99 +76,80 @@ class Steam
$balances = [];
$formatted = $start->format('Y-m-d');
$startBalance = $this->finalAccountBalance($account, $start);
$defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup);
$nativeCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup);
$accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = null !== $accountCurrency;
$currency = $accountCurrency ?? $defaultCurrency;
$currency = $accountCurrency ?? $nativeCurrency;
Log::debug(sprintf('Currency is %s', $currency->code));
// set start balances:
$startBalance[$currency->code] ??= '0';
if ($hasCurrency) {
$startBalance[$accountCurrency->code] ??= '0';
}
if (!$hasCurrency) {
Log::debug(sprintf('Also set start balance in %s', $defaultCurrency->code));
$startBalance[$defaultCurrency->code] ??= '0';
Log::debug(sprintf('Also set start balance in %s', $nativeCurrency->code));
$startBalance[$nativeCurrency->code] ??= '0';
}
$currencies = [
$currency->id => $currency,
$defaultCurrency->id => $defaultCurrency,
$currency->id => $currency,
$nativeCurrency->id => $nativeCurrency,
];
$startBalance[$currency->code] ??= '0';
$balances[$formatted] = $startBalance;
Log::debug('Final start balance: ', $startBalance);
// sums up the balance changes per day, for foreign, native and normal amounts.
// sums up the balance changes per day.
$set = $account->transactions()
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
->groupBy('transaction_journals.date')
->groupBy('transactions.transaction_currency_id')
->groupBy('transactions.foreign_currency_id')
->orderBy('transaction_journals.date', 'ASC')
->whereNull('transaction_journals.deleted_at')
->get(
[ // @phpstan-ignore-line
'transaction_journals.date',
'transactions.transaction_currency_id',
DB::raw('SUM(transactions.amount) AS modified'),
'transactions.foreign_currency_id',
DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'),
DB::raw('SUM(transactions.native_amount) AS modified_native'),
DB::raw('SUM(transactions.amount) AS sum_of_day'),
]
)
;
$currentBalance = $startBalance;
$converter = new ExchangeRateConverter();
/** @var Transaction $entry */
foreach ($set as $entry) {
// normal, native and foreign amount
$carbon = new Carbon($entry->date, $entry->date_tz);
$modified = (string) (null === $entry->modified ? '0' : $entry->modified);
$foreignModified = (string) (null === $entry->modified_foreign ? '0' : $entry->modified_foreign);
$nativeModified = (string) (null === $entry->modified_native ? '0' : $entry->modified_native);
// get date object
$carbon = new Carbon($entry->date, $entry->date_tz);
// make sure sum is a string:
$sumOfDay = (string) (null === $entry->sum_of_day ? '0' : $entry->sum_of_day);
// find currency of this entry.
// find currency of this entry, does not have to exist.
$currencies[$entry->transaction_currency_id] ??= TransactionCurrency::find($entry->transaction_currency_id);
// make sure this $entry has its own $entryCurrency
/** @var TransactionCurrency $entryCurrency */
$entryCurrency = $currencies[$entry->transaction_currency_id];
$entryCurrency = $currencies[$entry->transaction_currency_id];
Log::debug(sprintf('Processing transaction(s) on date %s', $carbon->format('Y-m-d H:i:s')));
$currentBalance[$entryCurrency->code] ??= '0';
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, $currentBalance[$entryCurrency->code]);
// if convert to native, if NOT convert to native.
if ($convertToNative) {
Log::debug(sprintf('Amount is %s %s, foreign amount is %s, native amount is %s', $entryCurrency->code, $this->bcround($modified, 2), $this->bcround($foreignModified, 2), $this->bcround($nativeModified, 2)));
// if the currency is the default currency add to native balance + currency balance
if ($entry->transaction_currency_id === $defaultCurrency->id) {
Log::debug('Add amount to native.');
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $modified);
}
// add to native balance.
if ($entry->foreign_currency_id !== $defaultCurrency->id) {
// this check is not necessary, because if the foreign currency is the same as the default currency, the native amount is zero.
// so adding this would mean nothing.
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $nativeModified);
}
if ($entry->foreign_currency_id === $defaultCurrency->id) {
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $foreignModified);
}
// add to balance if is the same.
if ($entry->transaction_currency_id === $accountCurrency?->id) {
$currentBalance['balance'] = bcadd($currentBalance['balance'], $modified);
}
// add currency balance
$currentBalance[$entryCurrency->code] = bcadd($currentBalance[$entryCurrency->code] ?? '0', $modified);
}
// if not convert to native, add the amount to "balance", do nothing else.
if (!$convertToNative) {
Log::debug(sprintf('Amount is %s %s, foreign amount is %s, native amount is %s', $entryCurrency->code, $modified, $foreignModified, $nativeModified));
// add to balance, as expected.
$currentBalance['balance'] = bcadd($currentBalance['balance'] ?? '0', $modified);
// add to GBP, as expected.
$currentBalance[$entryCurrency->code] = bcadd($currentBalance[$entryCurrency->code] ?? '0', $modified);
$currentBalance['balance'] = bcadd($currentBalance['balance'], $sumOfDay);
}
$balances[$carbon->format('Y-m-d')] = $currentBalance;
// if convert to native add the converted amount to "native_balance".
if ($convertToNative) {
$nativeSumOfDay = $converter->convert($entryCurrency, $nativeCurrency, $carbon, $sumOfDay);
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $nativeSumOfDay);
}
// add final $currentBalance array to the big one.
$balances[$carbon->format('Y-m-d')] = $currentBalance;
Log::debug('Updated entry', $currentBalance);
}
$cache->store($balances);
@@ -275,28 +257,18 @@ class Steam
/**
* Returns the balance of an account at exact moment given. Array with at least one value.
* Always returns:
* "balance": balance in the account's currency OR user's native currency if the account has no currency
* "EUR": balance in EUR (or whatever currencies the account has balance in)
*
* "balance" the balance in whatever currency the account has, so the sum of all transaction that happen to have
* THAT currency.
* "native_balance" the balance according to the "native_amount" + "native_foreign_amount" fields.
* "ABC" the balance in this particular currency code (may repeat for each found currency).
*
* Het maakt niet uit of de native currency wel of niet gelijk is aan de account currency.
* Optelsom zou hetzelfde moeten zijn. Als het EUR is en de rekening ook is native_amount 0.
* Zo niet is amount 0 en native_amount het bedrag.
*
* Eerst een som van alle transacties in de native currency. Alle EUR bij elkaar opgeteld.
* Om te weten wat er nog meer op de rekening gebeurt, pak alles waar currency niet EUR is, en de foreign ook niet,
* en tel native_amount erbij op.
* Daarna pak je alle transacties waar currency niet EUR is, en de foreign wel, en tel foreign_amount erbij op.
*
* Wil je niks weten van native currencies, pak je:
*
* Eerst een som van alle transacties gegroepeerd op currency. Einde.
* If the user has $convertToNative:
* "balance": balance in the account's currency OR user's native currency if the account has no currency
* --> "native_balance": balance in the user's native balance, with all amounts converted to native.
* "EUR": balance in EUR (or whatever currencies the account has balance in)
*/
public function finalAccountBalance(Account $account, Carbon $date): array
{
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty($date);
if ($cache->has()) {
@@ -305,86 +277,56 @@ class Steam
Log::debug(sprintf('Now in finalAccountBalance(#%d, "%s", "%s")', $account->id, $account->name, $date->format('Y-m-d H:i:s')));
$native = Amount::getNativeCurrencyByUserGroup($account->user->userGroup);
$convertToNative = Amount::convertToNative($account->user);
$accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = null !== $accountCurrency;
$currency = $hasCurrency ? $accountCurrency : $native;
$return = [];
// first, the "balance", as described earlier.
if ($convertToNative) {
// normal balance
$return['balance'] = (string) $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->where('transactions.transaction_currency_id', $native->id)
->sum('transactions.amount')
;
// plus virtual balance, if the account has a virtual_balance in the native currency
if ($native->id === $accountCurrency?->id) {
$return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']);
}
// Log::debug(sprintf('balance is (%s only) %s (with virtual balance)', $native->code, $this->bcround($return['balance'], 2)));
// native balance
$return['native_balance'] = (string) $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->whereNot('transactions.transaction_currency_id', $native->id)
->sum('transactions.native_amount')
;
// plus native virtual balance.
$return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['native_balance']);
// Log::debug(sprintf('native_balance is (all transactions to %s) %s (with virtual balance)', $native->code, $this->bcround($return['native_balance'])));
// plus foreign transactions in THIS currency.
$sum = (string) $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->whereNot('transactions.transaction_currency_id', $native->id)
->where('transactions.foreign_currency_id', $native->id)
->sum('transactions.foreign_amount')
;
$return['native_balance'] = bcadd($return['native_balance'], $sum);
// Log::debug(sprintf('Foreign amount transactions add (%s only) %s, total native_balance is now %s', $native->code, $this->bcround($sum), $this->bcround($return['native_balance'])));
}
// balance(s) in other (all) currencies.
$array = $account->transactions()
$native = Amount::getNativeCurrencyByUserGroup($account->user->userGroup);
$convertToNative = Amount::convertToNative($account->user);
$accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = null !== $accountCurrency;
$currency = $hasCurrency ? $accountCurrency : $native;
$return = [
'native_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
];
// balance(s) in all currencies.
$array = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->get(['transaction_currencies.code', 'transactions.amount'])->toArray()
;
$others = $this->groupAndSumTransactions($array, 'code', 'amount');
$others = $this->groupAndSumTransactions($array, 'code', 'amount');
// Log::debug('All balances are (joined)', $others);
// if the account has no own currency preference, drop balance in favor of native balance
if ($hasCurrency && !$convertToNative) {
$return['balance'] = $others[$currency->code] ?? '0';
$return['native_balance'] = $others[$currency->code] ?? '0';
// Log::debug(sprintf('Set balance + native_balance to %s', $return['balance']));
// if there is no request to convert, take this as "balance" and "native_balance".
$return['balance'] = $others[$currency->code] ?? '0';
if (!$convertToNative) {
unset($return['native_balance']);
// Log::debug(sprintf('Set balance to %s, unset native_balance', $return['balance']));
}
// if there is a request to convert, convert to "native_balance" and use "balance" for whichever amount is in the native currency.
if ($convertToNative) {
$return['native_balance'] = $this->convertAllBalances($others, $native, $date); // todo sum all and convert.
// Log::debug(sprintf('Set native_balance to %s', $return['native_balance']));
}
// if the currency is the same as the native currency, set the native_balance to the balance for consistency.
// if($currency->id === $native->id) {
// $return['native_balance'] = $return['balance'];
// }
// either way, the balance is always combined with the virtual balance:
$virtualBalance = (string) ('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance);
if (!$hasCurrency && array_key_exists('balance', $return)) {
// Log::debug('Account has no currency preference, dropping balance in favor of native balance.');
$sum = bcadd($return['balance'], $return['native_balance']);
// Log::debug(sprintf('%s + %s = %s', $return['balance'], $return['native_balance'], $sum));
$return['native_balance'] = $sum;
unset($return['balance']);
if ($convertToNative) {
// the native balance is combined with a converted virtual_balance:
$converter = new ExchangeRateConverter();
$nativeVirtualBalance = $converter->convert($currency, $native, $date, $virtualBalance);
$return['native_balance'] = bcadd($nativeVirtualBalance, $return['native_balance']);
// Log::debug(sprintf('Native virtual balance makes the native total %s', $return['native_balance']));
}
$final = array_merge($return, $others);
// Log::debug('Return is', $final);
if (!$convertToNative) {
// if not, also increase the balance + native balance for consistency.
$return['balance'] = bcadd($return['balance'], $virtualBalance);
// Log::debug(sprintf('Virtual balance makes the (native) total %s', $return['balance']));
}
$final = array_merge($return, $others);
Log::debug('Final balance is', $final);
$cache->store($final);
return array_merge($return, $others);
// Log::debug('Return is', $final);
return $final;
}
public function filterAccountBalances(array $total, Account $account, bool $convertToNative, ?TransactionCurrency $currency = null): array
@@ -410,17 +352,18 @@ class Steam
$defaultCurrency = app('amount')->getNativeCurrency();
if ($convertToNative) {
if ($defaultCurrency->id === $currency?->id) {
Log::debug(sprintf('Unset "native_balance" and "%s" for account #%d', $defaultCurrency->code, $account->id));
Log::debug(sprintf('Unset "native_balance" and [%s] for account #%d', $defaultCurrency->code, $account->id));
unset($set['native_balance'], $set[$defaultCurrency->code]);
}
// todo rethink this logic.
if (null !== $currency && $defaultCurrency->id !== $currency->id) {
Log::debug(sprintf('Unset balance for account #%d', $account->id));
unset($set['balance']);
}
if (null === $currency) {
Log::debug(sprintf('TEMP DO NOT Drop defaultCurrency balance for account #%d', $account->id));
// unset($set[$this->defaultCurrency->code]);
Log::debug(sprintf('Unset balance for account #%d', $account->id));
unset($set['balance']);
}
}
@@ -428,16 +371,15 @@ class Steam
if (null === $currency) {
Log::debug(sprintf('Unset native_balance and make defaultCurrency balance the balance for account #%d', $account->id));
$set['balance'] = $set[$defaultCurrency->code] ?? '0';
unset($set['native_balance'], $set[$defaultCurrency->code]);
unset($set[$defaultCurrency->code]);
}
if (null !== $currency) {
Log::debug(sprintf('Unset native_balance + defaultCurrency + currencyCode balance for account #%d', $account->id));
unset($set['native_balance'], $set[$defaultCurrency->code], $set[$currency->code]);
Log::debug(sprintf('Unset [%s] + [%s] balance for account #%d', $defaultCurrency->code, $currency->code, $account->id));
unset($set[$defaultCurrency->code], $set[$currency->code]);
}
}
// put specific value first in array.
if (array_key_exists('native_balance', $set)) {
$set = ['native_balance' => $set['native_balance']] + $set;
@@ -687,4 +629,21 @@ class Steam
return $amount;
}
private function convertAllBalances(array $others, TransactionCurrency $native, Carbon $date): string
{
$total = '0';
$converter = new ExchangeRateConverter();
foreach ($others as $key => $amount) {
$currency = TransactionCurrency::where('code', $key)->first();
if (null === $currency) {
continue;
}
$current = $converter->convert($currency, $native, $date, $amount);
Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $native->code, $current));
$total = bcadd($current, $total);
}
return $total;
}
}

View File

@@ -40,7 +40,7 @@ class AccountTransformer extends AbstractTransformer
{
protected AccountRepositoryInterface $repository;
protected bool $convertToNative;
protected TransactionCurrency $default;
protected TransactionCurrency $native;
/**
* AccountTransformer constructor.
@@ -50,7 +50,7 @@ class AccountTransformer extends AbstractTransformer
$this->parameters = new ParameterBag();
$this->repository = app(AccountRepositoryInterface::class);
$this->convertToNative = Amount::convertToNative();
$this->default = Amount::getNativeCurrency();
$this->native = Amount::getNativeCurrency();
}
/**
@@ -72,7 +72,7 @@ class AccountTransformer extends AbstractTransformer
$convertToNative = Amount::convertToNative();
// get account role (will only work if the type is asset).
$default = Amount::getNativeCurrency();
$native = Amount::getNativeCurrency();
$accountRole = $this->getAccountRole($account, $accountType);
$date = $this->getDate();
$date->endOfDay();
@@ -82,10 +82,10 @@ class AccountTransformer extends AbstractTransformer
[$openingBalance, $nativeOpeningBalance, $openingBalanceDate] = $this->getOpeningBalance($account, $accountType, $convertToNative);
[$interest, $interestPeriod] = $this->getInterest($account, $accountType);
$default = $this->default;
$native = $this->native;
if (!$this->convertToNative) {
// reset default currency to NULL, not interesting.
$default = null;
// reset native currency to NULL, not interesting.
$native = null;
}
$openingBalance = app('steam')->bcround($openingBalance, $decimalPlaces);
@@ -112,7 +112,7 @@ class AccountTransformer extends AbstractTransformer
}
$currentBalance = app('steam')->bcround($finalBalance['balance'] ?? '0', $decimalPlaces);
$nativeCurrentBalance = $convertToNative ? app('steam')->bcround($finalBalance['native_balance'] ?? '0', $default->decimal_places) : null;
$nativeCurrentBalance = $convertToNative ? app('steam')->bcround($finalBalance['native_balance'] ?? '0', $native->decimal_places) : null;
return [
'id' => (string) $account->id,
@@ -127,10 +127,10 @@ class AccountTransformer extends AbstractTransformer
'currency_code' => $currencyCode,
'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $decimalPlaces,
'native_currency_id' => null === $default ? null : (string) $default->id,
'native_currency_code' => $default?->code,
'native_currency_symbol' => $default?->symbol,
'native_currency_decimal_places' => $default?->decimal_places,
'native_currency_id' => null === $native ? null : (string) $native->id,
'native_currency_code' => $native?->code,
'native_currency_symbol' => $native?->symbol,
'native_currency_decimal_places' => $native?->decimal_places,
'current_balance' => $currentBalance,
'native_current_balance' => $nativeCurrentBalance,
'current_balance_date' => $date->toAtomString(),
@@ -141,7 +141,7 @@ class AccountTransformer extends AbstractTransformer
'iban' => '' === $account->iban ? null : $account->iban,
'bic' => $this->repository->getMetaValue($account, 'BIC'),
'virtual_balance' => app('steam')->bcround($account->virtual_balance, $decimalPlaces),
'native_virtual_balance' => $this->convertToNative ? app('steam')->bcround($account->native_virtual_balance, $default->decimal_places) : null,
'native_virtual_balance' => $this->convertToNative ? app('steam')->bcround($account->native_virtual_balance, $native->decimal_places) : null,
'opening_balance' => $openingBalance,
'native_opening_balance' => $nativeOpeningBalance,
'opening_balance_date' => $openingBalanceDate,
@@ -190,9 +190,9 @@ class AccountTransformer extends AbstractTransformer
{
$currency = $this->repository->getAccountCurrency($account);
// only grab default when result is null:
// only grab native when result is null:
if (null === $currency) {
$currency = $this->default;
$currency = $this->native;
}
$currencyId = (string) $currency->id;
$currencyCode = $currency->code;

Some files were not shown because too many files have changed in this diff Show More