Compare commits

...

88 Commits

Author SHA1 Message Date
github-actions
17b0b1f43f Auto commit for release 'v6.1.24' on 2024-11-23 2024-11-23 21:07:01 +01:00
James Cole
b61df5ec19 Update changelog 2024-11-23 21:01:58 +01:00
James Cole
1ac7275f83 Fix https://github.com/firefly-iii/firefly-iii/issues/9491 2024-11-23 20:59:08 +01:00
github-actions
cd10d04907 Auto commit for release 'v6.1.23' on 2024-11-23 2024-11-23 19:19:46 +01:00
James Cole
f9b76fcb8b Update changelog for new release. 2024-11-23 19:14:15 +01:00
James Cole
093fa067e6 Merge branches 'develop' and 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-23 16:15:00 +01:00
James Cole
fa655f065b Part of new API, cleanup code. 2024-11-23 16:14:47 +01:00
James Cole
c8f2244912 Merge pull request #9483 from antoniomrfranco/fix/report-sum-foreign-amount
fix: include foreign_amount in transaction sum calculation
2024-11-23 09:45:53 +01:00
James Cole
f3a20e14a6 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-23 09:06:36 +01:00
James Cole
33ad47b115 Add import 2024-11-23 09:06:29 +01:00
James Cole
775424d3b7 Merge pull request #9488 from antoniomrfranco/fix/transfer-dest-foreign-info
fix: set dest foreign_amount and foreign_currency_id for foreign transfers
2024-11-23 09:06:07 +01:00
Antônio Franco
c9c86bbd1d fix: set dest foreign_amount and foreign_currency_id for foreign transfers 2024-11-22 13:52:33 -03:00
Antônio Franco
f76a6ad85c fix: include foreign_amount in transaction sum calculation 2024-11-22 09:52:35 -03:00
James Cole
2138b14d89 Add to update as well. 2024-11-22 06:04:32 +01:00
James Cole
1bf61f57f5 Add UTC support. 2024-11-22 06:03:29 +01:00
James Cole
07b55bd71f Fix https://github.com/firefly-iii/firefly-iii/issues/9477 2024-11-19 06:31:49 +01:00
github-actions
8d2d3d4002 Auto commit for release 'develop' on 2024-11-18 2024-11-18 04:18:51 +01:00
James Cole
d182b4b4a6 Fix tables. 2024-11-17 07:12:54 +01:00
James Cole
60f6a91fe4 Cast fields to string and drop unused table. 2024-11-15 19:13:37 +01:00
James Cole
ec89a2f956 Add missing commas. 2024-11-15 18:59:57 +01:00
James Cole
87113d7181 Merge pull request #9468 from yparitcher/main 2024-11-15 06:17:17 +01:00
yparitcher
59fae290e5 Transaction Model: explicitly cast decimal to string
Laravel defers to PDO & the underlying database as to what type decimals are cast as.
When using sqlite text that match a float will be returned as a float, while mySql always returns a string for a decimal.
This leads to crashes with sqlite while trying to import tranansactions. (It does not happen every transaction, only when the balance{before,after} coming from the database is floatable)

`FireflyIII\Support\Models\AccountBalanceCalculator::getLatesBalance(): Return value must be of type string, float returned.`

Fixes: #9458

Signed-off-by: yparitcher <y@paritcher.com>
2024-11-15 00:03:21 -05:00
James Cole
1a8ba2ce53 Merge branch 'main' into develop 2024-11-14 06:37:23 +01:00
James Cole
dddaa25d86 Merge pull request #9465 from firefly-iii/dependabot/composer/composer-2bb02343b8 2024-11-13 18:35:54 +01:00
James Cole
f28341587a Merge branch 'main' into develop 2024-11-13 18:14:07 +01:00
James Cole
5593bf3e08 Fix https://github.com/firefly-iii/firefly-iii/issues/9466 2024-11-13 18:12:40 +01:00
dependabot[bot]
92574a7a9d Bump symfony/http-client in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [symfony/http-client](https://github.com/symfony/http-client).


Updates `symfony/http-client` from 7.1.7 to 7.1.8
- [Release notes](https://github.com/symfony/http-client/releases)
- [Changelog](https://github.com/symfony/http-client/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-client/compare/v7.1.7...v7.1.8)

---
updated-dependencies:
- dependency-name: symfony/http-client
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-13 16:42:52 +00:00
James Cole
e049266f5d Merge pull request #9463 from firefly-iii/dependabot/composer/composer-a1e7ad0bd3 2024-11-13 08:46:50 +01:00
dependabot[bot]
5b3e6fcb07 Bump laravel/framework in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [laravel/framework](https://github.com/laravel/framework).


Updates `laravel/framework` from 11.30.0 to 11.31.0
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v11.30.0...v11.31.0)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 22:48:57 +00:00
James Cole
b0bfb556db Fix https://github.com/firefly-iii/firefly-iii/issues/9458 2024-11-11 19:40:00 +01:00
James Cole
484acbcb45 Merge branch 'main' into develop 2024-11-11 05:59:45 +01:00
James Cole
cdc802cfb8 Fix broken links 2024-11-11 05:59:35 +01:00
github-actions
582671ca84 Auto commit for release 'develop' on 2024-11-11 2024-11-11 04:10:58 +01:00
James Cole
22498b5804 Expand access rights 2024-11-10 15:30:02 +01:00
James Cole
87f277a482 Small change in time(zone) representation. 2024-11-10 07:30:10 +01:00
James Cole
ae0d74f57a Fix https://github.com/firefly-iii/firefly-iii/issues/9451 2024-11-10 07:10:30 +01:00
James Cole
0ae5593dde Playing around with local date time parsing. 2024-11-09 20:38:30 +01:00
James Cole
0d11769590 Add better timezone support. 2024-11-09 12:19:01 +01:00
github-actions
b7d8daf013 Auto commit for release 'v6.1.22' on 2024-11-09 2024-11-09 06:35:28 +01:00
James Cole
a9c0126b05 Clean up changelog. 2024-11-09 06:30:39 +01:00
James Cole
6bc5a57d10 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-09 06:23:37 +01:00
James Cole
2714ee96f1 Fix https://github.com/firefly-iii/firefly-iii/issues/9447 2024-11-09 06:23:28 +01:00
github-actions
524d382b7a Auto commit for release 'develop' on 2024-11-08 2024-11-08 21:20:33 +01:00
James Cole
2723e05d2a Fix https://github.com/firefly-iii/firefly-iii/issues/9444 2024-11-08 21:13:41 +01:00
James Cole
6dd9bda6b4 Merge branch 'main' into develop 2024-11-08 21:08:52 +01:00
James Cole
44449bc716 Add changelog 2024-11-08 21:08:28 +01:00
James Cole
b17d8edb50 Add changelog line 2024-11-08 21:04:08 +01:00
James Cole
578072238a Fix https://github.com/firefly-iii/firefly-iii/issues/9443 2024-11-08 21:03:33 +01:00
James Cole
b4edd3dcc4 Catch potential nullpointers 2024-11-08 21:02:36 +01:00
James Cole
068094caac Update BillUpdateService.php
Catch nullpointers

Signed-off-by: James Cole <james@firefly-iii.org>
2024-11-08 09:30:24 +01:00
James Cole
deb58e617d Update PiggyBankObserver.php
Catch nullpointer.

Signed-off-by: James Cole <james@firefly-iii.org>
2024-11-08 09:29:35 +01:00
James Cole
baca0c1120 Update stale.yml
Signed-off-by: James Cole <james@firefly-iii.org>
2024-11-07 10:40:38 +01:00
github-actions
02543438a4 Auto commit for release 'develop' on 2024-11-07 2024-11-07 03:36:46 +01:00
James Cole
d507e59038 Catch nullpointer. 2024-11-07 03:31:55 +01:00
James Cole
9d0fd7ef1b Deprecate constants 2024-11-07 03:29:44 +01:00
James Cole
dbef5e2143 Merge branch 'main' into develop 2024-11-07 03:26:38 +01:00
James Cole
04eca755d2 Smarter (faster) way of updating timezone info. 2024-11-07 03:26:07 +01:00
James Cole
7883692196 Merge pull request #9438 from firefly-iii/dependabot/composer/composer-159f11b3b5 2024-11-07 03:01:55 +01:00
dependabot[bot]
8f64977cb9 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.14.0 to 3.14.1
- [Changelog](https://github.com/twigphp/Twig/blob/v3.14.1/CHANGELOG)
- [Commits](https://github.com/twigphp/Twig/compare/v3.14.0...v3.14.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 19:53:31 +00:00
github-actions
f94fdc4979 Auto commit for release 'develop' on 2024-11-06 2024-11-06 19:53:30 +01:00
James Cole
a0a0e28447 Small rewording 2024-11-06 19:44:08 +01:00
James Cole
f6f7783b94 Add command to upgrade 2024-11-06 19:42:09 +01:00
James Cole
d233cc1de8 Update packages. 2024-11-06 19:41:15 +01:00
James Cole
37671499c8 Merge branch 'main' into develop
# Conflicts:
#	composer.lock
2024-11-06 19:39:11 +01:00
James Cole
c83b79998d Undo casts 2024-11-06 19:33:10 +01:00
James Cole
ed842c2b42 Fix and undo some enums 2024-11-06 19:32:32 +01:00
James Cole
8c5f114339 Add enums to types. 2024-11-06 19:22:28 +01:00
James Cole
8b2f1d0b4f Add timezones to existing objects. 2024-11-06 19:22:10 +01:00
James Cole
591a1b3050 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-06 19:16:56 +01:00
James Cole
42ec3fe02b Merge pull request #9437 from firefly-iii/dependabot/composer/composer-ee86c9e087
Bump the composer group across 1 directory with 2 updates
2024-11-06 19:16:42 +01:00
dependabot[bot]
370a398b5e Bump the composer group across 1 directory with 2 updates
Bumps the composer group with 2 updates in the / directory: [symfony/http-foundation](https://github.com/symfony/http-foundation) and [symfony/process](https://github.com/symfony/process).


Updates `symfony/http-foundation` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/http-foundation/releases)
- [Changelog](https://github.com/symfony/http-foundation/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-foundation/compare/v7.1.5...v7.1.7)

Updates `symfony/process` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/process/releases)
- [Changelog](https://github.com/symfony/process/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/process/compare/v7.1.5...v7.1.7)

---
updated-dependencies:
- dependency-name: symfony/http-foundation
  dependency-type: indirect
  dependency-group: composer
- dependency-name: symfony/process
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 18:12:57 +00:00
James Cole
554d89b6e9 Merge pull request #9435 from firefly-iii/dependabot/composer/composer-cdaff7c96f 2024-11-06 19:12:21 +01:00
dependabot[bot]
cb049f5dda Bump symfony/http-client in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [symfony/http-client](https://github.com/symfony/http-client).


Updates `symfony/http-client` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/http-client/releases)
- [Changelog](https://github.com/symfony/http-client/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-client/compare/v7.1.5...v7.1.7)

---
updated-dependencies:
- dependency-name: symfony/http-client
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 15:27:07 +00:00
James Cole
0728668d41 Add timezone fields. 2024-11-06 14:10:34 +01:00
github-actions
dfc187874e Auto commit for release 'develop' on 2024-11-06 2024-11-06 14:02:38 +01:00
James Cole
225588f3e7 Update changelog and API 2024-11-06 13:50:47 +01:00
James Cole
06cc6c29aa Expand changelog. 2024-11-06 12:08:14 +01:00
James Cole
b2d4469908 Mild code cleanup. 2024-11-06 12:01:29 +01:00
James Cole
c398383905 Add column to fillable array 2024-11-06 11:59:37 +01:00
James Cole
7af9dce33b Add timezone to date objects 2024-11-06 11:57:12 +01:00
James Cole
038790a5d6 Add timezone info to new objects. 2024-11-06 11:12:26 +01:00
James Cole
fb3295bde1 Clean up code. 2024-11-06 11:12:12 +01:00
James Cole
43a4fd2ecb Fix https://github.com/firefly-iii/firefly-iii/issues/9427 2024-11-06 11:12:04 +01:00
James Cole
899c72d068 Add timezone. Cheap and backwards compatible. 2024-11-06 11:11:52 +01:00
James Cole
d118c0d886 Fix date requests 2024-11-06 11:11:38 +01:00
github-actions
6d4004d1ed Auto commit for release 'develop' on 2024-11-04 2024-11-04 04:14:51 +01:00
James Cole
ae60cd5b28 Fix https://github.com/firefly-iii/firefly-iii/issues/9294 2024-11-03 19:42:48 +01:00
James Cole
df5756dc86 Remove labels after finding them 2024-10-27 20:47:50 +01:00
86 changed files with 2705 additions and 1172 deletions

View File

@@ -72,16 +72,16 @@
},
{
"name": "composer/pcre",
"version": "3.3.1",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4"
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4",
"reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
@@ -91,8 +91,8 @@
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.11.10",
"phpstan/phpstan-strict-rules": "^1.1",
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"type": "library",
@@ -131,7 +131,7 @@
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.1"
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
@@ -147,7 +147,7 @@
"type": "tidelift"
}
],
"time": "2024-08-27T18:44:43+00:00"
"time": "2024-11-12T16:29:46+00:00"
},
{
"name": "composer/semver",
@@ -1259,16 +1259,16 @@
},
{
"name": "symfony/console",
"version": "v7.1.6",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57"
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"shasum": ""
},
"require": {
@@ -1332,7 +1332,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.1.6"
"source": "https://github.com/symfony/console/tree/v7.1.8"
},
"funding": [
{
@@ -1348,7 +1348,7 @@
"type": "tidelift"
}
],
"time": "2024-10-09T08:46:59+00:00"
"time": "2024-11-06T14:23:19+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2246,16 +2246,16 @@
},
{
"name": "symfony/process",
"version": "v7.1.6",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e"
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892",
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892",
"shasum": ""
},
"require": {
@@ -2287,7 +2287,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.1.6"
"source": "https://github.com/symfony/process/tree/v7.1.8"
},
"funding": [
{
@@ -2303,7 +2303,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-11-06T14:23:19+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2452,16 +2452,16 @@
},
{
"name": "symfony/string",
"version": "v7.1.6",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "61b72d66bf96c360a727ae6232df5ac83c71f626"
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/61b72d66bf96c360a727ae6232df5ac83c71f626",
"reference": "61b72d66bf96c360a727ae6232df5ac83c71f626",
"url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281",
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281",
"shasum": ""
},
"require": {
@@ -2519,7 +2519,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.1.6"
"source": "https://github.com/symfony/string/tree/v7.1.8"
},
"funding": [
{
@@ -2535,7 +2535,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-11-13T13:31:21+00:00"
}
],
"packages-dev": [],

View File

@@ -4,6 +4,7 @@
feature:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
unlabel: feature
comment: |
Hi there!
@@ -32,6 +33,7 @@ epic:
Thank you for your contributions.
enhancement:
unlabel: enhancement
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |

View File

@@ -12,6 +12,7 @@ jobs:
permissions:
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale PRs
actions: write
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9

View File

@@ -4,6 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2024
- Antônio Franco
- yparitcher
- Jhon Pedroza
- mzhubail
- tasnim

View File

@@ -112,7 +112,12 @@ class StoreController extends Controller
return response()->json([], 422);
}
$helper->saveAttachmentFromApi($attachment, $body);
$result = $helper->saveAttachmentFromApi($attachment, $body);
if (false === $result) {
app('log')->error('Could not save attachment from API.');
return response()->json([], 422);
}
return response()->json([], 204);
}

View File

@@ -52,7 +52,7 @@ class AboutController extends Controller
$data
= [
'version' => config('firefly.version'),
'api_version' => config('firefly.api_version'),
'api_version' => config('firefly.version'),
'php_version' => $phpVersion,
'os' => $phpOs,
'driver' => $currentDriver,

View File

@@ -46,6 +46,8 @@ class DateRequest extends FormRequest
{
$start = $this->getCarbonDate('start');
$end = $this->getCarbonDate('end');
$start->startOfDay();
$end->endOfDay();
if ($start->diffInYears($end, true) > 5) {
throw new FireflyException('Date range out of range.');
}

View File

@@ -138,7 +138,7 @@ class StoreRequest extends FormRequest
// 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|api-v%s', config('firefly.version'), config('firefly.api_version')),
'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']),

View File

@@ -44,7 +44,7 @@ class DateRequest extends FormRequest
public function getAll(): array
{
return [
'start' => $this->getCarbonDate('start'),
'start' => $this->getCarbonDate('start')->startOfDay(),
'end' => $this->getCarbonDate('end')->endOfDay(),
];
}

View File

@@ -147,7 +147,7 @@ class StoreRequest extends FormRequest
// 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|api-v%s', config('firefly.version'), config('firefly.api_version')),
'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']),

View File

@@ -48,8 +48,19 @@ class StoreRequest extends FormRequest
public function rules(): array
{
$roles = [];
foreach (UserRoleEnum::cases() as $role) {
$roles[] = $role->value;
}
$string = implode(',', $roles);
return [
'title' => 'unique:user_groups,title|required|min:1|max:255',
'title' => 'unique:user_groups,title|required|min:1|max:255',
'members' => 'required|min:1',
'members.*.user_email' => 'email|missing_with:members.*.user_id',
'members.*.user_id' => 'integer|exists:users,id|missing_with:members.*.user_email',
'members.*.roles' => 'required|array|min:1',
'members.*.roles.*' => sprintf('required|in:%s', $string),
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
/**
* Class SeparateTimezoneCaster
*
* Checks if the object has a separate _tz value. If it does, it will use that timezone to parse the date.
* If it is NULL, it will use the system's timezone.
*
* At some point a user's database consists entirely of UTC dates, and we won't need this anymore. However,
* the completeness of this migration is not yet guaranteed.
*/
class SeparateTimezoneCaster implements CastsAttributes
{
/**
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): ?Carbon
{
if ('' === $value || null === $value) {
return null;
}
$timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone');
return Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone'));
}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
return $value;
}
}

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/*
* AddTimezonesToDates.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
namespace FireflyIII\Console\Commands\Integrity;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Models\AccountBalance;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\InvitedUser;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
class AddTimezonesToDates extends Command
{
use ShowsFriendlyMessages;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:add-timezones-to-dates';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Make sure all dates have a timezone.';
public static array $models = [
AccountBalance::class => ['date'], // done
AvailableBudget::class => ['start_date', 'end_date'], // done
Bill::class => ['date', 'end_date', 'extension_date'], // done
BudgetLimit::class => ['start_date', 'end_date'], // done
CurrencyExchangeRate::class => ['date'], // done
InvitedUser::class => ['expires'],
PiggyBankEvent::class => ['date'],
PiggyBankRepetition::class => ['startdate', 'targetdate'],
PiggyBank::class => ['startdate', 'targetdate'], // done
Recurrence::class => ['first_date', 'repeat_until', 'latest_date'],
Tag::class => ['date'],
TransactionJournal::class => ['date'],
];
/**
* Execute the console command.
*/
public function handle(): void
{
foreach (self::$models as $model => $fields) {
$this->addTimezoneToModel($model, $fields);
}
// not yet in UTC mode
FireflyConfig::set('utc', false);
}
private function addTimezoneToModel(string $model, array $fields): void
{
foreach ($fields as $field) {
$this->addTimezoneToModelField($model, $field);
}
}
private function addTimezoneToModelField(string $model, string $field): void
{
$shortModel = str_replace('FireflyIII\Models\\', '', $model);
$timezoneField = sprintf('%s_tz', $field);
$count = 0;
try {
$count = $model::whereNull($timezoneField)->count();
} catch (QueryException $e) {
$this->friendlyError(sprintf('Cannot add timezone information to field "%s" of model "%s". Field does not exist.', $field, $shortModel));
Log::error($e->getMessage());
}
if (0 === $count) {
$this->friendlyPositive(sprintf('Timezone information is present in field "%s" of model "%s".', $field, $shortModel));
return;
}
$this->friendlyInfo(sprintf('Adding timezone information to field "%s" of model "%s".', $field, $shortModel));
$model::whereNull($timezoneField)->update([$timezoneField => config('app.timezone')]);
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/*
* ConvertDatesToUTC.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
namespace FireflyIII\Console\Commands\Integrity;
use Carbon\Carbon;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class ConvertDatesToUTC extends Command
{
use ShowsFriendlyMessages;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:migrate-to-utc';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Convert stored dates to UTC.';
/**
* Execute the console command.
*/
public function handle(): int
{
/**
* @var string $model
* @var array $fields
*/
foreach (AddTimezonesToDates::$models as $model => $fields) {
$this->ConvertModeltoUTC($model, $fields);
}
// tell the system we are now in UTC mode.
FireflyConfig::set('utc', true);
return Command::SUCCESS;
}
private function ConvertModeltoUTC(string $model, array $fields): void
{
/** @var string $field */
foreach ($fields as $field) {
$this->convertFieldtoUTC($model, $field);
}
}
private function convertFieldtoUTC(string $model, string $field): void
{
$this->info(sprintf('Converting %s.%s to UTC', $model, $field));
$shortModel = str_replace('FireflyIII\Models\\', '', $model);
$timezoneField = sprintf('%s_tz', $field);
$items = new Collection();
$timeZone = config('app.timezone');
try {
$items = $model::where($timezoneField, $timeZone)->get();
} catch (QueryException $e) {
$this->friendlyError(sprintf('Cannot find timezone information to field "%s" of model "%s". Field does not exist.', $field, $shortModel));
Log::error($e->getMessage());
}
if (0 === $items->count()) {
$this->friendlyPositive(sprintf('All timezone information is UTC in field "%s" of model "%s".', $field, $shortModel));
return;
}
$this->friendlyInfo(sprintf('Converting field "%s" of model "%s" to UTC.', $field, $shortModel));
$items->each(
function ($item) use ($field, $timezoneField): void {
/** @var Carbon $date */
$date = Carbon::parse($item->{$field}, $item->{$timezoneField});
$date->setTimezone('UTC');
$item->{$field} = $date->format('Y-m-d H:i:s');
$item->{$timezoneField} = 'UTC';
$item->save();
}
);
}
}

View File

@@ -47,6 +47,7 @@ class ReportIntegrity extends Command
return 1;
}
$commands = [
'firefly-iii:add-timezones-to-dates',
'firefly-iii:create-group-memberships',
'firefly-iii:report-empty-objects',
'firefly-iii:report-sum',

View File

@@ -58,7 +58,7 @@ class ReportSum extends Command
/** @var User $user */
foreach ($userRepository->all() as $user) {
$sum = (string)$user->transactions()->sum('amount');
$sum = (string)$user->transactions()->selectRaw('SUM(amount) + SUM(foreign_amount) as total')->value('total');
if (!is_numeric($sum)) {
$message = sprintf('Error: Transactions for user #%d (%s) have an invalid sum ("%s").', $user->id, $user->email, $sum);
$this->friendlyError($message);

View File

@@ -80,7 +80,7 @@ class ForceMigration extends Command
sleep(2);
Schema::dropIfExists('migrations');
$this->friendlyLine('Re-run all migrations...');
Artisan::call('migrate', ['--seed' => true]);
Artisan::call('migrate', ['--seed' => true, '--force' => true]);
sleep(2);
$this->friendlyLine('');
$this->friendlyWarning('There is a good chance you just saw a lot of error messages.');

View File

@@ -47,7 +47,7 @@ class FixPostgresSequences extends Command
return 0;
}
$this->friendlyLine('Going to verify PostgreSQL table sequences.');
$tablesToCheck = ['2fa_tokens', 'account_meta', 'account_types', 'accounts', 'attachments', 'auto_budgets', 'available_budgets', 'bills', 'budget_limits', 'budget_transaction', 'budget_transaction_journal', 'budgets', 'categories', 'category_transaction', 'category_transaction_journal', 'configuration', 'currency_exchange_rates', 'failed_jobs', 'group_journals', 'jobs', 'journal_links', 'journal_meta', 'limit_repetitions', 'link_types', 'locations', 'migrations', 'notes', 'oauth_clients', 'oauth_personal_access_clients', 'object_groups', 'permissions', 'piggy_bank_events', 'piggy_bank_repetitions', 'piggy_banks', 'preferences', 'recurrences', 'recurrences_meta', 'recurrences_repetitions', 'recurrences_transactions', 'roles', 'rt_meta', 'rule_actions', 'rule_groups', 'rule_triggers', 'rules', 'tag_transaction_journal', 'tags', 'transaction_currencies', 'transaction_groups', 'transaction_journals', 'transaction_types', 'transactions', 'users', 'webhook_attempts', 'webhook_messages', 'webhooks'];
$tablesToCheck = ['2fa_tokens', 'account_meta', 'account_types', 'accounts', 'attachments', 'auto_budgets', 'available_budgets', 'bills', 'budget_limits', 'budget_transaction', 'budget_transaction_journal', 'budgets', 'categories', 'category_transaction', 'category_transaction_journal', 'configuration', 'currency_exchange_rates', 'failed_jobs', 'group_journals', 'jobs', 'journal_links', 'journal_meta', 'link_types', 'locations', 'migrations', 'notes', 'oauth_clients', 'oauth_personal_access_clients', 'object_groups', 'permissions', 'piggy_bank_events', 'piggy_bank_repetitions', 'piggy_banks', 'preferences', 'recurrences', 'recurrences_meta', 'recurrences_repetitions', 'recurrences_transactions', 'roles', 'rt_meta', 'rule_actions', 'rule_groups', 'rule_triggers', 'rules', 'tag_transaction_journal', 'tags', 'transaction_currencies', 'transaction_groups', 'transaction_journals', 'transaction_types', 'transactions', 'users', 'webhook_attempts', 'webhook_messages', 'webhooks'];
foreach ($tablesToCheck as $tableToCheck) {
$this->friendlyLine(sprintf('Checking the next id sequence for table "%s".', $tableToCheck));

View File

@@ -154,7 +154,7 @@ class TransactionIdentifier extends Command
app('log')->error($e->getMessage());
$this->friendlyError('Firefly III could not find the "identifier" field in the "transactions" table.');
$this->friendlyError(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
$this->friendlyError('Please run "php artisan migrate" to add this field to the table.');
$this->friendlyError('Please run "php artisan migrate --force" to add this field to the table.');
$this->friendlyError('Then, run "php artisan firefly:upgrade-database" to try again.');
return null;

View File

@@ -67,6 +67,7 @@ class UpgradeDatabase extends Command
'firefly-iii:restore-oauth-keys',
'firefly-iii:correct-account-balance',
// also just in case, some integrity commands:
'firefly-iii:add-timezones-to-dates',
'firefly-iii:create-group-memberships',
'firefly-iii:upgrade-group-information',
'firefly-iii:upgrade-currency-preferences',

View File

@@ -30,6 +30,7 @@ namespace FireflyIII\Enums;
enum UserRoleEnum: string
{
// most basic rights, cannot see other members, can see everything else.
// includes reading of metadata
case READ_ONLY = 'ro';
// required to even USE the group properly (in this order)
@@ -38,6 +39,15 @@ enum UserRoleEnum: string
// required to edit, add or change categories/tags/object-groups
case MANAGE_META = 'mng_meta';
// read other objects and things.
case READ_BUDGETS = 'read_budgets';
case READ_PIGGY_BANKS = 'read_piggies';
case READ_SUBSCRIPTIONS = 'read_subscriptions';
case READ_RULES = 'read_rules';
case READ_RECURRING = 'read_recurring';
case READ_WEBHOOKS = 'read_webhooks';
case READ_CURRENCIES = 'read_currencies';
// manage other financial objects:
case MANAGE_BUDGETS = 'mng_budgets';
case MANAGE_PIGGY_BANKS = 'mng_piggies';

View File

@@ -58,20 +58,23 @@ class BillFactory
/** @var Bill $bill */
$bill = Bill::create(
[
'name' => $data['name'],
'match' => 'MIGRATED_TO_RULES',
'amount_min' => $data['amount_min'],
'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $currency->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'end_date' => $data['end_date'] ?? null,
'extension_date' => $data['extension_date'] ?? null,
'repeat_freq' => $data['repeat_freq'],
'skip' => $skip,
'automatch' => true,
'active' => $active,
'name' => $data['name'],
'match' => 'MIGRATED_TO_RULES',
'amount_min' => $data['amount_min'],
'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $currency->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'date_tz' => $data['date']->format('e'),
'end_date' => $data['end_date'] ?? null,
'end_date_tz' => $data['end_date']?->format('e'),
'extension_date' => $data['extension_date'] ?? null,
'extension_date_tz' => $data['extension_date']?->format('e'),
'repeat_freq' => $data['repeat_freq'],
'skip' => $skip,
'automatch' => true,
'active' => $active,
]
);
} catch (QueryException $e) {

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
@@ -43,6 +44,7 @@ use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
use FireflyIII\Validation\AccountValidator;
@@ -157,7 +159,7 @@ class TransactionJournalFactory
$this->errorIfDuplicate($row['import_hash_v2']);
/** Some basic fields */
// Some basic fields
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? today(config('app.timezone'));
$order = $row['order'] ?? 0;
@@ -170,6 +172,13 @@ class TransactionJournalFactory
// Manipulate basic fields
$carbon->setTimezone(config('app.timezone'));
// 2024-11-19, overrule timezone with UTC and store it as UTC.
if (FireflyConfig::get('utc', false)->data) {
$carbon->setTimezone('UTC');
}
// $carbon->setTimezone('UTC');
try {
// validate source and destination using a new Validator.
$this->validateAccounts($row);
@@ -205,7 +214,7 @@ class TransactionJournalFactory
app('log')->debug('Done with getAccount(2x)');
// this is the moment for a reconciliation sanity check (again).
if (TransactionType::RECONCILIATION === $type->type) {
if (TransactionTypeEnum::RECONCILIATION->value === $type->type) {
[$sourceAccount, $destinationAccount] = $this->reconciliationSanityCheck($sourceAccount, $destinationAccount);
}
@@ -225,7 +234,8 @@ class TransactionJournalFactory
'bill_id' => $billId,
'transaction_currency_id' => $currency->id,
'description' => substr($description, 0, 1000),
'date' => $carbon->format('Y-m-d H:i:s'),
'date' => $carbon,
'date_tz' => $carbon->format('e'),
'order' => $order,
'tag_count' => 0,
'completed' => 0,

View File

@@ -131,12 +131,14 @@ class BudgetLimitHandler
app('log')->debug(sprintf('Will create AB for period %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d')));
$availableBudget = new AvailableBudget(
[
'user_id' => $budgetLimit->budget->user->id,
'user_group_id' => $budgetLimit->budget->user->user_group_id,
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
'start_date' => $current,
'end_date' => $currentEnd,
'amount' => $amount,
'user_id' => $budgetLimit->budget->user->id,
'user_group_id' => $budgetLimit->budget->user->user_group_id,
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
'start_date' => $current,
'start_date_tz' => $current->format('e'),
'end_date' => $currentEnd,
'end_date_tz' => $currentEnd->format('e'),
'amount' => $amount,
]
);
$availableBudget->save();

View File

@@ -58,6 +58,7 @@ class PiggyBankEventHandler
'piggy_bank_id' => $event->piggyBank->id,
'transaction_journal_id' => $journal?->id,
'date' => $date->format('Y-m-d'),
'date_tz' => $date->format('e'),
'amount' => $event->amount,
]
);

View File

@@ -37,7 +37,9 @@ class PiggyBankObserver
$repetition = new PiggyBankRepetition();
$repetition->piggyBank()->associate($piggyBank);
$repetition->startdate = $piggyBank->startdate;
$repetition->startdate_tz = $piggyBank->startdate->format('e');
$repetition->targetdate = $piggyBank->targetdate;
$repetition->targetdate_tz = $piggyBank->targetdate?->format('e');
$repetition->currentamount = '0';
$repetition->save();
}

View File

@@ -93,7 +93,7 @@ trait AttachmentCollection
->where(
static function (EloquentBuilder $q1): void { // @phpstan-ignore-line
$q1->where('attachments.attachable_type', TransactionJournal::class);
$q1->where('attachments.uploaded', true);
// $q1->where('attachments.uploaded', true);
$q1->whereNull('attachments.deleted_at');
$q1->orWhereNull('attachments.attachable_type');
}

View File

@@ -117,6 +117,7 @@ class GroupCollector implements GroupCollectorInterface
'transaction_journals.transaction_type_id',
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.date_tz',
'transaction_journals.order',
// types

View File

@@ -40,6 +40,7 @@ use FireflyIII\Support\Http\Controllers\ChartGeneration;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class AccountController.
@@ -300,13 +301,13 @@ class AccountController extends Controller
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
app('log')->debug('Default set is ', $defaultSet);
Log::debug('Default set is ', $defaultSet);
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
app('log')->debug('Frontpage preference set is ', $frontpageArray);
Log::debug('Frontpage preference set is ', $frontpageArray);
if (0 === count($frontpageArray)) {
app('preferences')->set('frontpageAccounts', $defaultSet);
app('log')->debug('frontpage set is empty!');
Log::debug('frontpage set is empty!');
}
$accounts = $repository->getAccountsById($frontpageArray);
@@ -414,7 +415,7 @@ class AccountController extends Controller
*/
private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array
{
app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
Log::debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
$locale = app('steam')->getLocale();
$step = $this->calculateStep($start, $end);
$result = [
@@ -424,13 +425,13 @@ class AccountController extends Controller
];
$entries = [];
$current = clone $start;
app('log')->debug(sprintf('Step is %s', $step));
Log::debug(sprintf('Step is %s', $step));
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
// have to make sure this chart is always based on the balance at the END of the period.
// This period depends on the size of the chart
$current = app('navigation')->endOfX($current, $step, null);
app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d')));
Log::debug(sprintf('$current date is %s', $current->format('Y-m-d')));
if ('1D' === $step) {
// per day the entire period, balance for every day.
$format = (string)trans('config.month_and_day_js', [], $locale);
@@ -447,7 +448,7 @@ class AccountController extends Controller
}
if ('1W' === $step || '1M' === $step || '1Y' === $step) {
while ($end >= $current) {
app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d')));
Log::debug(sprintf('Current is: %s', $current->format('Y-m-d')));
$balance = (float)app('steam')->balance($account, $current, $currency);
$label = app('navigation')->periodShow($current, $step);
$entries[$label] = $balance;

View File

@@ -151,6 +151,7 @@ class CategoryController extends Controller
*/
private function reportPeriodChart(Collection $accounts, Carbon $start, Carbon $end, ?Category $category): array
{
$income = [];
$expenses = [];
$categoryId = 0;
@@ -169,8 +170,8 @@ class CategoryController extends Controller
$categoryId = $category->id;
// this gives us all currencies
$collection = new Collection([$category]);
$expenses = $opsRepository->listExpenses($start, $end, null, $collection);
$income = $opsRepository->listIncome($start, $end, null, $collection);
$expenses = $opsRepository->listExpenses($start, $end, $accounts, $collection);
$income = $opsRepository->listIncome($start, $end, $accounts, $collection);
}
$currencies = array_unique(array_merge(array_keys($income), array_keys($expenses)));
$periods = app('navigation')->listOfPeriods($start, $end);

View File

@@ -122,6 +122,7 @@ class CategoryReportController extends Controller
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse
{
$result = [];
$earned = $this->opsRepository->listIncome($start, $end, $accounts, $categories);

View File

@@ -24,18 +24,19 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Controllers\BasicDataSupport;
use FireflyIII\Support\Http\Controllers\ChartGeneration;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class ReportController.
@@ -88,7 +89,7 @@ class ReportController extends Controller
$includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth');
$result = null === $includeNetWorth ? true : '1' === $includeNetWorth;
if (false === $result) {
app('log')->debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
Log::debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
}
return $result;
@@ -136,6 +137,7 @@ class ReportController extends Controller
*/
public function operations(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{
$end->endOfDay();
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty('chart.report.operations');
@@ -146,7 +148,8 @@ class ReportController extends Controller
// return response()->json($cache->get());
}
app('log')->debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
Log::debug(sprintf('Period: %s to %s', $start->toW3cString(), $end->toW3cString()));
$format = app('navigation')->preferredCarbonFormat($start, $end);
$titleFormat = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
@@ -158,7 +161,14 @@ class ReportController extends Controller
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->withAccountInformation();
$collector->setXorAccounts($accounts);
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::RECONCILIATION, TransactionType::TRANSFER]);
$collector->setTypes(
[
TransactionTypeEnum::WITHDRAWAL,
TransactionTypeEnum::DEPOSIT,
TransactionTypeEnum::RECONCILIATION,
TransactionTypeEnum::TRANSFER,
]
);
$journals = $collector->getExtractedJournals();
// loop. group by currency and by period.
@@ -184,15 +194,25 @@ class ReportController extends Controller
// deposit = incoming
// transfer or reconcile or opening balance, and these accounts are the destination.
if (TransactionType::DEPOSIT === $journal['transaction_type_type'] || ((TransactionType::TRANSFER === $journal['transaction_type_type'] || TransactionType::RECONCILIATION === $journal['transaction_type_type'] || TransactionType::OPENING_BALANCE === $journal['transaction_type_type']) && in_array($journal['destination_account_id'], $ids, true))) {
if (
TransactionTypeEnum::DEPOSIT->value === $journal['transaction_type_type']
|| ((
TransactionTypeEnum::TRANSFER->value === $journal['transaction_type_type']
|| TransactionTypeEnum::RECONCILIATION->value === $journal['transaction_type_type']
|| TransactionTypeEnum::OPENING_BALANCE->value === $journal['transaction_type_type']
)
&& in_array($journal['destination_account_id'], $ids, true))) {
$key = 'earned';
}
$data[$currencyId][$period][$key] = bcadd($data[$currencyId][$period][$key], $amount);
}
// loop this data, make chart bars for each currency:
Log::debug('Looping data');
/** @var array $currency */
foreach ($data as $currency) {
Log::debug(sprintf('Now processing currency "%s"', $currency['currency_name']));
$income = [
'label' => (string)trans('firefly.box_earned_in_currency', ['currency' => $currency['currency_name']]),
'type' => 'bar',
@@ -214,12 +234,15 @@ class ReportController extends Controller
// loop all possible periods between $start and $end
$currentStart = clone $start;
$currentEnd = clone $end;
Log::debug(sprintf('START current start and end: %s and %s', $currentStart->toW3cString(), $currentEnd->toW3cString()));
// #8374. Sloppy fix for yearly charts. Not really interested in a better fix with v2 layout and all.
if ('1Y' === $preferredRange) {
$currentEnd = app('navigation')->endOfPeriod($currentEnd, $preferredRange);
}
Log::debug('Start of sub-loop');
while ($currentStart <= $currentEnd) {
Log::debug(sprintf('Current start: %s', $currentStart->toW3cString()));
$key = $currentStart->format($format);
$title = $currentStart->isoFormat($titleFormat);
// #8663 make sure the period exists in the data previously collected.
@@ -227,12 +250,20 @@ class ReportController extends Controller
$income['entries'][$title] = app('steam')->bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
$expense['entries'][$title] = app('steam')->bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
}
// #9477 if the period is not in the data, add it with zero values.
if (!array_key_exists($key, $currency)) {
$income['entries'][$title] = '0';
$expense['entries'][$title] = '0';
}
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
}
Log::debug('End of sub-loop');
$chartData[] = $income;
$chartData[] = $expense;
}
Log::debug('End of loop');
$data = $this->generator->multiSet($chartData);
$cache->store($data);

View File

@@ -115,6 +115,15 @@ class ShowController extends Controller
$array['attachments'][] = $item;
}
if (null !== $array['nr_of_repetitions']) {
$left = $array['nr_of_repetitions'] - $array['journal_count'];
$left = max(0, $left);
// limit each repetition to X occurrences:
foreach ($array['repetitions'] as $index => $repetition) {
$array['repetitions'][$index]['occurrences'] = array_slice($repetition['occurrences'], 0, $left);
}
}
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups', 'today'));

View File

@@ -40,24 +40,24 @@ class TriggerController extends Controller
{
public function trigger(Recurrence $recurrence, TriggerRecurrenceRequest $request): RedirectResponse
{
$all = $request->getAll();
$date = $all['date'];
$all = $request->getAll();
$date = $all['date'];
// grab the date from the last time the recurrence fired:
$backupDate = $recurrence->latest_date;
$backupDate = $recurrence->latest_date;
// fire the recurring cron job on the given date, then post-date the created transaction.
app('log')->info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s')));
/** @var CreateRecurringTransactions $job */
$job = app(CreateRecurringTransactions::class);
$job = app(CreateRecurringTransactions::class);
$job->setRecurrences(new Collection([$recurrence]));
$job->setDate($date);
$job->setForce(false);
$job->handle();
app('log')->debug('Done with recurrence.');
$groups = $job->getGroups();
$groups = $job->getGroups();
/** @var TransactionGroup $group */
foreach ($groups as $group) {
@@ -68,7 +68,8 @@ class TriggerController extends Controller
$journal->save();
}
}
$recurrence->latest_date = $backupDate;
$recurrence->latest_date = $backupDate;
$recurrence->latest_date_tz = $backupDate?->format('e');
$recurrence->save();
app('preferences')->mark();

View File

@@ -158,6 +158,11 @@ class Installer
// version compare thing.
$configVersion = (string)config('firefly.version');
$dbVersion = (string)app('fireflyconfig')->getFresh('ff3_version', '1.0')->data;
if (str_starts_with($configVersion, 'develop')) {
Log::debug('Skipping version check for develop version.');
return false;
}
if (1 === version_compare($configVersion, $dbVersion)) {
app('log')->warning(
sprintf(

View File

@@ -42,6 +42,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class CreateRecurringTransactions.
@@ -88,7 +89,7 @@ class CreateRecurringTransactions implements ShouldQueue
$this->recurrences = new Collection();
$this->groups = new Collection();
app('log')->debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d')));
Log::debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d')));
}
public function getGroups(): Collection
@@ -101,25 +102,25 @@ class CreateRecurringTransactions implements ShouldQueue
*/
public function handle(): void
{
app('log')->debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y')));
Log::debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y')));
// only use recurrences from database if there is no collection submitted.
if (0 !== count($this->recurrences)) {
app('log')->debug('Using predetermined set of recurrences.');
Log::debug('Using predetermined set of recurrences.');
}
if (0 === count($this->recurrences)) {
app('log')->debug('Grab all recurrences from the database.');
Log::debug('Grab all recurrences from the database.');
$this->recurrences = $this->repository->getAll();
}
$result = [];
$count = $this->recurrences->count();
$this->submitted = $count;
app('log')->debug(sprintf('Count of collection is %d', $count));
Log::debug(sprintf('Count of collection is %d', $count));
// filter recurrences:
$filtered = $this->filterRecurrences($this->recurrences);
app('log')->debug(sprintf('Left after filtering is %d', $filtered->count()));
Log::debug(sprintf('Left after filtering is %d', $filtered->count()));
/** @var Recurrence $recurrence */
foreach ($filtered as $recurrence) {
@@ -133,20 +134,20 @@ class CreateRecurringTransactions implements ShouldQueue
// clear cache for user
app('preferences')->setForUser($recurrence->user, 'lastActivity', microtime());
app('log')->debug(sprintf('Now at recurrence #%d of user #%d', $recurrence->id, $recurrence->user_id));
Log::debug(sprintf('Now at recurrence #%d of user #%d', $recurrence->id, $recurrence->user_id));
$createdReps = $this->handleRepetitions($recurrence);
app('log')->debug(sprintf('Done with recurrence #%d', $recurrence->id));
Log::debug(sprintf('Done with recurrence #%d', $recurrence->id));
$result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($createdReps);
++$this->executed;
}
app('log')->debug('Now running report thing.');
Log::debug('Now running report thing.');
// will now send email to users.
foreach ($result as $userId => $journals) {
event(new RequestedReportOnJournals($userId, $journals));
}
app('log')->debug('Done with handle()');
Log::debug('Done with handle()');
// clear cache:
app('preferences')->mark();
@@ -166,10 +167,10 @@ class CreateRecurringTransactions implements ShouldQueue
*/
private function validRecurrence(Recurrence $recurrence): bool
{
app('log')->debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id));
Log::debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id));
// is not active.
if (!$this->active($recurrence)) {
app('log')->info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id));
Log::info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id));
return false;
}
@@ -177,15 +178,15 @@ class CreateRecurringTransactions implements ShouldQueue
// has repeated X times.
$journalCount = $this->repository->getJournalCount($recurrence);
if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) {
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $journalCount));
Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $journalCount));
return false;
}
app('log')->debug(sprintf('Recurrence #%d has run %d times, max is %d times.', $recurrence->id, $journalCount, $recurrence->repetitions));
Log::debug(sprintf('Recurrence #%d has run %d times, max is %d times.', $recurrence->id, $journalCount, $recurrence->repetitions));
// is no longer running
if ($this->repeatUntilHasPassed($recurrence)) {
app('log')->info(
Log::info(
sprintf(
'Recurrence #%d was set to run until %s, and today\'s date is %s. Skipped.',
$recurrence->id,
@@ -199,7 +200,7 @@ class CreateRecurringTransactions implements ShouldQueue
// first_date is in the future
if ($this->hasNotStartedYet($recurrence)) {
app('log')->info(
Log::info(
sprintf(
'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.',
$recurrence->id,
@@ -213,11 +214,11 @@ class CreateRecurringTransactions implements ShouldQueue
// already fired today (with success):
if (false === $this->force && $this->hasFiredToday($recurrence)) {
app('log')->info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id));
Log::info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id));
return false;
}
app('log')->debug('Will be included.');
Log::debug('Will be included.');
return true;
}
@@ -245,10 +246,10 @@ class CreateRecurringTransactions implements ShouldQueue
private function hasNotStartedYet(Recurrence $recurrence): bool
{
$startDate = $this->getStartDate($recurrence);
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d H:i:s')));
app('log')->debug(sprintf('Ask date is %s', $this->date->format('Y-m-d H:i:s')));
Log::debug(sprintf('Start date is %s', $startDate->toW3cString()));
Log::debug(sprintf('Ask date is %s', $this->date->toW3cString()));
return $startDate->gte($this->date);
return $startDate->gt($this->date);
}
/**
@@ -285,7 +286,7 @@ class CreateRecurringTransactions implements ShouldQueue
/** @var RecurrenceRepetition $repetition */
foreach ($recurrence->recurrenceRepetitions as $repetition) {
app('log')->debug(
Log::debug(
sprintf(
'Now repeating %s with value "%s", skips every %d time(s)',
$repetition->repetition_type,
@@ -340,32 +341,32 @@ class CreateRecurringTransactions implements ShouldQueue
if ($date->ne($this->date)) {
return null;
}
app('log')->debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
// count created journals on THIS day.
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
if ($journalCount > 0 && false === $this->force) {
app('log')->info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
Log::info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
return null;
}
if ($this->repository->createdPreviously($recurrence, $date) && false === $this->force) {
app('log')->info('There is a transaction already made for this date, so will not be created now');
Log::info('There is a transaction already made for this date, so will not be created now');
return null;
}
if ($journalCount > 0 && true === $this->force) {
app('log')->warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d')));
Log::warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d')));
}
// create transaction array and send to factory.
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
// #8844, if there is one recurrence transaction, use the first title as the title.
// #9305, if there is one recurrence transaction, group title must be NULL.
$groupTitle = null;
$groupTitle = null;
// #8844, if there are more, use the recurrence transaction itself.
if ($count > 1) {
@@ -373,28 +374,29 @@ class CreateRecurringTransactions implements ShouldQueue
}
if (0 === $count) {
app('log')->error('No transactions to be created in this recurrence. Cannot continue.');
Log::error('No transactions to be created in this recurrence. Cannot continue.');
return null;
}
$array = [
$array = [
'user' => $recurrence->user_id,
'group_title' => $groupTitle,
'transactions' => $this->getTransactionData($recurrence, $repetition, $date),
];
/** @var TransactionGroup $group */
$group = $this->groupRepository->store($array);
$group = $this->groupRepository->store($array);
++$this->created;
app('log')->info(sprintf('Created new transaction group #%d', $group->id));
Log::info(sprintf('Created new transaction group #%d', $group->id));
// trigger event:
event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$this->groups->push($group);
// update recurring thing:
$recurrence->latest_date = $date;
$recurrence->latest_date = $date;
$recurrence->latest_date_tz = $date?->format('e');
$recurrence->save();
return $group;
@@ -459,6 +461,7 @@ class CreateRecurringTransactions implements ShouldQueue
{
$newDate = clone $date;
$newDate->startOfDay();
Log::debug(sprintf('Overruled date to "%s', $newDate->format('Y-m-d H:i:s')));
$this->date = $newDate;
}

View File

@@ -51,12 +51,13 @@ class Account extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'user_id' => 'integer',
'deleted_at' => 'datetime',
'active' => 'boolean',
'encrypted' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'user_id' => 'integer',
'deleted_at' => 'datetime',
'active' => 'boolean',
'encrypted' => 'boolean',
'virtual_balance' => 'string',
];
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban'];

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -14,7 +15,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AccountBalance extends Model
{
use HasFactory;
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance'];
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance', 'date', 'date_tz'];
protected function casts(): array
{
return [
'date' => SeparateTimezoneCaster::class,
'balance' => 'string',
];
}
public function account(): BelongsTo
{

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -34,19 +35,46 @@ class AccountType extends Model
{
use ReturnsIntegerIdTrait;
/** @deprecated */
public const string ASSET = 'Asset account';
/** @deprecated */
public const string BENEFICIARY = 'Beneficiary account';
/** @deprecated */
public const string CASH = 'Cash account';
/** @deprecated */
public const string CREDITCARD = 'Credit card';
/** @deprecated */
public const string DEBT = 'Debt';
/** @deprecated */
public const string DEFAULT = 'Default account';
/** @deprecated */
public const string EXPENSE = 'Expense account';
/** @deprecated */
public const string IMPORT = 'Import account';
/** @deprecated */
public const string INITIAL_BALANCE = 'Initial balance account';
/** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit account';
/** @deprecated */
public const string LOAN = 'Loan';
/** @deprecated */
public const string MORTGAGE = 'Mortgage';
/** @deprecated */
public const string RECONCILIATION = 'Reconciliation account';
/** @deprecated */
public const string REVENUE = 'Revenue account';
protected $casts
@@ -61,4 +89,11 @@ class AccountType extends Model
{
return $this->hasMany(Account::class);
}
protected function casts(): array
{
return [
// 'type' => AccountTypeEnum::class,
];
}
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\AutoBudgetType;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -38,11 +39,20 @@ class AutoBudget extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
/** @deprecated */
public const int AUTO_BUDGET_ADJUSTED = 3;
/** @deprecated */
public const int AUTO_BUDGET_RESET = 1;
/** @deprecated */
public const int AUTO_BUDGET_ROLLOVER = 2;
protected $fillable = ['budget_id', 'amount', 'period'];
protected $casts = [
'amount' => 'string',
];
public function budget(): BelongsTo
{
return $this->belongsTo(Budget::class);
@@ -53,6 +63,13 @@ class AutoBudget extends Model
return $this->belongsTo(TransactionCurrency::class);
}
protected function casts(): array
{
return [
// 'auto_budget_type' => AutoBudgetType::class,
];
}
protected function amount(): Attribute
{
return Attribute::make(

View File

@@ -49,9 +49,10 @@ class AvailableBudget extends Model
'start_date' => 'date',
'end_date' => 'date',
'transaction_currency_id' => 'int',
'amount' => 'string',
];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date'];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -49,14 +50,16 @@ class Bill extends Model
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'date' => 'date',
'end_date' => 'date',
'extension_date' => 'date',
'date' => SeparateTimezoneCaster::class,
'end_date' => SeparateTimezoneCaster::class,
'extension_date' => SeparateTimezoneCaster::class,
'skip' => 'int',
'automatch' => 'boolean',
'active' => 'boolean',
'name_encrypted' => 'boolean',
'match_encrypted' => 'boolean',
'amount_min' => 'string',
'amount_max' => 'string',
];
protected $fillable
@@ -68,6 +71,7 @@ class Bill extends Model
'user_group_id',
'amount_max',
'date',
'date_tz',
'repeat_freq',
'skip',
'automatch',
@@ -75,6 +79,8 @@ class Bill extends Model
'transaction_currency_id',
'end_date',
'extension_date',
'end_date_tz',
'extension_date_tz',
];
protected $hidden = ['amount_min_encrypted', 'amount_max_encrypted', 'name_encrypted', 'match_encrypted'];

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Events\Model\BudgetLimit\Created;
use FireflyIII\Events\Model\BudgetLimit\Deleted;
use FireflyIII\Events\Model\BudgetLimit\Updated;
@@ -43,9 +44,10 @@ class BudgetLimit extends Model
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'start_date' => 'date',
'end_date' => 'date',
'start_date' => SeparateTimezoneCaster::class,
'end_date' => SeparateTimezoneCaster::class,
'auto_budget' => 'boolean',
'amount' => 'string',
];
protected $dispatchesEvents
= [
@@ -54,7 +56,7 @@ class BudgetLimit extends Model
'deleted' => Deleted::class,
];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id'];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -47,9 +48,11 @@ class CurrencyExchangeRate extends Model
'user_id' => 'int',
'from_currency_id' => 'int',
'to_currency_id' => 'int',
'date' => 'datetime',
'date' => SeparateTimezoneCaster::class,
'rate' => 'string',
'user_rate' => 'string',
];
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'rate'];
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'date_tz', 'rate'];
public function fromCurrency(): BelongsTo
{

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -41,10 +42,10 @@ class InvitedUser extends Model
protected $casts
= [
'expires' => 'datetime',
'expires' => SeparateTimezoneCaster::class,
'redeemed' => 'boolean',
];
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'redeemed'];
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'expires_tz', 'redeemed'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -43,17 +43,18 @@ class PiggyBank extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'startdate' => 'date',
'targetdate' => 'date',
'order' => 'int',
'active' => 'boolean',
'encrypted' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'startdate' => 'date',
'targetdate' => 'date',
'order' => 'int',
'active' => 'boolean',
'encrypted' => 'boolean',
'targetamount' => 'string',
];
protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'targetdate', 'active'];
protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'startdate_tz', 'targetdate', 'targetdate_tz', 'active'];
protected $hidden = ['targetamount_encrypted', 'encrypted'];

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -39,10 +40,11 @@ class PiggyBankEvent extends Model
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'date' => 'date',
'date' => SeparateTimezoneCaster::class,
'amount' => 'string',
];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'amount'];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'date_tz', 'amount'];
protected $hidden = ['amount_encrypted'];

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -39,13 +40,14 @@ class PiggyBankRepetition extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'startdate' => 'date',
'targetdate' => 'date',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'startdate' => SeparateTimezoneCaster::class,
'targetdate' => SeparateTimezoneCaster::class,
'virtual_balance' => 'string',
];
protected $fillable = ['piggy_bank_id', 'startdate', 'targetdate', 'currentamount'];
protected $fillable = ['piggy_bank_id', 'startdate', 'startdate_tz', 'targetdate', 'targetdate_tz', 'currentamount'];
public function piggyBank(): BelongsTo
{

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -51,16 +52,16 @@ class Recurrence extends Model
'title' => 'string',
'id' => 'int',
'description' => 'string',
'first_date' => 'date',
'repeat_until' => 'date',
'latest_date' => 'date',
'first_date' => SeparateTimezoneCaster::class,
'repeat_until' => SeparateTimezoneCaster::class,
'latest_date' => SeparateTimezoneCaster::class,
'repetitions' => 'int',
'active' => 'bool',
'apply_rules' => 'bool',
];
protected $fillable
= ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active'];
= ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'first_date_tz', 'repeat_until', 'repeat_until_tz', 'latest_date', 'latest_date_tz', 'repetitions', 'apply_rules', 'active'];
/** @var string The table to store the data in */
protected $table = 'recurrences';

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\RecurrenceRepetitionWeekend;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -37,9 +38,16 @@ class RecurrenceRepetition extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
/** @deprecated */
public const int WEEKEND_DO_NOTHING = 1;
/** @deprecated */
public const int WEEKEND_SKIP_CREATION = 2;
/** @deprecated */
public const int WEEKEND_TO_FRIDAY = 3;
/** @deprecated */
public const int WEEKEND_TO_MONDAY = 4;
protected $casts
@@ -58,6 +66,13 @@ class RecurrenceRepetition extends Model
/** @var string The table to store the data in */
protected $table = 'recurrences_repetitions';
protected function casts(): array
{
return [
// 'weekend' => RecurrenceRepetitionWeekend::class,
];
}
public function recurrence(): BelongsTo
{
return $this->belongsTo(Recurrence::class);

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -47,13 +48,13 @@ class Tag extends Model
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'date' => 'date',
'date' => SeparateTimezoneCaster::class,
'zoomLevel' => 'int',
'latitude' => 'float',
'longitude' => 'float',
];
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'description', 'tagMode'];
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tagMode'];
protected $hidden = ['zoomLevel', 'latitude', 'longitude'];

View File

@@ -54,7 +54,11 @@ class Transaction extends Model
'bill_name_encrypted' => 'boolean',
'reconciled' => 'boolean',
'balance_dirty' => 'boolean',
'balance_before' => 'string',
'balance_after' => 'string',
'date' => 'datetime',
'amount' => 'string',
'foreign_amount' => 'string',
];
protected $fillable

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -55,7 +56,7 @@ class TransactionJournal extends Model
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'date' => 'datetime',
'date' => SeparateTimezoneCaster::class,
'interest_date' => 'date',
'book_date' => 'date',
'process_date' => 'date',
@@ -77,6 +78,7 @@ class TransactionJournal extends Model
'completed',
'order',
'date',
'date_tz',
];
protected $hidden = ['encrypted'];
@@ -89,7 +91,7 @@ class TransactionJournal extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$journalId = (int)$value;
$journalId = (int) $value;
/** @var User $user */
$user = auth()->user();
@@ -243,14 +245,14 @@ class TransactionJournal extends Model
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
get: static fn ($value) => (int) $value,
);
}
protected function transactionTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
get: static fn ($value) => (int) $value,
);
}
}

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -37,12 +38,25 @@ class TransactionType extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
/** @deprecated */
public const string DEPOSIT = 'Deposit';
/** @deprecated */
public const string INVALID = 'Invalid';
/** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit';
/** @deprecated */
public const string OPENING_BALANCE = 'Opening balance';
/** @deprecated */
public const string RECONCILIATION = 'Reconciliation';
/** @deprecated */
public const string TRANSFER = 'Transfer';
/** @deprecated */
public const string WITHDRAWAL = 'Withdrawal';
protected $casts
@@ -53,6 +67,13 @@ class TransactionType extends Model
];
protected $fillable = ['type'];
protected function casts(): array
{
return [
// 'type' => TransactionTypeEnum::class,
];
}
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
*

View File

@@ -53,6 +53,15 @@ class Webhook extends Model
];
protected $fillable = ['active', 'trigger', 'response', 'delivery', 'user_id', 'user_group_id', 'url', 'title', 'secret'];
protected function casts(): array
{
return [
// 'delivery' => WebhookDelivery::class,
// 'response' => WebhookResponse::class,
// 'trigger' => WebhookTrigger::class,
];
}
public static function getDeliveries(): array
{
$array = [];
@@ -130,7 +139,7 @@ class Webhook extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$webhookId = (int)$value;
$webhookId = (int) $value;
/** @var User $user */
$user = auth()->user();

View File

@@ -437,15 +437,17 @@ class BillRepository implements BillRepositoryInterface
}
// find the most recent date for this bill NOT in the future. Cache this date:
$start = clone $bill->date;
$start->startOfDay();
app('log')->debug('nextExpectedMatch: Start is '.$start->format('Y-m-d'));
while ($start < $date) {
app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d H:i:s'), $date->format('Y-m-d H:i:s')));
$start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
app('log')->debug('Start is now '.$start->format('Y-m-d'));
app('log')->debug('Start is now '.$start->format('Y-m-d H:i:s'));
}
$end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
$end->endOfDay();
// see if the bill was paid in this period.
$journalCount = $bill->transactionJournals()->before($end)->after($start)->count();

View File

@@ -198,11 +198,13 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
->where('end_date', $end->format('Y-m-d'))->first()
;
if (null === $availableBudget) {
$availableBudget = new AvailableBudget();
$availableBudget = new AvailableBudget();
$availableBudget->user()->associate($this->user);
$availableBudget->transactionCurrency()->associate($currency);
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->end_date_tz = $end->format('e');
}
$availableBudget->amount = $amount;
$availableBudget->save();
@@ -235,7 +237,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
'transaction_currency_id' => $data['currency_id'],
'amount' => $data['amount'],
'start_date' => $start->format('Y-m-d'),
'start_date_tz' => $start->format('e'),
'end_date' => $end->format('Y-m-d'),
'end_date_tz' => $end->format('e'),
]
);
}
@@ -255,8 +259,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
if (array_key_exists('start', $data)) {
$start = $data['start'];
if ($start instanceof Carbon) {
$start = $data['start']->startOfDay();
$availableBudget->start_date = $start->format('Y-m-d');
$start = $data['start']->startOfDay();
$availableBudget->start_date = $start->format('Y-m-d');
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->save();
}
}
@@ -264,8 +269,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
if (array_key_exists('end', $data)) {
$end = $data['end'];
if ($end instanceof Carbon) {
$end = $data['end']->endOfDay();
$availableBudget->end_date = $end->format('Y-m-d');
$end = $data['end']->endOfDay();
$availableBudget->end_date = $end->format('Y-m-d');
$availableBudget->end_date_tz = $end->format('e');
$availableBudget->save();
}
}

View File

@@ -277,7 +277,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$currency->save();
// find the budget:
$budget = $this->user->budgets()->find((int)$data['budget_id']);
$budget = $this->user->budgets()->find((int) $data['budget_id']);
if (null === $budget) {
throw new FireflyException('200004: Budget does not exist.');
}
@@ -323,8 +323,15 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{
$budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
$budgetLimit->start_date = array_key_exists('start', $data) ? $data['start']->format('Y-m-d 00:00:00') : $budgetLimit->start_date;
$budgetLimit->end_date = array_key_exists('end', $data) ? $data['end']->format('Y-m-d 23:59:59') : $budgetLimit->end_date;
if (array_key_exists('start', $data)) {
$budgetLimit->start_date = $data['start']->startOfDay();
$budgetLimit->start_date_tz = $data['start']->format('e');
}
if (array_key_exists('end', $data)) {
$budgetLimit->end_date = $data['end']->endOfDay();
$budgetLimit->end_date_tz = $data['end']->format('e');
}
// if no currency has been provided, use the user's default currency:
$currency = null;
@@ -351,7 +358,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
{
// count the limits:
$limits = $budget->budgetlimits()
$limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*')
@@ -360,7 +367,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// there might be a budget limit for these dates:
/** @var null|BudgetLimit $limit */
$limit = $budget->budgetlimits()
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*'])
@@ -395,11 +402,13 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
}
app('log')->debug('No existing budget limit, create a new one');
// or create one and return it.
$limit = new BudgetLimit();
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $start->startOfDay();
$limit->end_date = $end->startOfDay();
$limit->amount = $amount;
$limit->start_date = $start->startOfDay();
$limit->start_date_tz = $start->format('e');
$limit->end_date = $end->startOfDay();
$limit->end_date_tz = $end->format('e');
$limit->amount = $amount;
$limit->save();
app('log')->debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $amount));

View File

@@ -89,11 +89,12 @@ class CurrencyRepository implements CurrencyRepositoryInterface
{
return CurrencyExchangeRate::create(
[
'user_id' => $this->user->id,
'from_currency_id' => $fromCurrency->id,
'to_currency_id' => $toCurrency->id,
'date' => $date,
'rate' => $rate,
'user_id' => $this->user->id,
'from_currency_id' => $fromCurrency->id,
'to_currency_id' => $toCurrency->id,
'date' => $date,
'date_tz' => $date->format('e'),
'rate' => $rate,
]
);
}

View File

@@ -178,12 +178,12 @@ trait ModifiesPiggyBanks
*/
public function store(array $data): PiggyBank
{
$order = $this->getMaxOrder() + 1;
$order = $this->getMaxOrder() + 1;
if (array_key_exists('order', $data)) {
$order = $data['order'];
}
$data['order'] = 31337; // very high when creating.
$piggyData = $data;
$data['order'] = 31337; // very high when creating.
$piggyData = $data;
// unset fields just in case.
unset($piggyData['object_group_title'], $piggyData['object_group_id'], $piggyData['notes'], $piggyData['current_amount']);
@@ -192,6 +192,9 @@ trait ModifiesPiggyBanks
$piggyData['targetamount'] = '0';
}
$piggyData['startdate_tz'] = $piggyData['startdate']?->format('e');
$piggyData['targetdate_tz'] = $piggyData['targetdate']?->format('e');
try {
/** @var PiggyBank $piggyBank */
$piggyBank = PiggyBank::create($piggyData);
@@ -208,13 +211,13 @@ trait ModifiesPiggyBanks
$this->updateNote($piggyBank, $data['notes']);
// repetition is auto created.
$repetition = $this->getRepetition($piggyBank);
$repetition = $this->getRepetition($piggyBank);
if (null !== $repetition && array_key_exists('current_amount', $data) && '' !== $data['current_amount']) {
$repetition->currentamount = $data['current_amount'];
$repetition->save();
}
$objectGroupTitle = $data['object_group_title'] ?? '';
$objectGroupTitle = $data['object_group_title'] ?? '';
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
@@ -223,7 +226,7 @@ trait ModifiesPiggyBanks
}
}
// try also with ID
$objectGroupId = (int)($data['object_group_id'] ?? 0);
$objectGroupId = (int)($data['object_group_id'] ?? 0);
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {
@@ -373,10 +376,12 @@ trait ModifiesPiggyBanks
$piggyBank->targetamount = '0';
}
if (array_key_exists('targetdate', $data) && '' !== $data['targetdate']) {
$piggyBank->targetdate = $data['targetdate'];
$piggyBank->targetdate = $data['targetdate'];
$piggyBank->targetdate_tz = $data['targetdate']?->format('e');
}
if (array_key_exists('startdate', $data)) {
$piggyBank->startdate = $data['startdate'];
$piggyBank->startdate = $data['startdate'];
$piggyBank->startdate_tz = $data['targetdate']?->format('e');
}
$piggyBank->save();

View File

@@ -276,6 +276,7 @@ class UserRepository implements UserRepositoryInterface
$invitee->email = $email;
$invitee->redeemed = false;
$invitee->expires = $now;
$invitee->expires_tz = $now->format('e');
$invitee->save();
return $invitee;

View File

@@ -105,10 +105,12 @@ class IsUniqueAccount implements ValidationRule, DataAwareRule
/**
* TODO duplicate from old validation class.
*/
private function validateAccountAnonymously(): void
private function validateAccountAnonymously(): bool
{
if (!array_key_exists('user_id', $this->data)) {
$this->fail('No user ID provided.');
return false;
}
/** @var User $user */

View File

@@ -145,7 +145,8 @@ class BillUpdateService
$bill->amount_max = $data['amount_max'];
}
if (array_key_exists('date', $data) && '' !== (string)$data['date']) {
$bill->date = $data['date'];
$bill->date = $data['date'];
$bill->date_tz = $data['date']->format('e');
}
if (array_key_exists('repeat_freq', $data) && '' !== (string)$data['repeat_freq']) {
$bill->repeat_freq = $data['repeat_freq'];
@@ -157,10 +158,12 @@ class BillUpdateService
$bill->active = $data['active'];
}
if (array_key_exists('end_date', $data)) {
$bill->end_date = $data['end_date'];
$bill->end_date = $data['end_date'];
$bill->end_date_tz = $data['end_date']?->format('e');
}
if (array_key_exists('extension_date', $data)) {
$bill->extension_date = $data['extension_date'];
$bill->extension_date = $data['extension_date'];
$bill->extension_date_tz = $data['extension_date']?->format('e');
}
$bill->match = 'EMPTY';

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Services\Internal\Update;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TagFactory;
@@ -43,6 +44,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\Validation\AccountValidator;
use Illuminate\Support\Facades\Log;
@@ -481,15 +483,19 @@ class JournalUpdateService
$value = $this->data[$fieldName];
if ('date' === $fieldName) {
if ($value instanceof Carbon) {
// update timezone.
$value->setTimezone(config('app.timezone'));
}
if (!$value instanceof Carbon) {
$value = new Carbon($value);
}
$value->setTimezone(config('app.timezone'));
// 2024-11-22, overrule timezone with UTC and store it as UTC.
if (FireflyConfig::get('utc', false)->data) {
$value->setTimezone('UTC');
}
// do some parsing.
app('log')->debug(sprintf('Create date value from string "%s".', $value));
$this->transactionJournal->date_tz = $value->format('e');
}
event(
new TriggeredAuditLog(
@@ -610,6 +616,13 @@ class JournalUpdateService
'data' => $value,
];
$factory->updateOrCreate($set);
// also set date with timezone.
$set = [
'journal' => $this->transactionJournal,
'name' => sprintf('%s_tz', $field),
'data' => $value?->format('e'),
];
$factory->updateOrCreate($set);
}
}
}
@@ -708,13 +721,15 @@ class JournalUpdateService
// if the transaction is a TRANSFER, and the foreign amount and currency are set (like they seem to be)
// the correct fields to update in the destination transaction are NOT the foreign amount and currency
// but rather the normal amount and currency. This is new behavior.
if (TransactionType::TRANSFER === $this->transactionJournal->transactionType->type) {
$isTransfer = TransactionTypeEnum::TRANSFER->value === $this->transactionJournal->transactionType->type;
if ($isTransfer) {
Log::debug('Switch amounts, store in amount and not foreign_amount');
$dest->transaction_currency_id = $foreignCurrency->id;
$dest->amount = app('steam')->positive($foreignAmount);
$dest->foreign_amount = app('steam')->positive($source->amount);
$dest->foreign_currency_id = $source->transaction_currency_id;
}
if (TransactionType::TRANSFER !== $this->transactionJournal->transactionType->type) {
if (!$isTransfer) {
$dest->foreign_currency_id = $foreignCurrency->id;
$dest->foreign_amount = app('steam')->positive($foreignAmount);
}

View File

@@ -64,11 +64,13 @@ class RecurrenceUpdateService
$recurrence->description = $info['description'];
}
if (array_key_exists('first_date', $info)) {
$recurrence->first_date = $info['first_date'];
$recurrence->first_date = $info['first_date'];
$recurrence->first_date_tz = $info['first_date']?->format('e');
}
if (array_key_exists('repeat_until', $info)) {
$recurrence->repeat_until = $info['repeat_until'];
$recurrence->repetitions = 0;
$recurrence->repeat_until = $info['repeat_until'];
$recurrence->repeat_until_tz = $info['repeat_until']?->format('e');
$recurrence->repetitions = 0;
}
if (array_key_exists('nr_of_repetitions', $info)) {
if (0 !== (int)$info['nr_of_repetitions']) {

View File

@@ -39,7 +39,6 @@ class EitherConfigKey
// firefly iii settings
'firefly.version',
'firefly.api_version',
'firefly.default_location',
'firefly.account_to_transaction',
'firefly.allowed_opposing_types',

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Api;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Models\AccountType;
/**
@@ -32,7 +33,7 @@ trait AccountFilter
{
protected array $types = [
'all' => [
AccountType::DEFAULT,
AccountTypeEnum::DEFAULT->value,
AccountType::CASH,
AccountType::ASSET,
AccountType::EXPENSE,

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Bill;
@@ -84,9 +85,9 @@ trait ModelInformation
/** @var AccountType $mortgage */
$mortgage = $repository->getAccountTypeByType(AccountType::MORTGAGE);
$liabilityTypes = [
$debt->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::DEBT)),
$loan->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::LOAN)),
$mortgage->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::MORTGAGE)),
$debt->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::DEBT->value)),
$loan->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::LOAN->value)),
$mortgage->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::MORTGAGE->value)),
];
asort($liabilityTypes);

View File

@@ -86,7 +86,7 @@ trait RenderPartialViews
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$budgets = $repository->getBudgets();
$budgets = $repository->getActiveBudgets();
try {
$result = view('reports.options.budget', compact('budgets'))->render();

View File

@@ -95,7 +95,7 @@ class AccountBalanceCalculator
$query->where('transaction_journals.date', '<', $notBefore);
$first = $query->first(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount', 'transactions.balance_after']);
$balance = $first->balance_after ?? '0';
$balance = (string) ($first->balance_after ?? '0');
Log::debug(sprintf('getLatestBalance: found balance: %s in transaction #%d', $balance, $first->id ?? 0));
return $balance;
@@ -201,148 +201,6 @@ class AccountBalanceCalculator
return $entry;
}
// private function recalculateLatest(?Account $account): void
// {
// $query = Transaction::groupBy(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
//
// if (null !== $account) {
// $query->where('transactions.account_id', $account->id);
// }
// $result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
//
// // reset account balances:
// $this->resetAccountBalancesByAccount('balance', $account);
//
// /** @var \stdClass $row */
// foreach ($result as $row) {
// $account = (int) $row->account_id;
// $transactionCurrency = (int) $row->transaction_currency_id;
// $foreignCurrency = (int) $row->foreign_currency_id;
// $sumAmount = (string) $row->sum_amount;
// $sumForeignAmount = (string) $row->sum_foreign_amount;
// $sumAmount = '' === $sumAmount ? '0' : $sumAmount;
// $sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
//
// // at this point SQLite may return scientific notation because why not. Terrible.
// $sumAmount = app('steam')->floatalize($sumAmount);
// $sumForeignAmount = app('steam')->floatalize($sumForeignAmount);
//
// // first create for normal currency:
// $entry = $this->getAccountBalanceByAccount($account, $transactionCurrency);
//
// try {
// $entry->balance = bcadd((string) $entry->balance, $sumAmount);
// } catch (\ValueError $e) {
// $message = sprintf('[a] Could not add "%s" to "%s": %s', $entry->balance, $sumAmount, $e->getMessage());
// Log::error($message);
//
// throw new FireflyException($message, 0, $e);
// }
// $entry->save();
//
// // then do foreign amount, if present:
// if ($foreignCurrency > 0) {
// $entry = $this->getAccountBalanceByAccount($account, $foreignCurrency);
//
// try {
// $entry->balance = bcadd((string) $entry->balance, $sumForeignAmount);
// } catch (\ValueError $e) {
// $message = sprintf('[b] Could not add "%s" to "%s": %s', $entry->balance, $sumForeignAmount, $e->getMessage());
// Log::error($message);
//
// throw new FireflyException($message, 0, $e);
// }
// $entry->save();
// }
// }
// Log::debug(sprintf('Recalculated %d account balance(s)', $result->count()));
// }
// private function resetAccountBalancesByAccount(string $title, ?Account $account): void
// {
// if (null === $account) {
// $count = AccountBalance::whereNotNull('updated_at')->where('title', $title)->update(['balance' => '0']);
// Log::debug(sprintf('Set %d account balance(s) to zero.', $count));
//
// return;
// }
// $count = AccountBalance::where('account_id', $account->id)->where('title', $title)->update(['balance' => '0']);
// Log::debug(sprintf('Set %d account balance(s) of account #%d to zero.', $count, $account->id));
// }
// /**
// * Als je alles opnieuw doet, verzamel je alle transactions en het bedrag en zet je dat neer als "balance after
// * journal". Dat betekent, netjes op volgorde van datum en doorrekenen.
// *
// * Zodra je een transaction journal verplaatst (datum) moet je dat journal en alle latere journals opnieuw doen.
// * Maar dan moet je van de account wel een beginnetje hebben, namelijk de balance tot en met dat moment.
// *
// * 1. Dus dan search je eerst naar die SUM, som alle transactions eerder dan (niet inclusief) de journal.
// * 2. En vanaf daar pak je alle journals op of na de journal (dus ook de journal zelf) en begin je door te tellen.
// * 3. Elke voorbij gaande journal entry "balance_after_journal" geef je een update of voeg je toe.
// */
// private function recalculateJournals(?Account $account, ?TransactionJournal $transactionJournal): void
// {
// $query = Transaction::groupBy(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
// $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
// $query->orderBy('transaction_journals.date', 'asc');
// $amounts = [];
// if (null !== $account) {
// $query->where('transactions.account_id', $account->id);
// }
// if (null !== $account && null !== $transactionJournal) {
// $query->where('transaction_journals.date', '>=', $transactionJournal->date);
// $amounts = $this->getStartAmounts($account, $transactionJournal);
// }
// $result = $query->get(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
//
// /** @var \stdClass $row */
// foreach ($result as $row) {
// $account = (int) $row->account_id;
// $transactionCurrency = (int) $row->transaction_currency_id;
// $foreignCurrency = (int) $row->foreign_currency_id;
// $sumAmount = (string) $row->sum_amount;
// $sumForeignAmount = (string) $row->sum_foreign_amount;
// $journalId = (int) $row->id;
//
// // check for empty strings
// $sumAmount = '' === $sumAmount ? '0' : $sumAmount;
// $sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
//
// // new amounts:
// $amounts[$account][$transactionCurrency] = bcadd($amounts[$account][$transactionCurrency] ?? '0', $sumAmount);
// $amounts[$account][$foreignCurrency] = bcadd($amounts[$account][$foreignCurrency] ?? '0', $sumForeignAmount);
//
// // first create for normal currency:
// $entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $transactionCurrency);
// $entry->balance = $amounts[$account][$transactionCurrency];
// $entry->save();
//
// // then do foreign amount, if present:
// if ($foreignCurrency > 0) {
// $entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $foreignCurrency);
// $entry->balance = $amounts[$account][$foreignCurrency];
// $entry->save();
// }
// }
//
// // select transactions.account_id, transaction_journals.id, transactions.transaction_currency_id, transactions.foreign_currency_id, sum(transactions.amount), sum(transactions.foreign_amount)
// //
// // from transactions
// //
// // left join transaction_journals ON transaction_journals.id = transactions.transaction_journal_id
// //
// // group by account_id, transaction_journals.id, transaction_currency_id, foreign_currency_id
// // order by transaction_journals.date desc
// }
// private function getStartAmounts(Account $account, TransactionJournal $journal): array
// {
// exit('here we are 1');
//
// return [];
// }
private function storeAccountBalances(array $balances): void
{
/**
@@ -372,9 +230,18 @@ class AccountBalanceCalculator
}
/** @var AccountBalance $object */
$object = $account->accountBalances()->firstOrCreate(['title' => 'running_balance', 'balance' => '0', 'transaction_currency_id' => $currencyId, 'date' => $balance[1]]);
$object = $account->accountBalances()->firstOrCreate(
[
'title' => 'running_balance',
'balance' => '0',
'transaction_currency_id' => $currencyId,
'date' => $balance[1],
'date_tz' => $balance[1]?->format('e'),
]
);
$object->balance = $balance[0];
$object->date = $balance[1];
$object->date_tz = $balance[1]?->format('e');
$object->save();
}
}

View File

@@ -65,7 +65,7 @@ class UserGroupTransformer extends AbstractTransformer
/** @var GroupMembership $groupMembership */
foreach ($groupMemberships as $groupMembership) {
$this->memberships[$userGroupId][] = [
'user_id' => (string)$groupMembership->user_id,
'user_id' => (string) $groupMembership->user_id,
'user_email' => $groupMembership->user->email,
'role' => $groupMembership->userRole->title,
'you' => $groupMembership->user_id === $user->id,
@@ -73,6 +73,7 @@ class UserGroupTransformer extends AbstractTransformer
}
}
}
$this->mergeMemberships();
}
return $objects;
@@ -90,8 +91,28 @@ class UserGroupTransformer extends AbstractTransformer
'in_use' => $this->inUse[$userGroup->id] ?? false,
'title' => $userGroup->title,
'can_see_members' => $this->membershipsVisible[$userGroup->id] ?? false,
'members' => $this->memberships[$userGroup->id] ?? [],
'members' => array_values($this->memberships[$userGroup->id] ?? []),
];
// if the user has a specific role in this group, then collect the memberships.
}
private function mergeMemberships(): void
{
$new = [];
foreach ($this->memberships as $groupId => $members) {
$new[$groupId] ??= [];
foreach ($members as $member) {
$mail = $member['user_email'];
$new[$groupId][$mail] ??= [
'user_id' => $member['user_id'],
'user_email' => $member['user_email'],
'you' => $member['you'],
'roles' => [],
];
$new[$groupId][$mail]['roles'][] = $member['role'];
}
}
$this->memberships = $new;
}
}

View File

@@ -3,6 +3,61 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 6.1.24 - 2024-11-24
### Fixed
- [Issue 9491](https://github.com/firefly-iii/firefly-iii/issues/9491) (Transactions created at midnight on the first day of a month are not listed correctly) reported by @Neroxeles
## 6.1.23 - 2024-11-23
### Added
- Expand (future) timezone support.
- [Issue 9451](https://github.com/firefly-iii/firefly-iii/issues/9451) (Trigger "Has at least this many attachments" is misnamed?) reported by @Kvan7
- [Issue 9458](https://github.com/firefly-iii/firefly-iii/issues/9458) (The add transaction failed) reported by @wqwwffsytxwp
- [Issue 9466](https://github.com/firefly-iii/firefly-iii/issues/9466) (First instance of recurring transaction not firing automatically or manually) reported by @makobot-sh
- [PR 9468](https://github.com/firefly-iii/firefly-iii/pull/9468) (Transaction Model: explicitly cast decimal to string) reported by @yparitcher
- [Issue 9477](https://github.com/firefly-iii/firefly-iii/issues/9477) (Default Financial report: Income vs Expense has missing months when multiple currencies are used) reported by @thomase1234
- [PR 9488](https://github.com/firefly-iii/firefly-iii/pull/9488) (fix: set dest foreign_amount and foreign_currency_id for foreign transfers) reported by @antoniomrfranco
- [PR 9483](https://github.com/firefly-iii/firefly-iii/pull/9483) (fix: include foreign_amount in transaction sum calculation) reported by @antoniomrfranco
- Broken links in readme.
## 6.1.22 - 2024-11-07
### Added
- [Discussion 8092](https://github.com/orgs/firefly-iii/discussions/8092) (Fresh Install - Register -> 403 Error - Forbidden) started by @pheonix-devapps
- [Issue 9183](https://github.com/firefly-iii/firefly-iii/issues/9183) (2FA security improvements) reported by @JC5
- Firefly III stores timezone data in a separate field, preparing for a switch to UTC (in the database).
### Fixed
- [Issue 9106](https://github.com/firefly-iii/firefly-iii/issues/9106) (Inactive accounts are inaccessible when no active accounts of that type exist) reported by @codemicro
- [Issue 9147](https://github.com/firefly-iii/firefly-iii/issues/9147) (Store/Update Bill API end_date and extension_date cant be null in request) reported by @jkano
- [Issue 9175](https://github.com/firefly-iii/firefly-iii/issues/9175) ("Attempt to read property "type" on null" when mass editing transactions) reported by @Still34
- [Issue 9225](https://github.com/firefly-iii/firefly-iii/issues/9225) (Liability amount due calculated incorrectly on liabilities list when the liability is settled with a transfer to another liability) reported by @uumas
- [Discussion 9234](https://github.com/orgs/firefly-iii/discussions/9234) (Unsupported cipher or incorrect key length ( first run )) started by @spectroman
- [Issue 9236](https://github.com/firefly-iii/firefly-iii/issues/9236) (Autocomplete not working for rrules having the bill as a trigger) reported by @pvieira84
- [Issue 9282](https://github.com/firefly-iii/firefly-iii/issues/9282) (Default report - no transactions for no budget) reported by @rymrg
- [Issue 9294](https://github.com/firefly-iii/firefly-iii/issues/9294) (Repetition counts ignored for recurring transactions) reported by @Syncena
- [Issue 9303](https://github.com/firefly-iii/firefly-iii/issues/9303) (Rules > Rule > Action) reported by @EricVanCaenenberghe
- [Issue 9305](https://github.com/firefly-iii/firefly-iii/issues/9305) (Recurring transactions get group title on overview page) reported by @zeitwidrig
- [Discussion 9324](https://github.com/orgs/firefly-iii/discussions/9324) (Consistent behavior accross DB Engines) started by @stackcoder
- [Issue 9360](https://github.com/firefly-iii/firefly-iii/issues/9360) (Date incorrectly shown) reported by @enboig
- [Issue 9389](https://github.com/firefly-iii/firefly-iii/issues/9389) (Budget and Bill field on Recurring transactions not updating) reported by @HHUBSS
- [Issue 9416](https://github.com/firefly-iii/firefly-iii/issues/9416) (Linking Transaction to Bill doesn't mark as paid for 31st) reported by @harrhunt
- [Issue 9427](https://github.com/firefly-iii/firefly-iii/issues/9427) (The standard financial report does not show all transactions for the income categories) reported by @Neroxeles
- [Issue 9443](https://github.com/firefly-iii/firefly-iii/issues/9443) (Budget report on inactive budget gives a 404) reported by @adyanth
- [Issue 9444](https://github.com/firefly-iii/firefly-iii/issues/9444) (Printing a page does not include dates) reported by @cachho
- [Issue 9447](https://github.com/firefly-iii/firefly-iii/issues/9447) (Transaction doesn't show up when attaching HTML file) reported by @Marc928132
### API
- API version is no longer distinguished from Firefly III version. API jumps from v2.1.0 to v6.1.22
- API v2 is cleaned up and misses a few previously available endpoints. They will be added in the future.
- [Discussion 9271](https://github.com/orgs/firefly-iii/discussions/9271) (/v2/chart/balance/balance ignoring the `period` parameter) started by @victorbalssa
## 6.1.21 - 2024-09-30
### Added

469
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -110,9 +110,9 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2024-11-03',
'api_version' => '2.1.0',
'db_version' => 24,
'version' => '6.1.24',
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 25,
// generic settings
'maxUploadSize' => 1073741824, // 1 GB
@@ -255,6 +255,7 @@ return [
'allowedMimes' => [
// plain files
'text/plain',
'text/html',
// images
'image/jpeg',

View File

@@ -294,25 +294,6 @@ class CreateMainTables extends Migration
app('log')->error(self::TABLE_ALREADY_EXISTS);
}
}
if (!Schema::hasTable('limit_repetitions')) {
try {
Schema::create(
'limit_repetitions',
static function (Blueprint $table): void {
$table->increments('id');
$table->timestamps();
$table->integer('budget_limit_id', false, true);
$table->date('startdate');
$table->date('enddate');
$table->decimal('amount', 32, 12);
$table->foreign('budget_limit_id')->references('id')->on('budget_limits')->onDelete('cascade');
}
);
} catch (QueryException $e) {
app('log')->error(sprintf(self::TABLE_ERROR, 'limit_repetitions', $e->getMessage()));
app('log')->error(self::TABLE_ALREADY_EXISTS);
}
}
}
private function createCategoriesTable(): void

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\QueryException;
use Illuminate\Database\Schema\Blueprint;
return new class () extends Migration {
private array $tables;
public function __construct()
{
$this->tables = [
'account_balances' => ['date'], // done
'available_budgets' => ['start_date', 'end_date'], // done
'bills' => ['date', 'end_date', 'extension_date'], // done
'budget_limits' => ['start_date', 'end_date'], // done
'currency_exchange_rates' => ['date'], // done
'invited_users' => ['expires'],
'piggy_bank_events' => ['date'],
'piggy_bank_repetitions' => ['startdate', 'targetdate'],
'piggy_banks' => ['startdate', 'targetdate'], // done
'recurrences' => ['first_date', 'repeat_until', 'latest_date'],
'tags' => ['date'],
'transaction_journals' => ['date'],
];
}
/**
* Run the migrations.
* TODO journal_meta, all date fields?
*/
public function up(): void
{
foreach ($this->tables as $table => $columns) {
foreach ($columns as $column) {
$newColumn = sprintf('%s_tz', $column);
if (Schema::hasColumn($table, $column) && !Schema::hasColumn($table, $newColumn)) {
try {
Schema::table(
$table,
static function (Blueprint $table) use ($column, $newColumn): void {
$table->string($newColumn, 50)->nullable()->after($column);
}
);
} catch (QueryException $e) {
app('log')->error(sprintf('Could not add column "%s" to table "%s" query: %s', $newColumn, $table, $e->getMessage()));
app('log')->error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.');
}
}
}
}
}
/**
* Reverse the migrations.
*/
public function down(): void {}
};

2029
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,16 @@ $.ajaxSetup({
}
});
function parseToLocalDates() {
"use strict";
$('span.date-time').each(function () {
var date = $(this).data('date');
var obj = moment.utc(date).local();
var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
$(this).text(obj.format(date_time_js) + ' ('+ timeZone +')');
});
}
$(function () {
"use strict";
@@ -96,6 +106,9 @@ $(function () {
// trigger list thing
listLengthInitial();
// update dates:
parseToLocalDates();
});
function currencySelect(e) {

View File

@@ -127,9 +127,9 @@ There are many ways to run Firefly III
You can contact me at [james@firefly-iii.org](mailto:james@firefly-iii.org), you may open an issue in the [main repository](https://github.com/firefly-iii/firefly-iii) or contact me through [gitter](https://gitter.im/firefly-iii/firefly-iii) and [Mastodon](https://fosstodon.org/@ff3).
Of course, there are some [contributing guidelines](https://docs.firefly-iii.org/references/support/#contributing-code) and a [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/main/.github/code_of_conduct.md), which I invite you to check out.
Of course, there are some [contributing guidelines](https://docs.firefly-iii.org/explanation/support/#contributing-code) and a [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/main/.github/code_of_conduct.md), which I invite you to check out.
I can always use your help [squashing bugs](https://docs.firefly-iii.org/references/support/), thinking about [new features](https://docs.firefly-iii.org/references/support/) or [translating Firefly III](https://docs.firefly-iii.org/how-to/firefly-iii/development/translations/) into other languages.
I can always use your help [squashing bugs](https://docs.firefly-iii.org/explanation/support/), thinking about [new features](https://docs.firefly-iii.org/explanation/support/) or [translating Firefly III](https://docs.firefly-iii.org/how-to/firefly-iii/development/translations/) into other languages.
[Sonarcloud][sc-project-url] scans the code of Firefly III. If you want to help improve Firefly III, check out the latest reports and take your pick!

View File

@@ -5,8 +5,8 @@
"flash_warning": "Waarschuwing!",
"flash_success": "Gelukt!",
"close": "Sluiten",
"select_dest_account": "Please select or type a valid destination account name",
"select_source_account": "Please select or type a valid source account name",
"select_dest_account": "Selecteer of type een geldige doelrekeningnaam",
"select_source_account": "Selecteer of type een geldige bronrekeningnaam",
"split_transaction_title": "Beschrijving van de gesplitste transactie",
"errors_submission": "Er ging iets mis. Check de errors.",
"split": "Splitsen",

View File

@@ -26,6 +26,8 @@ declare(strict_types=1);
return [
// general stuff:
'stored_in_tz' => 'stored in ":timezone"',
'displayed_in_tz' => 'displayed in ":timezone"',
'close' => 'Close',
'actions' => 'Actions',
'edit' => 'Edit',
@@ -755,7 +757,7 @@ return [
'instructions_rule_from_journal' => 'Create a rule based on one of your transactions. Complement or submit the form below.',
'rule_is_strict' => 'strict rule',
'rule_is_not_strict' => 'non-strict rule',
'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.',
'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed if this particular rule is executed.',
'rule_help_strict' => 'In strict rules ALL triggers must fire for the action(s) to be executed. In non-strict rules, ANY trigger is enough for the action(s) to be executed.',
'rule_help_active' => 'Inactive rules will never fire.',
'stored_new_rule' => 'Stored new rule with title ":title"',
@@ -880,8 +882,8 @@ return [
'rule_trigger_currency_is' => 'Transaction currency is ":trigger_value"',
'rule_trigger_foreign_currency_is_choice' => 'Transaction foreign currency is..',
'rule_trigger_foreign_currency_is' => 'Transaction foreign currency is ":trigger_value"',
'rule_trigger_has_attachments_choice' => 'Has at least this many attachments',
'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)',
'rule_trigger_has_attachments_choice' => 'Has any attachments',
'rule_trigger_has_attachments' => 'Has any attachment(s)',
'rule_trigger_has_no_category_choice' => 'Has no category',
'rule_trigger_has_no_category' => 'Transaction has no category',
'rule_trigger_has_any_category_choice' => 'Has a (any) category',
@@ -1407,7 +1409,7 @@ return [
'administration_role_mng_piggies' => 'Manage piggy banks',
'administration_role_mng_subscriptions' => 'Manage subscriptions',
'administration_role_mng_rules' => 'Manage rules',
'administration_role_mng_recurring' => 'Manage recurring transactions ',
'administration_role_mng_recurring' => 'Manage recurring transactions',
'administration_role_mng_webhooks' => 'Manage webhooks',
'administration_role_mng_currencies' => 'Manage currencies',
'administration_role_view_reports' => 'View reports',

View File

@@ -63,7 +63,7 @@ return [
'invalid_selection' => 'Your selection is invalid.',
'belongs_user' => 'This value is linked to an object that does not seem to exist.',
'belongs_user_or_user_group' => 'This value is linked to an object that does not seem to exist in your current financial administration.',
'no_access_group' => 'The user has no access to this user group.',
'no_access_group' => 'The user has no access to this administration.',
'no_accepted_roles_defined' => 'No access roles have been defined for this endpoint, access denied.',
'at_least_one_transaction' => 'Need at least one transaction.',
'recurring_transaction_id' => 'Need at least one transaction.',
@@ -262,6 +262,7 @@ return [
'gte.file' => 'The :attribute must be greater than or equal to :value kilobytes.',
'gte.string' => 'The :attribute must be greater than or equal to :value characters.',
'gte.array' => 'The :attribute must have :value items or more.',
'missing_with' => 'The :attribute cannot be combined with another field.',
'amount_required_for_auto_budget' => 'The amount is required.',
'auto_budget_amount_positive' => 'The amount must be more than zero.',

View File

@@ -44,6 +44,7 @@ var todayText = ' {{ trans('firefly.today')|escape('js') }}';
// some formatting stuff:
var month_and_day_js = "{{ trans('config.month_and_day_js') }}";
var date_time_js = "{{ trans('config.date_time_js') }}";
var acc_config_new = {format: accountingConfig};
// strings and translations used often:

View File

@@ -33,7 +33,7 @@
<th class="hidden-xs">&nbsp;</th>
<th>{{ trans('list.description') }}</th>
<th>{{ trans('list.amount') }}</th>
<th class="hidden-xs">{{ trans('list.date') }}</th>
<th>{{ trans('list.date') }}</th>
<th>{{ trans('list.source_account') }}</th>
<th>{{ trans('list.destination_account') }}</th>
{% if showCategory %}
@@ -210,7 +210,7 @@
{% endif %}
{% endif %}
</td>
<td style=" {{ style|raw }}" class="hidden-xs">
<td style=" {{ style|raw }}">
{{ transaction.date.isoFormat(monthAndDayFormat) }}
</td>
<td style=" {{ style|raw }}">

View File

@@ -74,7 +74,12 @@
<tr>
<td style="width:30%;">{{ trans('list.date') }}</td>
<td>
<span class="date-time" data-date="{{ first.date.toIso8601ZuluString() }}" title="{{ first.date.isoFormat(dateTimeFormat) }}{% if(first.date_tz != '') %} ({{ trans('firefly.stored_in_tz', {timezone: first.date_tz }) }}, {{ trans('firefly.displayed_in_tz', {timezone: config('app.timezone') }) }}){% endif %}">
{{ first.date.isoFormat(dateTimeFormat) }}
{% if(first.date_tz != '') %}
({{ trans('firefly.stored_in_tz', {timezone: first.date_tz }) }})
{% endif %}
</span>
</td>
</tr>
</tbody>

View File

@@ -53,6 +53,24 @@ Route::group(
}
);
// USER GROUP ROUTES
Route::group(
[
'namespace' => 'FireflyIII\Api\V2\Controllers\UserGroup',
'prefix' => 'v2/user-groups',
'as' => 'api.v2.user-groups.',
],
static function (): void {
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']);
Route::get('{userGroup}', ['uses' => 'ShowController@show', 'as' => 'show']);
// Route::put('{userGroup}', ['uses' => 'UpdateController@update', 'as' => 'update']);
// Route::post('{userGroup}/use', ['uses' => 'UpdateController@useUserGroup', 'as' => 'use']);
// Route::put('{userGroup}/update-membership', ['uses' => 'UpdateController@updateMembership', 'as' => 'updateMembership']);
// Route::delete('{userGroup}', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']);
}
);
// CHART ROUTES
Route::group(
[
@@ -221,23 +239,7 @@ Route::group(
}
);
// V2 API route for user groups (administrations).
Route::group(
[
'namespace' => 'FireflyIII\Api\V2\Controllers\UserGroup',
'prefix' => 'v2/user-groups',
'as' => 'api.v2.user-groups.',
],
static function (): void {
// Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
// Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']);
// Route::get('{userGroup}', ['uses' => 'ShowController@show', 'as' => 'show']);
// Route::put('{userGroup}', ['uses' => 'UpdateController@update', 'as' => 'update']);
// Route::post('{userGroup}/use', ['uses' => 'UpdateController@useUserGroup', 'as' => 'use']);
// Route::put('{userGroup}/update-membership', ['uses' => 'UpdateController@updateMembership', 'as' => 'updateMembership']);
// Route::delete('{userGroup}', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']);
}
);
// V2 JSON API ROUTES
// JsonApiRoute::server('v2')->prefix('v2')