Compare commits

..

48 Commits

Author SHA1 Message Date
github-actions[bot]
01181ceea9 Merge pull request #10672 from firefly-iii/release-1753879077
🤖 Automatically merge the PR into the develop branch.
2025-07-30 14:38:08 +02:00
JC5
97643639d1 🤖 Auto commit for release 'develop' on 2025-07-30 2025-07-30 14:37:57 +02:00
James Cole
424783c47b Add convertToNative to cache key. 2025-07-30 14:33:26 +02:00
James Cole
ea0ced70b2 Clean up sankey. 2025-07-30 11:24:00 +02:00
James Cole
1a633e64ef Category chart will also convert. 2025-07-30 10:20:35 +02:00
James Cole
30da3f4399 Also include budget in currency conversion. 2025-07-30 09:59:52 +02:00
github-actions[bot]
6bdff95d87 Merge pull request #10671 from firefly-iii/release-1753858574
🤖 Automatically merge the PR into the develop branch.
2025-07-30 08:56:22 +02:00
JC5
895ae279d5 🤖 Auto commit for release 'develop' on 2025-07-30 2025-07-30 08:56:14 +02:00
James Cole
8b57f45963 Return the promise. 2025-07-30 08:37:19 +02:00
James Cole
a2d2b7edd3 Add warning to translations. 2025-07-30 07:27:00 +02:00
James Cole
3d65f00c6e Correct tag date and add warning. 2025-07-30 07:26:50 +02:00
James Cole
333004c4d9 Upgrade to font awesome 7, make sure chart includes currencies outside of limits. 2025-07-30 06:59:58 +02:00
James Cole
7451659824 Merge pull request #10661 from firefly-iii/dependabot/npm_and_yarn/develop/cross-env-10.0.0
Bump cross-env from 7.0.3 to 10.0.0
2025-07-28 20:44:31 +02:00
mergify[bot]
ec89c23ace Merge branch 'develop' into dependabot/npm_and_yarn/develop/cross-env-10.0.0 2025-07-28 05:08:38 +00:00
James Cole
92d07d346f Merge pull request #10660 from firefly-iii/dependabot/npm_and_yarn/develop/fortawesome/fontawesome-free-7.0.0
Bump @fortawesome/fontawesome-free from 6.7.2 to 7.0.0
2025-07-28 07:08:02 +02:00
mergify[bot]
a7ac894af2 Merge branch 'develop' into dependabot/npm_and_yarn/develop/cross-env-10.0.0 2025-07-28 04:39:49 +00:00
dependabot[bot]
20f89e3a7c Bump cross-env from 7.0.3 to 10.0.0
Bumps [cross-env](https://github.com/kentcdodds/cross-env) from 7.0.3 to 10.0.0.
- [Release notes](https://github.com/kentcdodds/cross-env/releases)
- [Changelog](https://github.com/kentcdodds/cross-env/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kentcdodds/cross-env/compare/v7.0.3...v10.0.0)

---
updated-dependencies:
- dependency-name: cross-env
  dependency-version: 10.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 04:39:15 +00:00
mergify[bot]
5d2f11c3c7 Merge branch 'develop' into dependabot/npm_and_yarn/develop/fortawesome/fontawesome-free-7.0.0 2025-07-28 04:38:58 +00:00
dependabot[bot]
1eea79e431 Bump @fortawesome/fontawesome-free from 6.7.2 to 7.0.0
Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 6.7.2 to 7.0.0.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/7.x/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.7.2...7.0.0)

---
updated-dependencies:
- dependency-name: "@fortawesome/fontawesome-free"
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 04:38:22 +00:00
James Cole
1aaaac67ca Merge pull request #10659 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-2.1.20
Bump phpstan/phpstan from 2.1.19 to 2.1.20
2025-07-28 06:38:11 +02:00
mergify[bot]
1eb86639c9 Merge branch 'develop' into dependabot/composer/develop/phpstan/phpstan-2.1.20 2025-07-28 04:31:47 +00:00
dependabot[bot]
28b911c4ee Bump phpstan/phpstan from 2.1.19 to 2.1.20
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 2.1.19 to 2.1.20.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/2.1.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/2.1.19...2.1.20)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-version: 2.1.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 04:31:06 +00:00
github-actions[bot]
c525c70ec0 Merge pull request #10657 from firefly-iii/release-1753673814
🤖 Automatically merge the PR into the develop branch.
2025-07-28 05:37:03 +02:00
JC5
1f7d6e218b 🤖 Auto commit for release 'develop' on 2025-07-28 2025-07-28 05:36:54 +02:00
James Cole
a69b6d9ce2 Respond to "convert to native". 2025-07-27 20:45:08 +02:00
James Cole
28b2ddde18 Account chart can do live update 2025-07-27 07:36:23 +02:00
James Cole
a16cc73c77 Sync "convert to native" 2025-07-26 19:17:26 +02:00
James Cole
46395e350a Improved budget chart. 2025-07-26 06:47:21 +02:00
Sander Dorigo
f62e49090c Merge branch 'main' into develop 2025-07-25 11:18:24 +02:00
James Cole
af3b40a314 Delete .github/workflows/sonarcloud.yml
Signed-off-by: James Cole <james@firefly-iii.org>
2025-07-25 11:10:05 +02:00
James Cole
c3cea0fa9e Update cleanup.yml
Signed-off-by: James Cole <james@firefly-iii.org>
2025-07-25 11:09:55 +02:00
James Cole
6cbdb2ce70 Update index.js
Fix thnigie

Signed-off-by: James Cole <james@firefly-iii.org>
2025-07-25 10:56:42 +02:00
James Cole
b78460100d Fix #10646 2025-07-25 05:44:03 +02:00
github-actions[bot]
bf6e1cb0e1 Merge pull request #10642 from firefly-iii/release-1753333227
🤖 Automatically merge the PR into the develop branch.
2025-07-24 07:00:35 +02:00
JC5
6a53f5031c 🤖 Auto commit for release 'develop' on 2025-07-24 2025-07-24 07:00:27 +02:00
James Cole
ae15ec01e8 Merge branch 'main' into develop 2025-07-24 06:56:07 +02:00
James Cole
fe3c7c47c4 Filter list of bills. 2025-07-24 06:55:53 +02:00
James Cole
68b934010c Merge pull request #10640 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-25988072ba
Bump the npm_and_yarn group across 1 directory with 2 updates
2025-07-23 19:37:09 +02:00
dependabot[bot]
22852bd238 Bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 1 update in the / directory: [axios](https://github.com/axios/axios).


Updates `axios` from 1.10.0 to 1.11.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.10.0...v1.11.0)

Updates `form-data` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.3...v4.0.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.11.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-23 16:51:30 +00:00
github-actions[bot]
5b3b1804f3 Merge pull request #10638 from firefly-iii/release-1753247140
🤖 Automatically merge the PR into the develop branch.
2025-07-23 07:05:49 +02:00
JC5
f2588eb343 🤖 Auto commit for release 'develop' on 2025-07-23 2025-07-23 07:05:40 +02:00
James Cole
64a643ceec Expand balances. 2025-07-23 07:01:10 +02:00
github-actions[bot]
1add505644 Merge pull request #10634 from firefly-iii/release-1753068994
🤖 Automatically merge the PR into the develop branch.
2025-07-21 05:36:42 +02:00
JC5
9663eb6a19 🤖 Auto commit for release 'develop' on 2025-07-21 2025-07-21 05:36:34 +02:00
github-actions[bot]
f30a24a02f Merge pull request #10629 from firefly-iii/release-1753029916
🤖 Automatically merge the PR into the develop branch.
2025-07-20 18:45:22 +02:00
JC5
68655d60a6 🤖 Auto commit for release 'develop' on 2025-07-20 2025-07-20 18:45:16 +02:00
James Cole
63b0efcd81 Remove sonarcloud flow. 2025-07-20 18:41:05 +02:00
James Cole
93284682c8 Improve bill overview. 2025-07-20 14:02:53 +02:00
51 changed files with 1332 additions and 925 deletions

View File

@@ -406,20 +406,20 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.84.0", "version": "v3.85.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca" "reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca", "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2fb6d7f6c3398dca5786a1635b27405d73a417ba",
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca", "reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"clue/ndjson-react": "^1.0", "clue/ndjson-react": "^1.3",
"composer/semver": "^3.4", "composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.5", "composer/xdebug-handler": "^3.0.5",
"ext-filter": "*", "ext-filter": "*",
@@ -429,12 +429,12 @@
"fidry/cpu-core-counter": "^1.2", "fidry/cpu-core-counter": "^1.2",
"php": "^7.4 || ^8.0", "php": "^7.4 || ^8.0",
"react/child-process": "^0.6.6", "react/child-process": "^0.6.6",
"react/event-loop": "^1.0", "react/event-loop": "^1.5",
"react/promise": "^2.11 || ^3.0", "react/promise": "^3.2",
"react/socket": "^1.0", "react/socket": "^1.16",
"react/stream": "^1.0", "react/stream": "^1.4",
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
"symfony/console": "^5.4.45 || ^6.4.13 || ^7.0", "symfony/console": "^5.4.47 || ^6.4.13 || ^7.0",
"symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0", "symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0", "symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0", "symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0",
@@ -499,7 +499,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0" "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.85.1"
}, },
"funding": [ "funding": [
{ {
@@ -507,7 +507,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-07-15T18:21:57+00:00" "time": "2025-07-29T22:22:50+00:00"
}, },
{ {
"name": "psr/container", "name": "psr/container",

View File

@@ -66,7 +66,6 @@ jobs:
'label-actions.yml', 'label-actions.yml',
'lock.yml', 'lock.yml',
'release.yml', 'release.yml',
'sonarcloud.yml',
'stale.yml' 'stale.yml'
] ]

View File

@@ -1,71 +0,0 @@
name: 'Code - Run Sonarcloud'
on:
pull_request:
workflow_dispatch:
push:
branches:
- main
env:
DB_CONNECTION: sqlite
APP_KEY: TestTestTestTestTestTestTestTest
jobs:
sonarcloud:
name: SonarCloud
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP with Xdebug
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: xdebug
extensions: >-
bcmath
curl
fileinfo
iconv
intl
json
sqlite3
mbstring
openssl
pdo
session
simplexml
sodium
tokenizer
xml
xmlwriter
- name: Copy standard configuration
run: cp .env.testing .env
- name: Install Composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress --no-scripts
- name: "Create database file"
run: |
touch storage/database/database.sqlite
wget -q https://github.com/firefly-iii/test-fixtures/raw/refs/heads/main/test-database.sqlite -O storage/database/database.sqlite
- name: "Upgrades the database to the latest version"
run: |
php artisan firefly-iii:upgrade-database
chmod 600 storage/oauth-public.key
chmod 600 storage/oauth-private.key
- name: "Integrity Database Report"
run: php artisan firefly-iii:report-integrity
- name: "Run tests with coverage"
run: composer coverage
- name: Fix code coverage paths
run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' coverage.xml
- name: SonarCloud Scan
uses: SonarSource/sonarqube-scan-action@v5.2.0
env:
GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_PERSONAL_ACCESS_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

View File

@@ -24,16 +24,16 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart; namespace FireflyIII\Api\V1\Controllers\Chart;
use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Models\TransactionCurrency;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Chart\ChartRequest; use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
use FireflyIII\Api\V1\Requests\Data\DateRequest; use FireflyIII\Api\V1\Requests\Data\DateRequest;
use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Preference; use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Chart\ChartData; use FireflyIII\Support\Chart\ChartData;
use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Facades\Preferences;
@@ -42,6 +42,7 @@ use FireflyIII\Support\Http\Api\ApiSupport;
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter; use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
/** /**
* Class AccountController * Class AccountController
@@ -86,10 +87,12 @@ class AccountController extends Controller
// move date to end of day // move date to end of day
$queryParameters['start']->startOfDay(); $queryParameters['start']->startOfDay();
$queryParameters['end']->endOfDay(); $queryParameters['end']->endOfDay();
Log::debug(sprintf('dashboard(), convert to native: %s', var_export($this->convertToNative, true)));
// loop each account, and collect info: // loop each account, and collect info:
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
Log::debug(sprintf('Account #%d ("%s")', $account->id, $account->name));
$this->renderAccountData($queryParameters, $account); $this->renderAccountData($queryParameters, $account);
} }
@@ -101,15 +104,22 @@ class AccountController extends Controller
*/ */
private function renderAccountData(array $params, Account $account): void private function renderAccountData(array $params, Account $account): void
{ {
$currency = $this->repository->getAccountCurrency($account); Log::debug(sprintf('Now in %s(array, #%d)', __METHOD__, $account->id));
$currency = $this->repository->getAccountCurrency($account);
$currentStart = clone $params['start'];
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
$previous = array_values($range)[0]['balance'];
$nativePrevious = null;
if (!$currency instanceof TransactionCurrency) { if (!$currency instanceof TransactionCurrency) {
$currency = $this->default; $currency = $this->default;
} }
$currentSet = [ $currentSet = [
'label' => $account->name, 'label' => $account->name,
// the currency that belongs to the account. // the currency that belongs to the account.
'currency_id' => (string) $currency->id, 'currency_id' => (string)$currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
@@ -121,18 +131,33 @@ class AccountController extends Controller
'period' => '1D', 'period' => '1D',
'entries' => [], 'entries' => [],
]; ];
$currentStart = clone $params['start']; if ($this->convertToNative) {
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative); $currentSet['native_entries'] = [];
$currentSet['native_currency_id'] = (string)$this->nativeCurrency->id;
$currentSet['native_currency_code'] = $this->nativeCurrency->code;
$currentSet['native_currency_symbol'] = $this->nativeCurrency->symbol;
$currentSet['native_currency_decimal_places'] = $this->nativeCurrency->decimal_places;
$nativePrevious = array_values($range)[0]['native_balance'];
}
$previous = array_values($range)[0]['balance'];
while ($currentStart <= $params['end']) { while ($currentStart <= $params['end']) {
$format = $currentStart->format('Y-m-d'); $format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString(); $label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous; $balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
$previous = $balance; $previous = $balance;
$currentSet['entries'][$label] = $balance;
// do the same for the native balance, if relevant:
$nativeBalance = null;
if ($this->convertToNative) {
$nativeBalance = array_key_exists($format, $range) ? $range[$format]['native_balance'] : $nativePrevious;
$nativePrevious = $nativeBalance;
$currentSet['native_entries'][$label] = $nativeBalance;
}
$currentStart->addDay(); $currentStart->addDay();
$currentSet['entries'][$label] = $balance;
} }
$this->chartData->add($currentSet); $this->chartData->add($currentSet);
} }
@@ -146,19 +171,84 @@ class AccountController extends Controller
public function overview(DateRequest $request): JsonResponse public function overview(DateRequest $request): JsonResponse
{ {
// parameters for chart: // parameters for chart:
$dates = $request->getAll(); $dates = $request->getAll();
/** @var Carbon $start */ /** @var Carbon $start */
$start = $dates['start']; $start = $dates['start'];
/** @var Carbon $end */ /** @var Carbon $end */
$end = $dates['end']; $end = $dates['end'];
// set dates to end of day + start of day: // set dates to end of day + start of day:
$start->startOfDay(); $start->startOfDay();
$end->endOfDay(); $end->endOfDay();
// user's preferences $frontPageIds = $this->getFrontPageAccountIds();
$accounts = $this->repository->getAccountsById($frontPageIds);
$chartData = [];
/** @var Account $account */
foreach ($accounts as $account) {
Log::debug(sprintf('Rendering chart data for account %s (%d)', $account->name, $account->id));
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
$currentStart = clone $start;
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
$previous = array_values($range)[0]['balance'];
$nativePrevious = null;
$currentSet = [
'label' => $account->name,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'start_date' => $start->toAtomString(),
'end_date' => $end->toAtomString(),
'type' => 'line', // line, area or bar
'yAxisID' => 0, // 0, 1, 2
'entries' => [],
];
// add "native_entries" if convertToNative is true:
if ($this->convertToNative) {
$currentSet['native_entries'] = [];
$currentSet['native_currency_id'] = (string)$this->nativeCurrency->id;
$currentSet['native_currency_code'] = $this->nativeCurrency->code;
$currentSet['native_currency_symbol'] = $this->nativeCurrency->symbol;
$currentSet['native_currency_decimal_places'] = $this->nativeCurrency->decimal_places;
$nativePrevious = array_values($range)[0]['native_balance'];
}
// also get the native balance if convertToNative is true:
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
// balance is based on "balance" from the $range variable.
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
$previous = $balance;
$currentSet['entries'][$label] = $balance;
// do the same for the native balance, if relevant:
$nativeBalance = null;
if ($this->convertToNative) {
$nativeBalance = array_key_exists($format, $range) ? $range[$format]['native_balance'] : $nativePrevious;
$nativePrevious = $nativeBalance;
$currentSet['native_entries'][$label] = $nativeBalance;
}
$currentStart->addDay();
}
$chartData[] = $currentSet;
}
return response()->json($chartData);
}
private function getFrontPageAccountIds(): array
{
$defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray(); $defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
/** @var Preference $frontpage */ /** @var Preference $frontpage */
@@ -169,41 +259,6 @@ class AccountController extends Controller
$frontpage->save(); $frontpage->save();
} }
// get accounts: return $frontpage->data ?? $defaultSet;
$accounts = $this->repository->getAccountsById($frontpage->data);
$chartData = [];
/** @var Account $account */
foreach ($accounts as $account) {
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
$field = $this->convertToNative && $currency->id !== $this->nativeCurrency->id ? 'native_balance' : 'balance';
$currentSet = [
'label' => $account->name,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'start_date' => $start->toAtomString(),
'end_date' => $end->toAtomString(),
'type' => 'line', // line, area or bar
'yAxisID' => 0, // 0, 1, 2
'entries' => [],
];
// TODO this code is also present in the V2 chart account controller so this method is due to be deprecated.
$currentStart = clone $start;
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
$previous = array_values($range)[0][$field];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format][$field] : $previous;
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;
}
$chartData[] = $currentSet;
}
return response()->json($chartData);
} }
} }

View File

@@ -36,6 +36,7 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData; use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait; use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -77,6 +78,8 @@ class BudgetController extends Controller
/** /**
* TODO see autocomplete/accountcontroller * TODO see autocomplete/accountcontroller
*
* @throws FireflyException
*/ */
public function dashboard(DateRequest $request): JsonResponse public function dashboard(DateRequest $request): JsonResponse
{ {
@@ -107,23 +110,41 @@ class BudgetController extends Controller
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
{ {
// get all limits: // get all limits:
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end); $limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
$rows = []; $rows = [];
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
/**
* @var int $currencyId
* @var array $row
*/
foreach ($expenses as $currencyId => $row) {
// budgeted, left and overspent are now 0.
$limit = $this->filterLimit($currencyId, $limits);
if (null !== $limit) {
$row['budgeted'] = $limit->amount;
$row['left'] = bcsub($row['budgeted'], bcmul($row['spent'], '-1'));
$row['overspent'] = bcmul($row['left'], '-1');
$row['left'] = 1 === bccomp($row['left'], '0') ? $row['left'] : '0';
$row['overspent'] = 1 === bccomp($row['overspent'], '0') ? $row['overspent'] : '0';
}
$rows[] = $row;
}
// if no limits // if no limits
if (0 === $limits->count()) { // if (0 === $limits->count()) {
// return as a single item in an array // return as a single item in an array
$rows = $this->noBudgetLimits($budget, $start, $end); // $rows = $this->noBudgetLimits($budget, $start, $end);
} // }
if ($limits->count() > 0) {
$rows = $this->budgetLimits($budget, $limits);
}
// is always an array // is always an array
$return = []; $return = [];
foreach ($rows as $row) { foreach ($rows as $row) {
$current = [ $current = [
'label' => $budget->name, 'label' => $budget->name,
'currency_id' => (string) $row['currency_id'], 'currency_id' => (string)$row['currency_id'],
'currency_code' => $row['currency_code'], 'currency_code' => $row['currency_code'],
'currency_name' => $row['currency_name'], 'currency_name' => $row['currency_name'],
'currency_decimal_places' => $row['currency_decimal_places'], 'currency_decimal_places' => $row['currency_decimal_places'],
@@ -131,6 +152,7 @@ class BudgetController extends Controller
'start' => $row['start'], 'start' => $row['start'],
'end' => $row['end'], 'end' => $row['end'],
'entries' => [ 'entries' => [
'budgeted' => $row['budgeted'],
'spent' => $row['spent'], 'spent' => $row['spent'],
'left' => $row['left'], 'left' => $row['left'],
'overspent' => $row['overspent'], 'overspent' => $row['overspent'],
@@ -159,11 +181,9 @@ class BudgetController extends Controller
* Shared between the "noBudgetLimits" function and "processLimit". Will take a single set of expenses and return * Shared between the "noBudgetLimits" function and "processLimit". Will take a single set of expenses and return
* its info. * its info.
* *
* @param array<int, array<int, string>> $array
*
* @throws FireflyException * @throws FireflyException
*/ */
private function processExpenses(int $budgetId, array $array, Carbon $start, Carbon $end): array private function processExpenses(int $budgetId, array $spent, Carbon $start, Carbon $end): array
{ {
$return = []; $return = [];
@@ -174,16 +194,17 @@ class BudgetController extends Controller
* @var int $currencyId * @var int $currencyId
* @var array $block * @var array $block
*/ */
foreach ($array as $currencyId => $block) { foreach ($spent as $currencyId => $block) {
$this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId); $this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
$return[$currencyId] ??= [ $return[$currencyId] ??= [
'currency_id' => (string) $currencyId, 'currency_id' => (string)$currencyId,
'currency_code' => $block['currency_code'], 'currency_code' => $block['currency_code'],
'currency_name' => $block['currency_name'], 'currency_name' => $block['currency_name'],
'currency_symbol' => $block['currency_symbol'], 'currency_symbol' => $block['currency_symbol'],
'currency_decimal_places' => (int) $block['currency_decimal_places'], 'currency_decimal_places' => (int)$block['currency_decimal_places'],
'start' => $start->toAtomString(), 'start' => $start->toAtomString(),
'end' => $end->toAtomString(), 'end' => $end->toAtomString(),
'budgeted' => '0',
'spent' => '0', 'spent' => '0',
'left' => '0', 'left' => '0',
'overspent' => '0', 'overspent' => '0',
@@ -193,7 +214,7 @@ class BudgetController extends Controller
// var_dump($return); // var_dump($return);
/** @var array $journal */ /** @var array $journal */
foreach ($currentBudgetArray['transaction_journals'] as $journal) { foreach ($currentBudgetArray['transaction_journals'] as $journal) {
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], (string) $journal['amount']); $return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], (string)$journal['amount']);
} }
} }
@@ -240,16 +261,52 @@ class BudgetController extends Controller
$filtered = array_filter($spent, fn ($entry) => $entry['currency_id'] === $limitCurrencyId); $filtered = array_filter($spent, fn ($entry) => $entry['currency_id'] === $limitCurrencyId);
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end); $result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
if (1 === count($result)) { if (1 === count($result)) {
$compare = bccomp($limit->amount, (string) app('steam')->positive($result[$limitCurrencyId]['spent'])); $compare = bccomp($limit->amount, (string)app('steam')->positive($result[$limitCurrencyId]['spent']));
$result[$limitCurrencyId]['budgeted'] = $limit->amount;
if (1 === $compare) { if (1 === $compare) {
// convert this amount into the native currency: // convert this amount into the native currency:
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, (string) $result[$limitCurrencyId]['spent']); $result[$limitCurrencyId]['left'] = bcadd($limit->amount, (string)$result[$limitCurrencyId]['spent']);
} }
if ($compare <= 0) { if ($compare <= 0) {
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, (string) $result[$limitCurrencyId]['spent'])); $result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, (string)$result[$limitCurrencyId]['spent']));
} }
} }
return $result; return $result;
} }
private function filterLimit(int $currencyId, Collection $limits): ?BudgetLimit
{
$amount = '0';
$limit = null;
$converter = new ExchangeRateConverter();
/** @var BudgetLimit $current */
foreach ($limits as $current) {
if (true === $this->convertToNative) {
if ($current->transaction_currency_id === $this->nativeCurrency->id) {
// simply add it.
$amount = bcadd($amount, (string)$current->amount);
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
if ($current->transaction_currency_id !== $this->nativeCurrency->id) {
// convert and then add it.
$converted = $converter->convert($current->transactionCurrency, $this->nativeCurrency, $limit->start_date, $limit->amount);
$amount = bcadd($amount, $converted);
Log::debug(sprintf('Budgeted in limit #%d: %s %s, converted to %s %s', $current->id, $current->transactionCurrency->code, $current->amount, $this->nativeCurrency->code, $converted));
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
}
if ($current->transaction_currency_id === $currencyId) {
$limit = $current;
}
}
if (null !== $limit && true === $this->convertToNative) {
// convert and add all amounts.
$limit->amount = app('steam')->positive($amount);
Log::debug(sprintf('Final amount in limit with converted amount %s', $limit->amount));
}
return $limit;
}
} }

View File

@@ -25,17 +25,20 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart; namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller; use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest; use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData; use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait; use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
/** /**
* Class BudgetController * Class BudgetController
@@ -45,6 +48,8 @@ class CategoryController extends Controller
use CleansChartData; use CleansChartData;
use ValidatesUserGroupTrait; use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private AccountRepositoryInterface $accountRepos; private AccountRepositoryInterface $accountRepos;
private CurrencyRepositoryInterface $currencyRepos; private CurrencyRepositoryInterface $currencyRepos;
@@ -79,9 +84,10 @@ class CategoryController extends Controller
/** @var Carbon $end */ /** @var Carbon $end */
$end = $this->parameters->get('end'); $end = $this->parameters->get('end');
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]); $accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]);
$currencies = []; $currencies = [];
$return = []; $return = [];
$converter = new ExchangeRateConverter();
// get journals for entire period: // get journals for entire period:
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
@@ -93,20 +99,40 @@ class CategoryController extends Controller
/** @var array $journal */ /** @var array $journal */
foreach ($journals as $journal) { foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id']; // find journal:
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId); $journalCurrencyId = (int)$journal['currency_id'];
$currencies[$currencyId] = $currency; $currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
$categoryName = $journal['category_name'] ?? (string) trans('firefly.no_category'); $currencies[$journalCurrencyId] = $currency;
$amount = app('steam')->positive($journal['amount']); $currencyId = (int)$currency->id;
$key = sprintf('%s-%s', $categoryName, $currency->code); $currencyName = (string)$currency->name;
$currencyCode = (string)$currency->code;
$currencySymbol = (string)$currency->symbol;
$currencyDecimalPlaces = (int)$currency->decimal_places;
$amount = app('steam')->positive($journal['amount']);
// overrule if necessary:
if ($this->convertToNative && $journalCurrencyId !== $this->nativeCurrency->id) {
$currencyId = (int)$this->nativeCurrency->id;
$currencyName = (string)$this->nativeCurrency->name;
$currencyCode = (string)$this->nativeCurrency->code;
$currencySymbol = (string)$this->nativeCurrency->symbol;
$currencyDecimalPlaces = (int)$this->nativeCurrency->decimal_places;
$convertedAmount = $converter->convert($currency, $this->nativeCurrency, $journal['date'], $amount);
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->nativeCurrency->code, $convertedAmount));
$amount = $convertedAmount;
}
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
$key = sprintf('%s-%s', $categoryName, $currencyCode);
// create arrays // create arrays
$return[$key] ??= [ $return[$key] ??= [
'label' => $categoryName, 'label' => $categoryName,
'currency_id' => (string) $currency->id, 'currency_id' => (string)$currencyId,
'currency_code' => $currency->code, 'currency_code' => $currencyCode,
'currency_name' => $currency->name, 'currency_name' => $currencyName,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currencyDecimalPlaces,
'period' => null, 'period' => null,
'start' => $start->toAtomString(), 'start' => $start->toAtomString(),
'end' => $end->toAtomString(), 'end' => $end->toAtomString(),
@@ -114,12 +140,12 @@ class CategoryController extends Controller
]; ];
// add monies // add monies
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string) $amount); $return[$key]['amount'] = bcadd($return[$key]['amount'], (string)$amount);
} }
$return = array_values($return); $return = array_values($return);
// order by amount // order by amount
usort($return, static fn (array $a, array $b) => (float) $a['amount'] < (float) $b['amount'] ? 1 : -1); usort($return, static fn (array $a, array $b) => (float)$a['amount'] < (float)$b['amount'] ? 1 : -1);
return response()->json($this->clean($return)); return response()->json($this->clean($return));
} }

View File

@@ -147,6 +147,7 @@ class ShowController extends Controller
$enrichment->setUser($admin); $enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup); $selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters); $transformer->setParameters($this->parameters);

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Jobs\SendWebhookMessage; use FireflyIII\Jobs\SendWebhookMessage;
use FireflyIII\Models\WebhookMessage; use FireflyIII\Models\WebhookMessage;
use Illuminate\Support\Facades\Log;
/** /**
* Class WebhookEventHandler * Class WebhookEventHandler
@@ -37,7 +38,13 @@ class WebhookEventHandler
*/ */
public function sendWebhookMessages(): void public function sendWebhookMessages(): void
{ {
app('log')->debug(sprintf('Now in %s', __METHOD__)); Log::debug(sprintf('Now in %s', __METHOD__));
if (false === config('firefly.feature_flags.webhooks') || false === config('firefly.allow_webhooks')) {
Log::info('Webhook event handler is disabled, do not run sendWebhookMessages().');
return;
}
// kick off the job! // kick off the job!
$messages = WebhookMessage::where('webhook_messages.sent', false) $messages = WebhookMessage::where('webhook_messages.sent', false)
->get(['webhook_messages.*']) ->get(['webhook_messages.*'])
@@ -45,14 +52,14 @@ class WebhookEventHandler
static fn (WebhookMessage $message) => $message->webhookAttempts()->count() <= 2 static fn (WebhookMessage $message) => $message->webhookAttempts()->count() <= 2
)->splice(0, 5) )->splice(0, 5)
; ;
app('log')->debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count())); Log::debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count()));
foreach ($messages as $message) { foreach ($messages as $message) {
if (false === $message->sent) { if (false === $message->sent) {
app('log')->debug(sprintf('Send message #%d', $message->id)); Log::debug(sprintf('Send message #%d', $message->id));
SendWebhookMessage::dispatch($message)->afterResponse(); SendWebhookMessage::dispatch($message)->afterResponse();
} }
if (false !== $message->sent) { if (false !== $message->sent) {
app('log')->debug(sprintf('Skip message #%d', $message->id)); Log::debug(sprintf('Skip message #%d', $message->id));
} }
} }
} }

View File

@@ -558,7 +558,6 @@ class GroupCollector implements GroupCollectorInterface
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal); $groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
} }
} }
$groups = $this->parseSums($groups); $groups = $this->parseSums($groups);
return new Collection($groups); return new Collection($groups);

View File

@@ -154,12 +154,18 @@ class TagController extends Controller
*/ */
public function index(TagRepositoryInterface $repository) public function index(TagRepositoryInterface $repository)
{ {
// start with oldest tag // start with the oldest tag
$first = session('first', today()) ?? today(); $first = session('first', today()) ?? today();
$oldestTagDate = $repository->oldestTag() instanceof Tag ? $repository->oldestTag()->date : clone $first; $oldestTagDate = $repository->oldestTag() instanceof Tag ? $repository->oldestTag()->date : clone $first;
$newestTagDate = $repository->newestTag() instanceof Tag ? $repository->newestTag()->date : today(); $newestTagDate = $repository->newestTag() instanceof Tag ? $repository->newestTag()->date : today();
$oldestTagDate->startOfYear(); $oldestTagDate->startOfYear();
$newestTagDate->endOfYear(); $newestTagDate->endOfYear();
if ($oldestTagDate->year < 1970) {
$oldestTagDate = Carbon::create(1970, 1, 1, 0, 0, 0, config('app.timezone'));
request()->session()->flash('error', trans('firefly.bad_date_transaction'));
}
$tags = []; $tags = [];
$tags['no-date'] = $repository->getTagsInYear(null); $tags['no-date'] = $repository->getTagsInYear(null);

View File

@@ -312,11 +312,23 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString())); Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString()));
return $bill->transactionJournals() return $bill->transactionJournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
->where('transactions.amount', '>', 0)
->before($end)->after($start)->get( ->before($end)->after($start)->get(
[ [
'transaction_journals.id', 'transaction_journals.id',
'transaction_journals.date', 'transaction_journals.date',
'transaction_journals.transaction_group_id', 'transaction_journals.transaction_group_id',
'transactions.transaction_currency_id',
'currency.code AS transaction_currency_code',
'currency.decimal_places AS transaction_currency_decimal_places',
'transactions.foreign_currency_id',
'foreign_currency.code AS foreign_currency_code',
'foreign_currency.decimal_places AS foreign_currency_decimal_places',
'transactions.amount',
'transactions.foreign_amount',
] ]
) )
; ;

View File

@@ -24,14 +24,16 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Budget; namespace FireflyIII\Repositories\Budget;
use Deprecated;
use Carbon\Carbon; use Carbon\Carbon;
use Deprecated;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer; use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface; use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait; use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
@@ -55,17 +57,17 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$total = '0'; $total = '0';
$count = 0; $count = 0;
foreach ($budget->budgetlimits as $limit) { foreach ($budget->budgetlimits as $limit) {
$diff = (int) $limit->start_date->diffInDays($limit->end_date, true); $diff = (int)$limit->start_date->diffInDays($limit->end_date, true);
$diff = 0 === $diff ? 1 : $diff; $diff = 0 === $diff ? 1 : $diff;
$amount = $limit->amount; $amount = $limit->amount;
$perDay = bcdiv((string) $amount, (string) $diff); $perDay = bcdiv((string)$amount, (string)$diff);
$total = bcadd($total, $perDay); $total = bcadd($total, $perDay);
++$count; ++$count;
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total)); app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
} }
$avg = $total; $avg = $total;
if ($count > 0) { if ($count > 0) {
$avg = bcdiv($total, (string) $count); $avg = bcdiv($total, (string)$count);
} }
app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg)); app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
@@ -93,9 +95,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
/** @var array $journal */ /** @var array $journal */
foreach ($journals as $journal) { foreach ($journals as $journal) {
// prep data array for currency: // prep data array for currency:
$budgetId = (int) $journal['budget_id']; $budgetId = (int)$journal['budget_id'];
$budgetName = $journal['budget_name']; $budgetName = $journal['budget_name'];
$currencyId = (int) $journal['currency_id']; $currencyId = (int)$journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId); $key = sprintf('%d-%d', $budgetId, $currencyId);
$data[$key] ??= [ $data[$key] ??= [
@@ -110,7 +112,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
'entries' => [], 'entries' => [],
]; ];
$date = $journal['date']->format($carbonFormat); $date = $journal['date']->format($carbonFormat);
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string) $journal['amount']); $data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string)$journal['amount']);
} }
return $data; return $data;
@@ -124,7 +126,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array
{ {
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
if ($accounts instanceof Collection && $accounts->count() > 0) { if ($accounts instanceof Collection && $accounts->count() > 0) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);
@@ -136,15 +138,41 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setBudgets($this->getBudgets()); $collector->setBudgets($this->getBudgets());
} }
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation(); $collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
$array = []; $array = [];
// if needs conversion to native.
$convertToNative = Amount::convertToNative($this->user);
$nativeCurrency = Amount::getNativeCurrencyByUserGroup($this->userGroup);
$currencyId = (int) $nativeCurrency->id;
$currencyCode = $nativeCurrency->code;
$currencyName = $nativeCurrency->name;
$currencySymbol = $nativeCurrency->symbol;
$currencyDecimalPlaces = $nativeCurrency->decimal_places;
$converter = new ExchangeRateConverter();
$currencies = [
$currencyId => $nativeCurrency,
];
foreach ($journals as $journal) { foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id']; $amount = app('steam')->negative($journal['amount']);
$budgetId = (int) $journal['budget_id']; $journalCurrencyId = (int)$journal['currency_id'];
$budgetName = (string) $journal['budget_name']; if (false === $convertToNative) {
$currencyId = $journalCurrencyId;
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
}
if (true === $convertToNative && $journalCurrencyId !== $currencyId) {
$currencies[$journalCurrencyId] ??= TransactionCurrency::find($journalCurrencyId);
$amount = $converter->convert($currencies[$journalCurrencyId], $nativeCurrency, $journal['date'], $amount);
}
// catch "no category" entries. $budgetId = (int)$journal['budget_id'];
$budgetName = (string)$journal['budget_name'];
// catch "no budget" entries.
if (0 === $budgetId) { if (0 === $budgetId) {
continue; continue;
} }
@@ -153,10 +181,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$array[$currencyId] ??= [ $array[$currencyId] ??= [
'budgets' => [], 'budgets' => [],
'currency_id' => $currencyId, 'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'], 'currency_name' => $currencyName,
'currency_symbol' => $journal['currency_symbol'], 'currency_symbol' => $currencySymbol,
'currency_code' => $journal['currency_code'], 'currency_code' => $currencyCode,
'currency_decimal_places' => $journal['currency_decimal_places'], 'currency_decimal_places' => $currencyDecimalPlaces,
]; ];
// info about the categories: // info about the categories:
@@ -168,9 +196,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// add journal to array: // add journal to array:
// only a subset of the fields. // only a subset of the fields.
$journalId = (int) $journal['transaction_journal_id']; $journalId = (int)$journal['transaction_journal_id'];
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [ $array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [
'amount' => app('steam')->negative($journal['amount']), 'amount' => $amount,
'destination_account_id' => $journal['destination_account_id'], 'destination_account_id' => $journal['destination_account_id'],
'destination_account_name' => $journal['destination_account_name'], 'destination_account_name' => $journal['destination_account_name'],
'source_account_id' => $journal['source_account_id'], 'source_account_id' => $journal['source_account_id'],

View File

@@ -32,6 +32,7 @@ use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use NumberFormatter; use NumberFormatter;
/** /**
@@ -66,13 +67,13 @@ class Amount
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol); $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
$fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces); $fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces); $fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
$result = (string) $fmt->format((float) $rounded); // intentional float $result = (string)$fmt->format((float)$rounded); // intentional float
if (true === $coloured) { if (true === $coloured) {
if (1 === bccomp((string) $rounded, '0')) { if (1 === bccomp((string)$rounded, '0')) {
return sprintf('<span class="text-success money-positive">%s</span>', $result); return sprintf('<span class="text-success money-positive">%s</span>', $result);
} }
if (-1 === bccomp((string) $rounded, '0')) { if (-1 === bccomp((string)$rounded, '0')) {
return sprintf('<span class="text-danger money-negative">%s</span>', $result); return sprintf('<span class="text-danger money-negative">%s</span>', $result);
} }
@@ -106,23 +107,21 @@ class Amount
$amount = $journal[$field] ?? '0'; $amount = $journal[$field] ?? '0';
// Log::debug(sprintf('Field is %s, amount is %s', $field, $amount)); // Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
// fallback, the transaction has a foreign amount in $currency. // fallback, the transaction has a foreign amount in $currency.
if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int) $journal['foreign_currency_id']) { if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
$amount = $journal['foreign_amount']; $amount = $journal['foreign_amount'];
// Log::debug(sprintf('Overruled, amount is now %s', $amount)); // Log::debug(sprintf('Overruled, amount is now %s', $amount));
} }
return (string) $amount; return (string)$amount;
} }
public function convertToNative(?User $user = null): bool public function convertToNative(?User $user = null): bool
{ {
if (!$user instanceof User) { if (!$user instanceof User) {
return true === Preferences::get('convert_to_native', false)->data && true === config('cer.enabled'); return true === Preferences::get('convert_to_native', false)->data && true === config('cer.enabled');
// Log::debug(sprintf('convertToNative [a]: %s', var_export($result, true)));
} }
return true === Preferences::getForUser($user, 'convert_to_native', false)->data && true === config('cer.enabled'); return true === Preferences::getForUser($user, 'convert_to_native', false)->data && true === config('cer.enabled');
// Log::debug(sprintf('convertToNative [b]: %s', var_export($result, true)));
} }
public function getNativeCurrency(): TransactionCurrency public function getNativeCurrency(): TransactionCurrency
@@ -180,9 +179,9 @@ class Amount
return '0'; return '0';
} }
$amount = $sourceTransaction->{$field} ?? '0'; $amount = $sourceTransaction->{$field} ?? '0';
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) { if ((int)$sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead! // use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount. $amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
} }
return $amount; return $amount;

View File

@@ -93,7 +93,7 @@ class Steam
return []; return [];
} }
$defaultCurrency = app('amount')->getNativeCurrency(); $defaultCurrency = Amount::getNativeCurrency();
if ($convertToNative) { if ($convertToNative) {
if ($defaultCurrency->id === $currency?->id) { if ($defaultCurrency->id === $currency?->id) {
Log::debug(sprintf('Unset [%s] for account #%d (no longer unset "native_balance")', $defaultCurrency->code, $account->id)); Log::debug(sprintf('Unset [%s] for account #%d (no longer unset "native_balance")', $defaultCurrency->code, $account->id));
@@ -224,7 +224,7 @@ class Steam
$request->subDay()->endOfDay(); $request->subDay()->endOfDay();
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String())); Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
$startBalance = $this->finalAccountBalance($account, $request); $startBalance = $this->finalAccountBalance($account, $request);
$nativeCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup); $nativeCurrency = Amount::getNativeCurrencyByUserGroup($account->user->userGroup);
$accountCurrency = $this->getAccountCurrency($account); $accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = $accountCurrency instanceof TransactionCurrency; $hasCurrency = $accountCurrency instanceof TransactionCurrency;
$currency = $accountCurrency ?? $nativeCurrency; $currency = $accountCurrency ?? $nativeCurrency;
@@ -294,7 +294,7 @@ class Steam
$currentBalance[$entryCurrency->code] ??= '0'; $currentBalance[$entryCurrency->code] ??= '0';
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string) $currentBalance[$entryCurrency->code]); $currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string) $currentBalance[$entryCurrency->code]);
// if not convert to native, add the amount to "balance", do nothing else. // if not requested to convert to native, add the amount to "balance", do nothing else.
if (!$convertToNative) { if (!$convertToNative) {
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay); $currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
} }
@@ -302,13 +302,13 @@ class Steam
// if there is a request to convert, convert to "native_balance" and use "balance" for whichever amount is in the native currency. // if there is a request to convert, convert to "native_balance" and use "balance" for whichever amount is in the native currency.
if ($convertToNative) { if ($convertToNative) {
$nativeSumOfDay = $converter->convert($entryCurrency, $nativeCurrency, $carbon, $sumOfDay); $nativeSumOfDay = $converter->convert($entryCurrency, $nativeCurrency, $carbon, $sumOfDay);
$currentBalance['native_balance'] = bcadd((string) $currentBalance['native_balance'], $nativeSumOfDay); $currentBalance['native_balance'] = bcadd((string) ($currentBalance['native_balance'] ?? '0'), $nativeSumOfDay);
// if it's the same currency as the entry, also add to balance (see other code).
if ($currency->id === $entryCurrency->id) { if ($currency->id === $entryCurrency->id) {
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay); $currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
} }
} }
// just set it. // add to final array.
$balances[$carbonKey] = $currentBalance; $balances[$carbonKey] = $currentBalance;
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance); Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
} }

View File

@@ -67,8 +67,8 @@ class AccountTransformer extends AbstractTransformer
} }
// get account type: // get account type:
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type)); $accountType = (string)config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type)); $liabilityType = (string)config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType); $liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
$liabilityDirection = $account->meta['liability_direction'] ?? null; $liabilityDirection = $account->meta['liability_direction'] ?? null;
@@ -89,10 +89,10 @@ class AccountTransformer extends AbstractTransformer
$native = null; $native = null;
} }
$decimalPlaces = (int) $account->meta['currency']?->decimal_places; $decimalPlaces = (int)$account->meta['currency']?->decimal_places;
$decimalPlaces = 0 === $decimalPlaces ? 2 : $decimalPlaces; $decimalPlaces = 0 === $decimalPlaces ? 2 : $decimalPlaces;
$openingBalance = Steam::bcround($openingBalance, $decimalPlaces); $openingBalanceRounded = Steam::bcround($openingBalance, $decimalPlaces);
$includeNetWorth = 1 === (int) ($account->meta['include_net_worth'] ?? 0); $includeNetWorth = 1 === (int)($account->meta['include_net_worth'] ?? 0);
$longitude = $account->meta['location']['longitude'] ?? null; $longitude = $account->meta['location']['longitude'] ?? null;
$latitude = $account->meta['location']['latitude'] ?? null; $latitude = $account->meta['location']['latitude'] ?? null;
$zoomLevel = $account->meta['location']['zoom_level'] ?? null; $zoomLevel = $account->meta['location']['zoom_level'] ?? null;
@@ -112,8 +112,55 @@ class AccountTransformer extends AbstractTransformer
$currentBalance = Steam::bcround($finalBalance['balance'] ?? '0', $decimalPlaces); $currentBalance = Steam::bcround($finalBalance['balance'] ?? '0', $decimalPlaces);
$nativeCurrentBalance = $this->convertToNative ? Steam::bcround($finalBalance['native_balance'] ?? '0', $native->decimal_places) : null; $nativeCurrentBalance = $this->convertToNative ? Steam::bcround($finalBalance['native_balance'] ?? '0', $native->decimal_places) : null;
// set up balances array:
$balances = [];
$balances[]
= [
'type' => 'current',
'amount' => $currentBalance,
'currency_id' => $account->meta['currency_id'] ?? null,
'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'date' => $date->toAtomString(),
];
if (null !== $nativeCurrentBalance) {
$balances[] = [
'type' => 'native_current',
'amount' => $nativeCurrentBalance,
'currency_id' => $native instanceof TransactionCurrency ? (string)$native->id : null,
'currency_code' => $native?->code,
'currency_symbol' => $native?->symbol,
'ccurrency_decimal_places' => $native?->decimal_places,
'date' => $date->toAtomString(),
];
}
if (null !== $openingBalance) {
$balances[] = [
'type' => 'opening',
'amount' => $openingBalanceRounded,
'currency_id' => $account->meta['currency_id'] ?? null,
'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'date' => $openingBalanceDate,
];
}
if (null !== $account->virtual_balance) {
$balances[] = [
'type' => 'virtual',
'amount' => Steam::bcround($account->virtual_balance, $decimalPlaces),
'currency_id' => $account->meta['currency_id'] ?? null,
'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'date' => $date->toAtomString(),
];
}
return [ return [
'id' => (string) $account->id, 'id' => (string)$account->id,
'created_at' => $account->created_at->toAtomString(), 'created_at' => $account->created_at->toAtomString(),
'updated_at' => $account->updated_at->toAtomString(), 'updated_at' => $account->updated_at->toAtomString(),
'active' => $account->active, 'active' => $account->active,
@@ -125,7 +172,7 @@ class AccountTransformer extends AbstractTransformer
'currency_code' => $account->meta['currency']?->code, 'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol, 'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places, 'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'native_currency_id' => $native instanceof TransactionCurrency ? (string) $native->id : null, 'native_currency_id' => $native instanceof TransactionCurrency ? (string)$native->id : null,
'native_currency_code' => $native?->code, 'native_currency_code' => $native?->code,
'native_currency_symbol' => $native?->symbol, 'native_currency_symbol' => $native?->symbol,
'native_currency_decimal_places' => $native?->decimal_places, 'native_currency_decimal_places' => $native?->decimal_places,
@@ -140,7 +187,7 @@ class AccountTransformer extends AbstractTransformer
'bic' => $account->meta['BIC'] ?? null, 'bic' => $account->meta['BIC'] ?? null,
'virtual_balance' => Steam::bcround($account->virtual_balance, $decimalPlaces), 'virtual_balance' => Steam::bcround($account->virtual_balance, $decimalPlaces),
'native_virtual_balance' => $this->convertToNative ? Steam::bcround($account->native_virtual_balance, $native->decimal_places) : null, 'native_virtual_balance' => $this->convertToNative ? Steam::bcround($account->native_virtual_balance, $native->decimal_places) : null,
'opening_balance' => $openingBalance, 'opening_balance' => $openingBalanceRounded,
'native_opening_balance' => $nativeOpeningBalance, 'native_opening_balance' => $nativeOpeningBalance,
'opening_balance_date' => $openingBalanceDate, 'opening_balance_date' => $openingBalanceDate,
'liability_type' => $liabilityType, 'liability_type' => $liabilityType,
@@ -153,6 +200,7 @@ class AccountTransformer extends AbstractTransformer
'latitude' => $latitude, 'latitude' => $latitude,
'zoom_level' => $zoomLevel, 'zoom_level' => $zoomLevel,
'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null, 'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null,
'balances' => $balances,
'links' => [ 'links' => [
[ [
'rel' => 'self', 'rel' => 'self',
@@ -165,7 +213,7 @@ class AccountTransformer extends AbstractTransformer
private function getAccountRole(Account $account, string $accountType): ?string private function getAccountRole(Account $account, string $accountType): ?string
{ {
$accountRole = $account->meta['account_role'] ?? null; $accountRole = $account->meta['account_role'] ?? null;
if ('asset' !== $accountType || '' === (string) $accountRole) { if ('asset' !== $accountType || '' === (string)$accountRole) {
return null; return null;
} }
@@ -201,7 +249,7 @@ class AccountTransformer extends AbstractTransformer
} }
$monthlyPaymentDate = $object->toAtomString(); $monthlyPaymentDate = $object->toAtomString();
} }
if (10 !== strlen((string) $monthlyPaymentDate)) { if (10 !== strlen((string)$monthlyPaymentDate)) {
$monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString(); $monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString();
} }
} }

View File

@@ -32,8 +32,10 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Models\BillDateCalculator; use FireflyIII\Support\Models\BillDateCalculator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class BillTransformer * Class BillTransformer
@@ -150,11 +152,11 @@ class BillTransformer extends AbstractTransformer
'id' => $bill->id, 'id' => $bill->id,
'created_at' => $bill->created_at->toAtomString(), 'created_at' => $bill->created_at->toAtomString(),
'updated_at' => $bill->updated_at->toAtomString(), 'updated_at' => $bill->updated_at->toAtomString(),
'currency_id' => (string) $bill->transaction_currency_id, 'currency_id' => (string)$bill->transaction_currency_id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => null === $default ? null : (string) $default->id, 'native_currency_id' => null === $default ? null : (string)$default->id,
'native_currency_code' => $default?->code, 'native_currency_code' => $default?->code,
'native_currency_symbol' => $default?->symbol, 'native_currency_symbol' => $default?->symbol,
'native_currency_decimal_places' => $default?->decimal_places, 'native_currency_decimal_places' => $default?->decimal_places,
@@ -171,7 +173,7 @@ class BillTransformer extends AbstractTransformer
'active' => $bill->active, 'active' => $bill->active,
'order' => $bill->order, 'order' => $bill->order,
'notes' => $notes, 'notes' => $notes,
'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null, 'object_group_id' => null !== $objectGroupId ? (string)$objectGroupId : null,
'object_group_order' => $objectGroupOrder, 'object_group_order' => $objectGroupOrder,
'object_group_title' => $objectGroupTitle, 'object_group_title' => $objectGroupTitle,
@@ -194,9 +196,9 @@ class BillTransformer extends AbstractTransformer
*/ */
protected function paidData(Bill $bill): array protected function paidData(Bill $bill): array
{ {
app('log')->debug(sprintf('Now in paidData for bill #%d', $bill->id)); Log::debug(sprintf('Now in paidData for bill #%d', $bill->id));
if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) { if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) {
app('log')->debug('parameters are NULL, return empty array'); Log::debug('parameters are NULL, return empty array');
return []; return [];
} }
@@ -217,27 +219,40 @@ class BillTransformer extends AbstractTransformer
$searchStart->startOfDay(); $searchStart->startOfDay();
$searchEnd->endOfDay(); $searchEnd->endOfDay();
app('log')->debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); Log::debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
app('log')->debug(sprintf('Search parameters are: start: %s', $searchStart->format('Y-m-d'))); Log::debug(sprintf('Search parameters are: start: %s', $searchStart->format('Y-m-d')));
// Get from database when bill was paid. // Get from database when bill was paid.
$set = $this->repository->getPaidDatesInRange($bill, $searchStart, $searchEnd); $set = $this->repository->getPaidDatesInRange($bill, $searchStart, $searchEnd);
app('log')->debug(sprintf('Count %d entries in getPaidDatesInRange()', $set->count())); Log::debug(sprintf('Count %d entries in getPaidDatesInRange()', $set->count()));
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date. // Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.
app('log')->debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d'))); Log::debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d')));
$lastPaidDate = $this->lastPaidDate($set, $start); $lastPaidDate = $this->lastPaidDate($set, $start);
app('log')->debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d'))); Log::debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d')));
// At this point the "next match" is exactly after the last time the bill was paid. // At this point the "next match" is exactly after the last time the bill was paid.
$result = []; $result = [];
foreach ($set as $entry) { foreach ($set as $entry) {
$result[] = [ $array = [
'transaction_group_id' => (string) $entry->transaction_group_id, 'transaction_group_id' => (string)$entry->transaction_group_id,
'transaction_journal_id' => (string) $entry->id, 'transaction_journal_id' => (string)$entry->id,
'date' => $entry->date->format('Y-m-d'), 'date' => $entry->date->format('Y-m-d'),
'date_object' => $entry->date, 'date_object' => $entry->date,
'currency_id' => $entry->transaction_currency_id,
'currency_code' => $entry->transaction_currency_code,
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
]; ];
if (null !== $entry->foreign_amount && null !== $entry->foreign_currency_code) {
$array['foreign_currency_id'] = $entry->foreign_currency_id;
$array['foreign_currency_code'] = $entry->foreign_currency_code;
$array['foreign_currency_decimal_places'] = $entry->foreign_currency_decimal_places;
$array['foreign_amount'] = Steam::bcround($entry->foreign_amount, $entry->foreign_currency_decimal_places);
}
$result[] = $array;
} }
return $result; return $result;
@@ -265,7 +280,7 @@ class BillTransformer extends AbstractTransformer
private function getLastPaidDate(array $paidData): ?Carbon private function getLastPaidDate(array $paidData): ?Carbon
{ {
app('log')->debug('getLastPaidDate()'); Log::debug('getLastPaidDate()');
$return = null; $return = null;
foreach ($paidData as $entry) { foreach ($paidData as $entry) {
if (null !== $return) { if (null !== $return) {
@@ -274,15 +289,15 @@ class BillTransformer extends AbstractTransformer
if ($current->gt($return)) { if ($current->gt($return)) {
$return = clone $current; $return = clone $current;
} }
app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
} }
if (null === $return) { if (null === $return) {
/** @var Carbon $return */ /** @var Carbon $return */
$return = $entry['date_object']; $return = $entry['date_object'];
app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
} }
} }
app('log')->debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d'))); Log::debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d')));
return $return; return $return;
} }

View File

@@ -121,6 +121,15 @@ class TransactionGroupTransformer extends AbstractTransformer
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string) $transaction['foreign_amount'])) { if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string) $transaction['foreign_amount'])) {
$foreignAmount = app('steam')->positive($transaction['foreign_amount']); $foreignAmount = app('steam')->positive($transaction['foreign_amount']);
} }
// set native amount to the normal amount if the currency matches.
if ($transaction['native_currency']['id'] ?? null === $transaction['currency_id']) {
$transaction['native_amount'] = $amount;
}
if (array_key_exists('native_amount', $transaction) && null !== $transaction['native_amount']) {
$transaction['native_amount'] = app('steam')->positive($transaction['native_amount']);
}
$type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value); $type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value);
// must be 0 (int) or NULL // must be 0 (int) or NULL

137
composer.lock generated
View File

@@ -1879,16 +1879,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v12.20.0", "version": "v12.21.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "1b9a00f8caf5503c92aa436279172beae1a484ff" "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/1b9a00f8caf5503c92aa436279172beae1a484ff", "url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
"reference": "1b9a00f8caf5503c92aa436279172beae1a484ff", "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2090,7 +2090,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2025-07-08T15:02:21+00:00" "time": "2025-07-22T15:41:55+00:00"
}, },
{ {
"name": "laravel/passport", "name": "laravel/passport",
@@ -2229,16 +2229,16 @@
}, },
{ {
"name": "laravel/sanctum", "name": "laravel/sanctum",
"version": "v4.1.2", "version": "v4.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/sanctum.git", "url": "https://github.com/laravel/sanctum.git",
"reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491" "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/e4c09e69aecd5a383e0c1b85a6bb501c997d7491", "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
"reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491", "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2289,7 +2289,7 @@
"issues": "https://github.com/laravel/sanctum/issues", "issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum" "source": "https://github.com/laravel/sanctum"
}, },
"time": "2025-07-01T15:49:32+00:00" "time": "2025-07-09T19:45:24+00:00"
}, },
{ {
"name": "laravel/serializable-closure", "name": "laravel/serializable-closure",
@@ -2619,16 +2619,16 @@
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "2.7.0", "version": "2.7.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/commonmark.git", "url": "https://github.com/thephpleague/commonmark.git",
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" "reference": "10732241927d3971d28e7ea7b5712721fa2296ca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca",
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", "reference": "10732241927d3971d28e7ea7b5712721fa2296ca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2657,7 +2657,7 @@
"symfony/process": "^5.4 | ^6.0 | ^7.0", "symfony/process": "^5.4 | ^6.0 | ^7.0",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
"unleashedtech/php-coding-standard": "^3.1.1", "unleashedtech/php-coding-standard": "^3.1.1",
"vimeo/psalm": "^4.24.0 || ^5.0.0" "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
}, },
"suggest": { "suggest": {
"symfony/yaml": "v2.3+ required if using the Front Matter extension" "symfony/yaml": "v2.3+ required if using the Front Matter extension"
@@ -2722,7 +2722,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-05-05T12:20:28+00:00" "time": "2025-07-20T12:47:49+00:00"
}, },
{ {
"name": "league/config", "name": "league/config",
@@ -5101,16 +5101,16 @@
}, },
{ {
"name": "predis/predis", "name": "predis/predis",
"version": "v3.0.1", "version": "v3.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/predis/predis.git", "url": "https://github.com/predis/predis.git",
"reference": "34fb0a7da0330df1bab4280fcac4afdeeccc3edf" "reference": "202e0c5322b906ec4c761c0cefebad6d0959a699"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/predis/predis/zipball/34fb0a7da0330df1bab4280fcac4afdeeccc3edf", "url": "https://api.github.com/repos/predis/predis/zipball/202e0c5322b906ec4c761c0cefebad6d0959a699",
"reference": "34fb0a7da0330df1bab4280fcac4afdeeccc3edf", "reference": "202e0c5322b906ec4c761c0cefebad6d0959a699",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -5152,7 +5152,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/predis/predis/issues", "issues": "https://github.com/predis/predis/issues",
"source": "https://github.com/predis/predis/tree/v3.0.1" "source": "https://github.com/predis/predis/tree/v3.1.0"
}, },
"funding": [ "funding": [
{ {
@@ -5160,7 +5160,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-05-16T18:30:32+00:00" "time": "2025-07-22T15:37:44+00:00"
}, },
{ {
"name": "psr/cache", "name": "psr/cache",
@@ -9840,16 +9840,16 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "barryvdh/laravel-debugbar", "name": "barryvdh/laravel-debugbar",
"version": "v3.15.4", "version": "v3.16.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git", "url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106" "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106", "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f265cf5e38577d42311f1a90d619bcd3740bea23",
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106", "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -9857,7 +9857,7 @@
"illuminate/session": "^9|^10|^11|^12", "illuminate/session": "^9|^10|^11|^12",
"illuminate/support": "^9|^10|^11|^12", "illuminate/support": "^9|^10|^11|^12",
"php": "^8.1", "php": "^8.1",
"php-debugbar/php-debugbar": "~2.1.1", "php-debugbar/php-debugbar": "~2.2.0",
"symfony/finder": "^6|^7" "symfony/finder": "^6|^7"
}, },
"require-dev": { "require-dev": {
@@ -9877,7 +9877,7 @@
] ]
}, },
"branch-alias": { "branch-alias": {
"dev-master": "3.15-dev" "dev-master": "3.16-dev"
} }
}, },
"autoload": { "autoload": {
@@ -9909,7 +9909,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues", "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4" "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.0"
}, },
"funding": [ "funding": [
{ {
@@ -9921,7 +9921,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-04-16T06:32:06+00:00" "time": "2025-07-14T11:56:43+00:00"
}, },
{ {
"name": "barryvdh/laravel-ide-helper", "name": "barryvdh/laravel-ide-helper",
@@ -10768,16 +10768,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v5.5.0", "version": "v5.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9" "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -10820,9 +10820,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0"
}, },
"time": "2025-05-31T08:24:38+00:00" "time": "2025-07-27T20:03:57+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@@ -10944,16 +10944,16 @@
}, },
{ {
"name": "php-debugbar/php-debugbar", "name": "php-debugbar/php-debugbar",
"version": "v2.1.6", "version": "v2.2.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-debugbar/php-debugbar.git", "url": "https://github.com/php-debugbar/php-debugbar.git",
"reference": "16fa68da5617220594aa5e33fa9de415f94784a0" "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/16fa68da5617220594aa5e33fa9de415f94784a0", "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35",
"reference": "16fa68da5617220594aa5e33fa9de415f94784a0", "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -10961,6 +10961,9 @@
"psr/log": "^1|^2|^3", "psr/log": "^1|^2|^3",
"symfony/var-dumper": "^4|^5|^6|^7" "symfony/var-dumper": "^4|^5|^6|^7"
}, },
"replace": {
"maximebf/debugbar": "self.version"
},
"require-dev": { "require-dev": {
"dbrekelmans/bdi": "^1", "dbrekelmans/bdi": "^1",
"phpunit/phpunit": "^8|^9", "phpunit/phpunit": "^8|^9",
@@ -10975,7 +10978,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.0-dev" "dev-master": "2.1-dev"
} }
}, },
"autoload": { "autoload": {
@@ -11008,9 +11011,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/php-debugbar/php-debugbar/issues", "issues": "https://github.com/php-debugbar/php-debugbar/issues",
"source": "https://github.com/php-debugbar/php-debugbar/tree/v2.1.6" "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4"
}, },
"time": "2025-02-21T17:47:03+00:00" "time": "2025-07-22T14:01:30+00:00"
}, },
{ {
"name": "phpstan/extension-installer", "name": "phpstan/extension-installer",
@@ -11062,16 +11065,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "2.1.18", "version": "2.1.21",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7" "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/ee1f390b7a70cdf74a2b737e554f68afea885db7", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
"reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7", "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -11116,7 +11119,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-07-17T17:22:31+00:00" "time": "2025-07-28T19:35:08+00:00"
}, },
{ {
"name": "phpstan/phpstan-deprecation-rules", "name": "phpstan/phpstan-deprecation-rules",
@@ -11167,16 +11170,16 @@
}, },
{ {
"name": "phpstan/phpstan-strict-rules", "name": "phpstan/phpstan-strict-rules",
"version": "2.0.4", "version": "2.0.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git", "url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a" "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094",
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -11209,22 +11212,22 @@
"description": "Extra strict and opinionated rules for PHPStan", "description": "Extra strict and opinionated rules for PHPStan",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4" "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6"
}, },
"time": "2025-03-18T11:42:40+00:00" "time": "2025-07-21T12:19:29+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "12.3.1", "version": "12.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "ddec29dfc128eba9c204389960f2063f3b7fa170" "reference": "086553c5b2e0e1e20293d782d788ab768202b621"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ddec29dfc128eba9c204389960f2063f3b7fa170", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/086553c5b2e0e1e20293d782d788ab768202b621",
"reference": "ddec29dfc128eba9c204389960f2063f3b7fa170", "reference": "086553c5b2e0e1e20293d782d788ab768202b621",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -11280,7 +11283,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.1" "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -11300,7 +11303,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-06-18T08:58:13+00:00" "time": "2025-07-29T06:19:24+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@@ -11549,16 +11552,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "12.2.7", "version": "12.2.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "8b1348b254e5959acaf1539c6bd790515fb49414" "reference": "49dc7c0669b97c7a36f63411635f48eedc140eb4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8b1348b254e5959acaf1539c6bd790515fb49414", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49dc7c0669b97c7a36f63411635f48eedc140eb4",
"reference": "8b1348b254e5959acaf1539c6bd790515fb49414", "reference": "49dc7c0669b97c7a36f63411635f48eedc140eb4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -11572,7 +11575,7 @@
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1", "phar-io/version": "^3.2.1",
"php": ">=8.3", "php": ">=8.3",
"phpunit/php-code-coverage": "^12.3.1", "phpunit/php-code-coverage": "^12.3.2",
"phpunit/php-file-iterator": "^6.0.0", "phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0", "phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0", "phpunit/php-text-template": "^5.0.0",
@@ -11626,7 +11629,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.7" "source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.8"
}, },
"funding": [ "funding": [
{ {
@@ -11650,7 +11653,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-07-11T04:11:13+00:00" "time": "2025-07-30T05:58:18+00:00"
}, },
{ {
"name": "rector/rector", "name": "rector/rector",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false), 'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag. // see cer.php for exchange rates feature flag.
], ],
'version' => 'develop/2025-07-20', 'version' => 'develop/2025-07-30',
'build_time' => 1753007724, 'build_time' => 1753878970,
'api_version' => '2.1.0', // field is no longer used. 'api_version' => '2.1.0', // field is no longer used.
'db_version' => 26, 'db_version' => 26,

View File

@@ -65,6 +65,7 @@ return [
'interest_calc_half-year', 'interest_calc_half-year',
'interest_calc_quarterly', 'interest_calc_quarterly',
'spent', 'spent',
'budgeted',
'administration_owner', 'administration_owner',
'administration_you', 'administration_you',
'administration_role_owner', 'administration_role_owner',

326
package-lock.json generated
View File

@@ -402,14 +402,14 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.27.6", "version": "7.28.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/types": "^7.27.6" "@babel/types": "^7.28.2"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -1622,9 +1622,9 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.27.6", "version": "7.28.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -1665,9 +1665,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.1", "version": "7.28.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1699,6 +1699,13 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true,
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.8", "version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
@@ -2142,9 +2149,9 @@
} }
}, },
"node_modules/@fortawesome/fontawesome-free": { "node_modules/@fortawesome/fontawesome-free": {
"version": "6.7.2", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz",
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==", "integrity": "sha512-X48nISrSOa89zu2VMljC4XaRf8NmgTwQBVHfS2Nu5G00ZwM31oOVrAtGxZF3b6wDYf9lJsf/Eq4cCSFKIkOWPQ==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -2585,9 +2592,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
"integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2599,9 +2606,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
"integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2613,9 +2620,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
"integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2627,9 +2634,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
"integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2641,9 +2648,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
"integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2655,9 +2662,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
"integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2669,9 +2676,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
"integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2683,9 +2690,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
"integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2697,9 +2704,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
"integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2711,9 +2718,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
"integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2725,9 +2732,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
"integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -2738,10 +2745,10 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
"integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -2753,9 +2760,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
"integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -2767,9 +2774,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
"integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -2781,9 +2788,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
"integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -2795,9 +2802,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
"integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2809,9 +2816,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
"integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2823,9 +2830,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
"integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2837,9 +2844,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
"integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -2851,9 +2858,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
"integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -3141,9 +3148,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.0.15", "version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
"integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==", "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3249,42 +3256,42 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.17", "version": "3.5.18",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
"integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.27.5", "@babel/parser": "^7.28.0",
"@vue/shared": "3.5.17", "@vue/shared": "3.5.18",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.17", "version": "3.5.18",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz",
"integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.5.17", "@vue/compiler-core": "3.5.18",
"@vue/shared": "3.5.17" "@vue/shared": "3.5.18"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.5.17", "version": "3.5.18",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz",
"integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.27.5", "@babel/parser": "^7.28.0",
"@vue/compiler-core": "3.5.17", "@vue/compiler-core": "3.5.18",
"@vue/compiler-dom": "3.5.17", "@vue/compiler-dom": "3.5.18",
"@vue/compiler-ssr": "3.5.17", "@vue/compiler-ssr": "3.5.18",
"@vue/shared": "3.5.17", "@vue/shared": "3.5.18",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"postcss": "^8.5.6", "postcss": "^8.5.6",
@@ -3292,14 +3299,14 @@
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.5.17", "version": "3.5.18",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz",
"integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.17", "@vue/compiler-dom": "3.5.18",
"@vue/shared": "3.5.17" "@vue/shared": "3.5.18"
} }
}, },
"node_modules/@vue/component-compiler-utils": { "node_modules/@vue/component-compiler-utils": {
@@ -3381,9 +3388,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.17", "version": "3.5.18",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz",
"integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -3931,14 +3938,14 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.10.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
@@ -4479,9 +4486,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001727", "version": "1.0.30001731",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -5018,22 +5025,21 @@
} }
}, },
"node_modules/cross-env": { "node_modules/cross-env": {
"version": "7.0.3", "version": "10.0.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.1" "@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
}, },
"bin": { "bin": {
"cross-env": "src/bin/cross-env.js", "cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js" "cross-env-shell": "dist/bin/cross-env-shell.js"
}, },
"engines": { "engines": {
"node": ">=10.14", "node": ">=20"
"npm": ">=6",
"yarn": ">=1"
} }
}, },
"node_modules/cross-fetch": { "node_modules/cross-fetch": {
@@ -5694,9 +5700,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.187", "version": "1.5.192",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz",
"integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -7832,14 +7838,14 @@
} }
}, },
"node_modules/launch-editor": { "node_modules/launch-editor": {
"version": "2.10.0", "version": "2.11.0",
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.0.tgz",
"integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", "integrity": "sha512-R/PIF14L6e2eHkhvQPu7jDRCr0msfCYCxbYiLgkkAGi0dVPWuM+RrsPu0a5dpuNe0KWGL3jpAkOlv53xGfPheQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"picocolors": "^1.0.0", "picocolors": "^1.1.1",
"shell-quote": "^1.8.1" "shell-quote": "^1.8.3"
} }
}, },
"node_modules/leaflet": { "node_modules/leaflet": {
@@ -10117,9 +10123,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.45.1", "version": "4.46.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
"integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -10133,26 +10139,26 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm-eabi": "4.46.2",
"@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-android-arm64": "4.46.2",
"@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.46.2",
"@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-darwin-x64": "4.46.2",
"@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.46.2",
"@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.46.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
"@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.46.2",
"@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.46.2",
"@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.46.2",
"@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-ppc64-gnu": "4.46.2",
"@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.46.2",
"@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.46.2",
"@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.46.2",
"@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.46.2",
"@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.46.2",
"@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.46.2",
"@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.46.2",
"@rollup/rollup-win32-x64-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.46.2",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@@ -11518,15 +11524,15 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.0.5", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
"integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==", "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.6", "fdir": "^6.4.6",
"picomatch": "^4.0.2", "picomatch": "^4.0.3",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"rollup": "^4.40.0", "rollup": "^4.40.0",
"tinyglobby": "^0.2.14" "tinyglobby": "^0.2.14"
@@ -11838,9 +11844,9 @@
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.100.2", "version": "5.101.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.0.tgz",
"integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==", "integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -12502,9 +12508,9 @@
"devDependencies": { "devDependencies": {
"@johmun/vue-tags-input": "^2", "@johmun/vue-tags-input": "^2",
"@vue/compiler-sfc": "^3.5.11", "@vue/compiler-sfc": "^3.5.11",
"axios": "^1.8", "axios": "^1.11",
"bootstrap-sass": "^3", "bootstrap-sass": "^3",
"cross-env": "^7.0", "cross-env": "^10.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"jquery": "^3", "jquery": "^3",
"laravel-mix": "^6.0", "laravel-mix": "^6.0",
@@ -12519,7 +12525,7 @@
"resources/assets/v2": { "resources/assets/v2": {
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0", "@fortawesome/fontawesome-free": "^7",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"admin-lte": "^4.0.0-rc4", "admin-lte": "^4.0.0-rc4",
"alpinejs": "^3.13.7", "alpinejs": "^3.13.7",

View File

@@ -16,9 +16,9 @@
"devDependencies": { "devDependencies": {
"@johmun/vue-tags-input": "^2", "@johmun/vue-tags-input": "^2",
"@vue/compiler-sfc": "^3.5.11", "@vue/compiler-sfc": "^3.5.11",
"axios": "^1.8", "axios": "^1.11",
"bootstrap-sass": "^3", "bootstrap-sass": "^3",
"cross-env": "^7.0", "cross-env": "^10.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"jquery": "^3", "jquery": "^3",
"laravel-mix": "^6.0", "laravel-mix": "^6.0",

View File

@@ -1,11 +1,11 @@
{ {
"firefly": { "firefly": {
"administrations_page_title": "Financial administrations", "administrations_page_title": "Spr\u00e1va financ\u00ed",
"administrations_index_menu": "Financial administrations", "administrations_index_menu": "Spr\u00e1va financ\u00ed",
"expires_at": "Expires at", "expires_at": "Expires at",
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its native currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.", "temp_administrations_introduction": "Firefly III brzy z\u00edsk\u00e1 mo\u017enost spravovat v\u00edce spr\u00e1v financ\u00ed. Moment\u00e1ln\u011b m\u00e1te pouze jednu. M\u016f\u017eete nastavit n\u00e1zev t\u00e9to spr\u00e1vy a jej\u00ed m\u00edstn\u00ed m\u011bnu. Toto nahrazuje p\u0159edchoz\u00ed nastaven\u00ed, kde jste nastavovali \u201ev\u00fdchoz\u00ed m\u011bnu\u201c. Toto nastaven\u00ed je nyn\u00ed v\u00e1z\u00e1no na spr\u00e1vu financ\u00ed a m\u016f\u017ee se li\u0161it pro ka\u017edou spr\u00e1vu.",
"administration_currency_form_help": "It may take a long time for the page to load if you change the native currency because transaction may need to be converted to your (new) native currency.", "administration_currency_form_help": "Pokud zm\u011bn\u00edte m\u00edstn\u00ed m\u011bnu, m\u016f\u017ee na\u010dten\u00ed str\u00e1nky trvat d\u00e9le, proto\u017ee transakce mo\u017en\u00e1 bude pot\u0159eba p\u0159ev\u00e9st na va\u0161i (novou) m\u00edstn\u00ed m\u011bnu.",
"administrations_page_edit_sub_title_js": "Edit financial administration \"{title}\"", "administrations_page_edit_sub_title_js": "Upravit spr\u00e1vu financ\u00ed \u201e{title}\u201c",
"table": "Tabulka", "table": "Tabulka",
"welcome_back": "Jak to jde?", "welcome_back": "Jak to jde?",
"flash_error": "Chyba!", "flash_error": "Chyba!",
@@ -20,14 +20,14 @@
"split": "Rozd\u011blit", "split": "Rozd\u011blit",
"single_split": "Rozd\u011blit", "single_split": "Rozd\u011blit",
"not_enough_currencies": "Not enough currencies", "not_enough_currencies": "Not enough currencies",
"not_enough_currencies_enabled": "If you have just one currency enabled, there is no need to add exchange rates.", "not_enough_currencies_enabled": "Pokud m\u00e1te povolenou pouze jednu m\u011bnu, nemus\u00edte p\u0159id\u00e1vat sm\u011bnn\u00e9 kurzy.",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakce #{ID} (\"{title}\")<\/a> byla ulo\u017eena.", "transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakce #{ID} (\"{title}\")<\/a> byla ulo\u017eena.",
"webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID} (\"{title}\")<\/a> byl ulo\u017een.", "webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID} (\"{title}\")<\/a> byl ulo\u017een.",
"webhook_updated_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID}<\/a> (\"{title}\") byl aktualizov\u00e1n.", "webhook_updated_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID}<\/a> (\"{title}\") byl aktualizov\u00e1n.",
"transaction_updated_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID}<\/a> (\"{title}\") has been updated.", "transaction_updated_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID}<\/a> (\"{title}\") has been updated.",
"transaction_new_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakce #{ID}<\/a> byla ulo\u017eena.", "transaction_new_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakce #{ID}<\/a> byla ulo\u017eena.",
"transaction_journal_information": "Informace o transakci", "transaction_journal_information": "Informace o transakci",
"submission_options": "Submission options", "submission_options": "Mo\u017enosti polo\u017eky",
"apply_rules_checkbox": "Aplikovat pravidla", "apply_rules_checkbox": "Aplikovat pravidla",
"fire_webhooks_checkbox": "Spustit webhooky", "fire_webhooks_checkbox": "Spustit webhooky",
"no_budget_pointer": "<a href=\"budgets\">Zde<\/a> si m\u016f\u017eete spravovat rozpo\u010dty, kter\u00e9 v\u00e1m mohou pomoci sledovat v\u00fddaje.", "no_budget_pointer": "<a href=\"budgets\">Zde<\/a> si m\u016f\u017eete spravovat rozpo\u010dty, kter\u00e9 v\u00e1m mohou pomoci sledovat v\u00fddaje.",
@@ -36,7 +36,7 @@
"hidden_fields_preferences": "Dal\u0161\u00ed vlastnosti transakc\u00ed m\u016f\u017eete zaznamenat kdy\u017e je zapnete v <a href=\"preferences\">nastaven\u00ed<\/a>.", "hidden_fields_preferences": "Dal\u0161\u00ed vlastnosti transakc\u00ed m\u016f\u017eete zaznamenat kdy\u017e je zapnete v <a href=\"preferences\">nastaven\u00ed<\/a>.",
"destination_account": "C\u00edlov\u00fd \u00fa\u010det", "destination_account": "C\u00edlov\u00fd \u00fa\u010det",
"add_another_split": "P\u0159idat dal\u0161\u00ed roz\u00fa\u010dtov\u00e1n\u00ed", "add_another_split": "P\u0159idat dal\u0161\u00ed roz\u00fa\u010dtov\u00e1n\u00ed",
"submission": "Submission", "submission": "Polo\u017eka",
"stored_journal": "\u00dasp\u011b\u0161n\u011b vytvo\u0159ena nov\u00e1 transakce \u201e:description\u201c", "stored_journal": "\u00dasp\u011b\u0161n\u011b vytvo\u0159ena nov\u00e1 transakce \u201e:description\u201c",
"create_another": "Po ulo\u017een\u00ed se vr\u00e1tit sem pro vytvo\u0159en\u00ed dal\u0161\u00ed.", "create_another": "Po ulo\u017een\u00ed se vr\u00e1tit sem pro vytvo\u0159en\u00ed dal\u0161\u00ed.",
"reset_after": "Po odesl\u00e1n\u00ed vymazat obsah formul\u00e1\u0159e", "reset_after": "Po odesl\u00e1n\u00ed vymazat obsah formul\u00e1\u0159e",
@@ -75,24 +75,24 @@
"profile_oauth_clients": "Klienti OAuth", "profile_oauth_clients": "Klienti OAuth",
"profile_oauth_no_clients": "Zat\u00edm jste nevytvo\u0159ili OAuth klienty.", "profile_oauth_no_clients": "Zat\u00edm jste nevytvo\u0159ili OAuth klienty.",
"profile_oauth_clients_header": "Klienti", "profile_oauth_clients_header": "Klienti",
"profile_oauth_client_id": "ID z\u00e1kazn\u00edka", "profile_oauth_client_id": "ID klienta",
"profile_oauth_client_name": "Jm\u00e9no", "profile_oauth_client_name": "Jm\u00e9no",
"profile_oauth_client_secret": "Tajn\u00fd kl\u00ed\u010d", "profile_oauth_client_secret": "Tajn\u00e9",
"profile_oauth_create_new_client": "Vytvo\u0159it nov\u00e9ho klienta", "profile_oauth_create_new_client": "Vytvo\u0159it nov\u00e9ho klienta",
"profile_oauth_create_client": "Vytvo\u0159it klienta", "profile_oauth_create_client": "Vytvo\u0159it klienta",
"profile_oauth_edit_client": "Upravit klienta", "profile_oauth_edit_client": "Upravit klienta",
"profile_oauth_name_help": "N\u011bco \u010demu va\u0161i u\u017eivatel\u00e9 budou d\u016fv\u011b\u0159ovat.", "profile_oauth_name_help": "N\u011bco \u010demu va\u0161i u\u017eivatel\u00e9 budou d\u016fv\u011b\u0159ovat.",
"profile_oauth_redirect_url": "P\u0159esm\u011brovat URL adresu", "profile_oauth_redirect_url": "P\u0159esm\u011brovat URL adresu",
"profile_oauth_clients_external_auth": "Pokud pro ov\u011b\u0159ov\u00e1n\u00ed pou\u017e\u00edv\u00e1te extern\u00ed slu\u017ebu, nap\u0159\u00edklad Authelia, OAuth klienti nemus\u00ed fungovat spr\u00e1vn\u011b. M\u00edsto toho m\u016f\u017eete pou\u017e\u00edt Personal Access Token.", "profile_oauth_clients_external_auth": "Pokud pro ov\u011b\u0159ov\u00e1n\u00ed pou\u017e\u00edv\u00e1te extern\u00ed slu\u017ebu, nap\u0159\u00edklad Authelia, OAuth klienti nemus\u00ed fungovat spr\u00e1vn\u011b. M\u00edsto toho m\u016f\u017eete pou\u017e\u00edt osobn\u00ed p\u0159\u00edstupov\u00fd token.",
"profile_oauth_redirect_url_help": "Callback URL va\u0161\u00ed aplikace.", "profile_oauth_redirect_url_help": "Callback URL va\u0161\u00ed aplikace.",
"profile_authorized_apps": "Authorized applications", "profile_authorized_apps": "Authorized applications",
"profile_authorized_clients": "Autorizovan\u00ed klienti", "profile_authorized_clients": "Autorizovan\u00ed klienti",
"profile_scopes": "Scopes", "profile_scopes": "Scopes",
"profile_revoke": "Revoke", "profile_revoke": "Revoke",
"profile_personal_access_tokens": "Personal Access Token", "profile_personal_access_tokens": "Osobn\u00ed p\u0159\u00edstupov\u00fd token",
"profile_personal_access_token": "Personal Access Token", "profile_personal_access_token": "Osobn\u00ed p\u0159\u00edstupov\u00e9 tokeny",
"profile_personal_access_token_explanation": "Tohle je v\u00e1\u0161 nov\u00fd p\u0159\u00edstupov\u00fd token. Tohle je naposled kdy ho vid\u00edte, tak\u017ee ho neztra\u0165te! M\u016f\u017eete ho pou\u017e\u00edt pro vol\u00e1n\u00ed API.", "profile_personal_access_token_explanation": "Zde je v\u00e1\u0161 nov\u00fd osobn\u00ed p\u0159\u00edstupov\u00fd token. Toto je jedin\u00fd okam\u017eik, kdy jej uvid\u00edte, tak\u017ee ho neztra\u0165te! Nyn\u00ed m\u016f\u017eete tento token pou\u017e\u00edt k odes\u00edl\u00e1n\u00ed po\u017eadavk\u016f na API.",
"profile_no_personal_access_token": "Je\u0161t\u011b jste nevytvo\u0159ili \u017e\u00e1dn\u00e9 p\u0159\u00edstupov\u00e9 tokeny.", "profile_no_personal_access_token": "Zat\u00edm jste nevytvo\u0159ili \u017e\u00e1dn\u00e9 osobn\u00ed p\u0159\u00edstupov\u00e9 tokeny.",
"profile_create_new_token": "Vytvo\u0159it nov\u00fd token", "profile_create_new_token": "Vytvo\u0159it nov\u00fd token",
"profile_create_token": "Vytvo\u0159it token", "profile_create_token": "Vytvo\u0159it token",
"profile_create": "Vytvo\u0159it", "profile_create": "Vytvo\u0159it",
@@ -140,12 +140,12 @@
"response": "Odpov\u011b\u010f", "response": "Odpov\u011b\u010f",
"visit_webhook_url": "Nav\u0161t\u00edvit URL webhooku", "visit_webhook_url": "Nav\u0161t\u00edvit URL webhooku",
"reset_webhook_secret": "Restartovat tajn\u00fd kl\u00ed\u010d webhooku", "reset_webhook_secret": "Restartovat tajn\u00fd kl\u00ed\u010d webhooku",
"header_exchange_rates": "Exchange rates", "header_exchange_rates": "Sm\u011bnn\u00e9 kurzy",
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">the documentation<\/a>.", "exchange_rates_intro": "Firefly III podporuje stahov\u00e1n\u00ed a pou\u017e\u00edv\u00e1n\u00ed sm\u011bnn\u00fdch kurz\u016f. V\u00edce informac\u00ed o t\u00e9to funkci najdete <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">v dokumentaci<\/a>.",
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)", "exchange_rates_from_to": "Mezi {from} a {to} (a opa\u010dn\u011b)",
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.", "exchange_rates_intro_rates": "Firefly III pou\u017e\u00edv\u00e1 n\u00e1sleduj\u00edc\u00ed sm\u011bnn\u00e9 kurzy. Inverzn\u00ed kurz je automaticky vypo\u010d\u00edt\u00e1n, pokud nen\u00ed zad\u00e1n. Pokud pro datum transakce neexistuje sm\u011bnn\u00fd kurz, Firefly III se vr\u00e1t\u00ed zp\u011bt v \u010dase a pokus\u00ed se n\u011bjak\u00fd naj\u00edt. Pokud \u017e\u00e1dn\u00fd nenajde, pou\u017eije se kurz \u201e1\u201c.",
"header_exchange_rates_rates": "Exchange rates", "header_exchange_rates_rates": "Sm\u011bnn\u00e9 kurzy",
"header_exchange_rates_table": "Table with exchange rates", "header_exchange_rates_table": "Tabulka se sm\u011bnn\u00fdmi kurzy",
"help_rate_form": "On this day, how many {to} will you get for one {from}?", "help_rate_form": "On this day, how many {to} will you get for one {from}?",
"add_new_rate": "Add a new exchange rate", "add_new_rate": "Add a new exchange rate",
"save_new_rate": "Save new rate" "save_new_rate": "Save new rate"
@@ -154,7 +154,7 @@
"url": "URL", "url": "URL",
"active": "Aktivn\u00ed", "active": "Aktivn\u00ed",
"interest_date": "\u00darokov\u00e9 datum", "interest_date": "\u00darokov\u00e9 datum",
"administration_currency": "Native currency", "administration_currency": "M\u00edstn\u00ed m\u011bna",
"title": "N\u00e1zev", "title": "N\u00e1zev",
"date": "Datum", "date": "Datum",
"book_date": "Datum rezervace", "book_date": "Datum rezervace",

View File

@@ -16,7 +16,7 @@
"vite-plugin-manifest-sri": "^0.2.0" "vite-plugin-manifest-sri": "^0.2.0"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0", "@fortawesome/fontawesome-free": "^7",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"admin-lte": "^4.0.0-rc4", "admin-lte": "^4.0.0-rc4",
"alpinejs": "^3.13.7", "alpinejs": "^3.13.7",

View File

@@ -25,7 +25,7 @@ export default class Dashboard {
dashboard(start, end) { dashboard(start, end) {
let startStr = format(start, 'y-MM-dd'); let startStr = format(start, 'y-MM-dd');
let endStr = format(end, 'y-MM-dd'); let endStr = format(end, 'y-MM-dd');
return api.get('/api/v1/chart/account/dashboard', {params: {fix: true, start: startStr, end: endStr}}); return api.get('/api/v1/chart/account/dashboard', {params: {start: startStr, end: endStr}});
} }
expense(start, end) { expense(start, end) {

View File

@@ -315,7 +315,7 @@ let index = function () {
// need to find the input thing // need to find the input thing
console.log('Clicked edit button for account on index #' + index + ' and field ' + fieldName); console.log('Clicked edit button for account on index #' + index + ' and field ' + fieldName);
const querySelector = 'input[data-field="' + fieldName + '"][data-index="' + index + '"]'; const querySelector = 'input[data-field="' + fieldName + '"][data-index="' + index + '"]';
console.log(querySelector); // console.log(querySelector);
const newValue = document.querySelectorAll(querySelector)[0].value ?? ''; const newValue = document.querySelectorAll(querySelector)[0].value ?? '';
if ('' === newValue) { if ('' === newValue) {
return; return;
@@ -352,12 +352,15 @@ let index = function () {
// filter instructions // filter instructions
let filters = {}; let filters = {};
let type = this.filters.type;
let active = this.filters.active;
for (let k in this.filters) { for (let k in this.filters) {
if (this.filters.hasOwnProperty(k) && null !== this.filters[k]) { if (this.filters.hasOwnProperty(k) && null !== this.filters[k]) {
filters[k] = this.filters[k]; filters[k] = this.filters[k];
//filters.push({column: k, filter: this.filters[k]}); //filters.push({column: k, filter: this.filters[k]});
} }
} }
delete filters.type;
// get start and end from the store: // get start and end from the store:
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
@@ -367,24 +370,23 @@ let index = function () {
let params = { let params = {
sort: sorting, sort: sorting,
filter: filters, filter: filters,
active: active,
currentMoment: today, currentMoment: today,
// type: type, type: type,
page: {number: this.page}, page: this.page,
startPeriod: start, startPeriod: start,
endPeriod: end endPeriod: end
}; };
if (!this.tableColumns.balance_difference.enabled) { if (!this.tableColumns.balance_difference.enabled) {
delete params.startPeriod; delete params.startPeriod;
delete params.enPeriod; delete params.endPeriod;
} }
this.accounts = []; this.accounts = [];
let groupedAccounts = {}; let groupedAccounts = {};
// one page only.o // one page only.o
(new Get()).index(params).then(response => { (new Get()).index(params).then(response => {
console.log(response);
this.totalPages = response.meta.pagination.total_pages; this.totalPages = response.meta.pagination.total_pages;
console.log('a');
for (let i = 0; i < response.data.length; i++) { for (let i = 0; i < response.data.length; i++) {
if (response.data.hasOwnProperty(i)) { if (response.data.hasOwnProperty(i)) {
let current = response.data[i]; let current = response.data[i];
@@ -399,18 +401,14 @@ let index = function () {
account_number: null === current.attributes.account_number ? '' : current.attributes.account_number, account_number: null === current.attributes.account_number ? '' : current.attributes.account_number,
current_balance: current.attributes.current_balance, current_balance: current.attributes.current_balance,
currency_code: current.attributes.currency_code, currency_code: current.attributes.currency_code,
//native_current_balance: current.attributes.native_current_balance,
//native_currency_code: current.attributes.native_currency_code,
last_activity: null === current.attributes.last_activity ? '' : format(new Date(current.attributes.last_activity), i18next.t('config.month_and_day_fns')), last_activity: null === current.attributes.last_activity ? '' : format(new Date(current.attributes.last_activity), i18next.t('config.month_and_day_fns')),
//balance_difference: current.attributes.balance_difference,
//native_balance_difference: current.attributes.native_balance_difference,
liability_type: current.attributes.liability_type, liability_type: current.attributes.liability_type,
liability_direction: current.attributes.liability_direction, liability_direction: current.attributes.liability_direction,
interest: current.attributes.interest, interest: current.attributes.interest,
interest_period: current.attributes.interest_period, interest_period: current.attributes.interest_period,
//current_debt: current.attributes.current_debt,
balance: current.attributes.balance, balance: current.attributes.balance,
native_balance: current.attributes.native_balance, native_balance: current.attributes.native_balance,
balances: current.attributes.balances,
}; };
// get group info: // get group info:
let groupId = current.attributes.object_group_id; let groupId = current.attributes.object_group_id;

View File

@@ -40,24 +40,33 @@ export default () => ({
loadingAccounts: false, loadingAccounts: false,
accountList: [], accountList: [],
convertToNative: false, convertToNative: false,
convertToNativeAvailable: false,
chartOptions: null, chartOptions: null,
switchConvertToNative() {
this.convertToNative = !this.convertToNative;
setVariable('convertToNative', this.convertToNative);
},
localCacheKey(type) { localCacheKey(type) {
return 'ds_accounts_' + type; return 'ds_accounts_' + type;
}, },
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/accounts)');
this.convertToNative = event.detail;
this.accountList = [];
chartData = null;
this.loadChart();
this.loadAccounts();
}
},
getFreshData() { getFreshData() {
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
const chartCacheKey = getCacheKey(this.localCacheKey('chart'), {start: start, end: end}) const chartCacheKey = getCacheKey(this.localCacheKey('chart'), {convertToNative: this.convertToNative, start: start, end: end})
const cacheValid = window.store.get('cacheValid'); const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(chartCacheKey); let cachedData = window.store.get(chartCacheKey);
if (cacheValid && typeof cachedData !== 'undefined') { if (cacheValid && typeof cachedData !== 'undefined') {
console.log('Generate from cache: ', chartCacheKey);
this.drawChart(this.generateOptions(cachedData)); this.drawChart(this.generateOptions(cachedData));
this.loading = false; this.loading = false;
return; return;
@@ -67,6 +76,7 @@ export default () => ({
this.chartData = response.data; this.chartData = response.data;
// cache generated options: // cache generated options:
window.store.set(chartCacheKey, response.data); window.store.set(chartCacheKey, response.data);
console.log('Generate FRESH!');
this.drawChart(this.generateOptions(this.chartData)); this.drawChart(this.generateOptions(this.chartData));
this.loading = false; this.loading = false;
}); });
@@ -90,18 +100,23 @@ export default () => ({
dataset.label = current.label; dataset.label = current.label;
// use the "native" currency code and use the "native_entries" as array // use the "native" currency code and use the "native_entries" as array
// if (this.convertToNative) { if (this.convertToNative) {
// currencies.push(current.native_currency_code); currencies.push(current.native_currency_code);
// dataset.currency_code = current.native_currency_code; dataset.currency_code = current.native_currency_code;
// collection = Object.values(current.native_entries); if(!current.hasOwnProperty('native_entries')) {
// yAxis = 'y' + current.native_currency_code; console.error('No native entries ('+this.convertToNative+') found for account: ', current);
// } }
// if (!this.convertToNative) { if(current.hasOwnProperty('native_entries')) {
collection = Object.values(current.native_entries);
}
yAxis = 'y' + current.native_currency_code;
}
if (!this.convertToNative) {
yAxis = 'y' + current.currency_code; yAxis = 'y' + current.currency_code;
dataset.currency_code = current.currency_code; dataset.currency_code = current.currency_code;
currencies.push(current.currency_code); currencies.push(current.currency_code);
collection = Object.values(current.entries); collection = Object.values(current.entries);
// } }
dataset.yAxisID = yAxis; dataset.yAxisID = yAxis;
dataset.data = collection; dataset.data = collection;
@@ -196,6 +211,14 @@ export default () => ({
(new Get).show(accountId, new Date(window.store.get('end'))).then((response) => { (new Get).show(accountId, new Date(window.store.get('end'))).then((response) => {
let parent = response.data.data; let parent = response.data.data;
// apply function to each element of parent:
parent.attributes.balances = parent.attributes.balances.map((balance) => {
balance.amount_formatted = formatMoney(balance.amount, balance.currency_code);
return balance;
});
// console.log(parent);
// get groups for account: // get groups for account:
const params = { const params = {
page: 1, page: 1,
@@ -243,8 +266,7 @@ export default () => ({
name: parent.attributes.name, name: parent.attributes.name,
order: parent.attributes.order, order: parent.attributes.order,
id: parent.id, id: parent.id,
balance: parent.attributes.balance, balances: parent.attributes.balances,
//native_balance: parent.attributes.native_balance,
groups: groups, groups: groups,
}); });
// console.log(parent.attributes); // console.log(parent.attributes);
@@ -266,13 +288,16 @@ export default () => ({
init() { init() {
// console.log('accounts init'); // console.log('accounts init');
Promise.all([getVariable('viewRange', '1M'), getVariable('convertToNative', false), getVariable('language', 'en_US'), Promise.all([
getConfiguration('cer.enabled', false) getVariable('viewRange', '1M'), // 0
getVariable('convert_to_native', false), // 1
getVariable('language', 'en_US'), // 2
getConfiguration('cer.enabled', false) // 3
]).then((values) => { ]).then((values) => {
//console.log('accounts after promises'); //console.log('accounts after promises');
this.convertToNative = values[1] && values[3]; this.convertToNative = values[1] && values[3];
this.convertToNativeAvailable = values[3];
afterPromises = true; afterPromises = true;
//console.log('convertToNative in accounts.js: ', values);
// main dashboard chart: // main dashboard chart:
this.loadChart(); this.loadChart();
@@ -289,7 +314,7 @@ export default () => ({
this.loadChart(); this.loadChart();
this.loadAccounts(); this.loadAccounts();
}); });
window.store.observe('convertToNative', () => { window.store.observe('convert_to_native', () => {
if (!afterPromises) { if (!afterPromises) {
return; return;
} }

View File

@@ -35,11 +35,21 @@ export default () => ({
loading: false, loading: false,
boxData: null, boxData: null,
boxOptions: null, boxOptions: null,
eventListeners: {
['@convert-to-native.window'](event){
this.convertToNative = event.detail;
this.accountList = [];
console.log('I heard that! (dashboard/boxes)');
this.boxData = null;
this.loadBoxes();
}
},
getFreshData() { getFreshData() {
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
// TODO cache key is hard coded, problem? // TODO cache key is hard coded, problem?
const boxesCacheKey = getCacheKey('ds_boxes_data', {start: start, end: end}); const boxesCacheKey = getCacheKey('ds_boxes_data', {convertToNative: this.convertToNative, start: start, end: end});
cleanupCache(); cleanupCache();
//const cacheValid = window.store.get('cacheValid'); //const cacheValid = window.store.get('cacheValid');
@@ -76,7 +86,7 @@ export default () => ({
continue; continue;
} }
let key = current.key; let key = current.key;
console.log('NOT NATIVE'); // console.log('NOT NATIVE');
if (key.startsWith('balance-in-')) { if (key.startsWith('balance-in-')) {
this.balanceBox.amounts.push(formatMoney(current.monetary_value, current.currency_code)); this.balanceBox.amounts.push(formatMoney(current.monetary_value, current.currency_code));
continue; continue;
@@ -153,7 +163,7 @@ export default () => ({
init() { init() {
// console.log('boxes init'); // console.log('boxes init');
// TODO can be replaced by "getVariables" // TODO can be replaced by "getVariables"
Promise.all([getVariable('viewRange'), getVariable('convertToNative', false)]).then((values) => { Promise.all([getVariable('viewRange'), getVariable('convert_to_native', false)]).then((values) => {
// console.log('boxes after promises'); // console.log('boxes after promises');
afterPromises = true; afterPromises = true;
this.convertToNative = values[1]; this.convertToNative = values[1];
@@ -167,7 +177,7 @@ export default () => ({
this.boxData = null; this.boxData = null;
this.loadBoxes(); this.loadBoxes();
}); });
window.store.observe('convertToNative', (newValue) => { window.store.observe('convert_to_native', (newValue) => {
if (!afterPromises) { if (!afterPromises) {
return; return;
} }

View File

@@ -48,9 +48,19 @@ export default () => ({
} }
this.getFreshData(); this.getFreshData();
}, },
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/budgets)');
this.convertToNative = event.detail;
chartData = null;
this.loadChart();
}
},
drawChart(options) { drawChart(options) {
if (null !== chart) { if (null !== chart) {
chart.data.datasets = options.data.datasets; chart.data = options.data;
chart.update(); chart.update();
return; return;
} }
@@ -59,8 +69,9 @@ export default () => ({
getFreshData() { getFreshData() {
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey('ds_bdg_chart', {start: start, end: end}); const cacheKey = getCacheKey('ds_bdg_chart', {convertToNative: this.convertToNative, start: start, end: end});
const cacheValid = window.store.get('cacheValid'); //const cacheValid = window.store.get('cacheValid');
const cacheValid = false;
let cachedData = window.store.get(cacheKey); let cachedData = window.store.get(cacheKey);
if (cacheValid && typeof cachedData !== 'undefined') { if (cacheValid && typeof cachedData !== 'undefined') {
@@ -80,7 +91,7 @@ export default () => ({
}, },
generateOptions(data) { generateOptions(data) {
currencies = []; currencies = [];
let options = getDefaultChartSettings('column'); let options = getDefaultChartSettings('bar');
options.options.locale = window.store.get('locale').replace('_', '-'); options.options.locale = window.store.get('locale').replace('_', '-');
options.options.plugins = { options.options.plugins = {
tooltip: { tooltip: {
@@ -94,7 +105,7 @@ export default () => ({
if (label) { if (label) {
label += ': '; label += ': ';
} }
return label + ' ' + formatMoney(context.parsed.y, currencies[context.parsed.x] ?? 'EUR'); return label + ' ' + formatMoney(context.parsed.x, currencies[context.parsed.x] ?? 'EUR');
} }
} }
} }
@@ -103,28 +114,19 @@ export default () => ({
labels: [], labels: [],
datasets: [ datasets: [
{ {
label: i18next.t('firefly.budgeted'),
data: [],
borderWidth: 1,
backgroundColor: getColors('budgeted', 'background'),
borderColor: getColors('budgeted', 'border'),
},
{
//label: i18next.t('firefly.budgeted'),
label: i18next.t('firefly.spent'), label: i18next.t('firefly.spent'),
data: [], data: [],
borderWidth: 1, borderWidth: 1,
stack: 1,
backgroundColor: getColors('spent', 'background'), backgroundColor: getColors('spent', 'background'),
borderColor: getColors('spent', 'border'), borderColor: getColors('spent', 'border'),
},
{
label: i18next.t('firefly.left'),
data: [],
borderWidth: 1,
stack: 1,
backgroundColor: getColors('left', 'background'),
borderColor: getColors('left', 'border'),
},
{
label: i18next.t('firefly.overspent'),
data: [],
borderWidth: 1,
stack: 1,
backgroundColor: getColors('overspent', 'background'),
borderColor: getColors('overspent', 'border'),
} }
] ]
}; };
@@ -134,24 +136,16 @@ export default () => ({
// // convert to EUR yes no? // // convert to EUR yes no?
let label = current.label + ' (' + current.currency_code + ')'; let label = current.label + ' (' + current.currency_code + ')';
options.data.labels.push(label); options.data.labels.push(label);
if (this.convertToNative) { // label = current.label + ' (' + current.currency_code + ') b';
currencies.push(current.native_currency_code); // options.data.labels.push(label);
// series 0: spent
options.data.datasets[0].data.push(parseFloat(current.native_entries.spent) * -1); currencies.push(current.currency_code);
// series 1: left // series 0: budgeted
options.data.datasets[1].data.push(parseFloat(current.native_entries.left)); options.data.datasets[0].data.push(parseFloat(current.entries.budgeted));
// series 2: overspent // series 1: spent
options.data.datasets[2].data.push(parseFloat(current.native_entries.overspent)); options.data.datasets[1].data.push(parseFloat(current.entries.spent) * -1);
} // series 2: overspent
if (!this.convertToNative) { // options.data.datasets[2].data.push(parseFloat(current.entries.overspent));
currencies.push(current.currency_code);
// series 0: spent
options.data.datasets[0].data.push(parseFloat(current.entries.spent) * -1);
// series 1: left
options.data.datasets[1].data.push(parseFloat(current.entries.left));
// series 2: overspent
options.data.datasets[2].data.push(parseFloat(current.entries.overspent));
}
} }
} }
// the currency format callback for the Y axis is AlWAYS based on whatever the first currency is. // the currency format callback for the Y axis is AlWAYS based on whatever the first currency is.
@@ -160,9 +154,10 @@ export default () => ({
options.options.scales = { options.options.scales = {
y: { y: {
ticks: { ticks: {
callback: function (context) { // callback: function (context) {
return formatMoney(context, currencies[0] ?? 'EUR'); // return 'abc';
} // return formatMoney(context, currencies[0] ?? 'EUR');
// }
} }
} }
}; };
@@ -172,7 +167,7 @@ export default () => ({
init() { init() {
Promise.all([getVariable('convertToNative', false)]).then((values) => { Promise.all([getVariable('convert_to_native', false)]).then((values) => {
this.convertToNative = values[0]; this.convertToNative = values[0];
afterPromises = true; afterPromises = true;
if (false === this.loading) { if (false === this.loading) {
@@ -189,7 +184,7 @@ export default () => ({
this.loadChart(); this.loadChart();
} }
}); });
window.store.observe('convertToNative', (newValue) => { window.store.observe('convert_to_native', (newValue) => {
if (!afterPromises) { if (!afterPromises) {
return; return;
} }

View File

@@ -33,6 +33,17 @@ let afterPromises = false;
export default () => ({ export default () => ({
loading: false, loading: false,
convertToNative: false, convertToNative: false,
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/categories)');
this.convertToNative = event.detail;
chartData = null;
this.loadChart();
}
},
generateOptions(data) { generateOptions(data) {
currencies = []; currencies = [];
let options = getDefaultChartSettings('column'); let options = getDefaultChartSettings('column');
@@ -43,11 +54,6 @@ export default () => ({
if (data.hasOwnProperty(i)) { if (data.hasOwnProperty(i)) {
let current = data[i]; let current = data[i];
let code = current.currency_code; let code = current.currency_code;
// only use native code when doing auto conversion.
if (this.convertToNative) {
code = current.native_currency_code;
}
if (!series.hasOwnProperty(code)) { if (!series.hasOwnProperty(code)) {
series[code] = { series[code] = {
name: code, name: code,
@@ -65,9 +71,6 @@ export default () => ({
let yAxis = 'y'; let yAxis = 'y';
let current = data[i]; let current = data[i];
let code = current.currency_code; let code = current.currency_code;
if (this.convertToNative) {
code = current.native_currency_code;
}
// loop series, add 0 if not present or add actual amount. // loop series, add 0 if not present or add actual amount.
for (const ii in series) { for (const ii in series) {
@@ -77,10 +80,6 @@ export default () => ({
// this series' currency matches this column's currency. // this series' currency matches this column's currency.
amount = parseFloat(current.amount); amount = parseFloat(current.amount);
yAxis = 'y' + current.currency_code; yAxis = 'y' + current.currency_code;
if (this.convertToNative) {
amount = parseFloat(current.native_amount);
yAxis = 'y' + current.native_currency_code;
}
} }
if (series[ii].data.hasOwnProperty(current.label)) { if (series[ii].data.hasOwnProperty(current.label)) {
// there is a value for this particular currency. The amount from this column will be added. // there is a value for this particular currency. The amount from this column will be added.
@@ -147,7 +146,7 @@ export default () => ({
getFreshData() { getFreshData() {
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey('ds_ct_chart', {start: start, end: end}); const cacheKey = getCacheKey('ds_ct_chart', {convertToNative: this.convertToNative, start: start, end: end});
const cacheValid = window.store.get('cacheValid'); const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey); let cachedData = window.store.get(cacheKey);
@@ -183,7 +182,7 @@ export default () => ({
}, },
init() { init() {
// console.log('categories init'); // console.log('categories init');
Promise.all([getVariable('convertToNative', false),]).then((values) => { Promise.all([getVariable('convert_to_native', false),]).then((values) => {
this.convertToNative = values[0]; this.convertToNative = values[0];
afterPromises = true; afterPromises = true;
this.loadChart(); this.loadChart();
@@ -195,7 +194,7 @@ export default () => ({
this.chartData = null; this.chartData = null;
this.loadChart(); this.loadChart();
}); });
window.store.observe('convertToNative', (newValue) => { window.store.observe('convert_to_native', (newValue) => {
if (!afterPromises) { if (!afterPromises) {
return; return;
} }

View File

@@ -46,7 +46,8 @@ import {
} from "chart.js"; } from "chart.js";
import 'chartjs-adapter-date-fns'; import 'chartjs-adapter-date-fns';
import {showInternalsButton} from "../../support/page-settings/show-internals-button.js"; import {showInternalsButton} from "../../support/page-settings/show-internals-button.js";
import {showWizardButton} from "../../support/page-settings/show-wizard-button.js"; import {setVariable} from "../../store/set-variable.js";
import {getVariable} from "../../store/get-variable.js";
// register things // register things
Chart.register({ Chart.register({
@@ -66,7 +67,26 @@ Chart.register({
Legend Legend
}); });
let index = function () {
return {
convertToNative: false,
saveNativeSettings(event) {
let target = event.currentTarget || event.target;
setVariable('convert_to_native',target.checked).then(() => {
console.log('Set convert to native to: ', target.checked);
this.$dispatch('convert-to-native', target.checked);
});
},
init() {
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
this.convertToNative = values[0];
});
}
}
};
const comps = { const comps = {
index,
dates, dates,
boxes, boxes,
accounts, accounts,

View File

@@ -36,7 +36,7 @@ export default () => ({
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
// needs user data. // needs user data.
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {start: start, end: end}); const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {convertToNative: this.convertToNative, start: start, end: end});
const cacheValid = window.store.get('cacheValid'); const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey); let cachedData = window.store.get(cacheKey);
@@ -129,7 +129,7 @@ export default () => ({
init() { init() {
// console.log('piggies init'); // console.log('piggies init');
apiData = []; apiData = [];
Promise.all([getVariable('convertToNative', false)]).then((values) => { Promise.all([getVariable('convert_to_native', false)]).then((values) => {
afterPromises = true; afterPromises = true;
this.convertToNative = values[0]; this.convertToNative = values[0];
@@ -144,7 +144,7 @@ export default () => ({
apiData = []; apiData = [];
this.loadPiggyBanks(); this.loadPiggyBanks();
}); });
window.store.observe('convertToNative', (newValue) => { window.store.observe('convert_to_native', (newValue) => {
if (!afterPromises) { if (!afterPromises) {
return; return;
} }

View File

@@ -29,17 +29,16 @@ import i18next from "i18next";
Chart.register({SankeyController, Flow}); Chart.register({SankeyController, Flow});
const SANKEY_CACHE_KEY = 'ds_sankey_data'; const SANKEY_CACHE_KEY = 'ds_sankey_data';
let currencies = []; let currencies = [];
let afterPromises = false; let afterPromises = false;
let chart = null; let chart = null;
let transactions = []; let transactions = [];
let convertToNative = false; let convertToNative = false;
let translations = { let translations = {
category: null, category: null,
unknown_category: null, unknown_category: null,
in: null, in: null,
out: null, out: null,
// TODO
unknown_source: null, unknown_source: null,
unknown_dest: null, unknown_dest: null,
unknown_account: null, unknown_account: null,
@@ -80,74 +79,97 @@ const getColor = function (key) {
// little helper // little helper
function getObjectName(type, name, direction, code) { function getObjectName(type, name, direction, code) {
if(convertToNative) {
return getObjectNameWithoutCurrency(type, name, direction);
}
return getObjectNameWithCurrency(type, name, direction, code);
// category 4x
if ('category' === type && null !== name && 'in' === direction) {
return translations.category + ' "' + name + '" (' + translations.in + (convertToNative ? ', ' + code + ')' : ')');
}
if ('category' === type && null === name && 'in' === direction) {
return translations.unknown_category + ' (' + translations.in + (convertToNative ? ', ' + code + ')' : ')');
}
if ('category' === type && null !== name && 'out' === direction) {
return translations.category + ' "' + name + '" (' + translations.out + (convertToNative ? ', ' + code + ')' : ')');
}
if ('category' === type && null === name && 'out' === direction) {
return translations.unknown_category + ' (' + translations.out + (convertToNative ? ', ' + code + ')' : ')');
}
// account 4x
if ('account' === type && null === name && 'in' === direction) {
return translations.unknown_source + (convertToNative ? ' (' + code + ')' : '');
}
if ('account' === type && null !== name && 'in' === direction) {
return translations.revenue_account + '"' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
}
if ('account' === type && null === name && 'out' === direction) {
return translations.unknown_dest + (convertToNative ? ' (' + code + ')' : '');
}
if ('account' === type && null !== name && 'out' === direction) {
return translations.expense_account + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
}
// budget 2x
if ('budget' === type && null !== name) {
return translations.budget + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
}
if ('budget' === type && null === name) {
return translations.unknown_budget + (convertToNative ? ' (' + code + ')' : '');
}
console.error('Cannot handle: type:"' + type + '", dir: "' + direction + '"');
} }
function getLabelName(type, name, code) { function getObjectNameWithoutCurrency(type, name, direction) {
// category if('category' === type) {
if ('category' === type && null !== name) { let catName = null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
return translations.category + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : ''); let directionText = 'in' === direction ? translations.in : translations.out;
return catName + ' (' + directionText + ')';
} }
if ('category' === type && null === name) { if('account' === type) {
return translations.unknown_category + (convertToNative ? ' (' + code + ')' : ''); let accountName = null === name ? translations.unknown_account : name;
let directionText = 'in' === direction ? translations.in : translations.out;
let fullAccountName = 'in' === direction ? translations.revenue_account + ' "' + accountName + '"' : translations.expense_account + ' "' + accountName + '"';
return fullAccountName + ' (' + directionText + ')';
} }
// account if('budget' === type) {
if ('account' === type && null === name) { return null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"';
return translations.unknown_account + (convertToNative ? ' (' + code + ')' : '');
} }
if ('account' === type && null !== name) { console.error('[a] Cannot handle: type:"' + type + '", dir: "' + direction + '"');
return name + (convertToNative ? ' (' + code + ')' : ''); }
function getObjectNameWithCurrency(type, name, direction, code) {
if('category' === type) {
let catName = null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
let directionText = 'in' === direction ? translations.in : translations.out;
return catName + ' (' + directionText + ', ' + code + ')';
} }
if('account' === type) {
// budget 2x let accountName = null === name ? translations.unknown_account : name;
if ('budget' === type && null !== name) { let directionText = 'in' === direction ? translations.in : translations.out;
return translations.budget + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : ''); let fullAccountName = 'in' === direction ? translations.revenue_account + ' "' + accountName + '"' : translations.expense_account + ' "' + accountName + '"';
return fullAccountName + ' (' + directionText + ', ' + code + ')';
} }
if ('budget' === type && null === name) { if('budget' === type) {
return translations.unknown_budget + (convertToNative ? ' (' + code + ')' : ''); return (null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"') + ' (' + code + ')';
} }
console.error('Cannot handle: type:"' + type + '"'); console.error('[b] Cannot handle: type:"' + type + '", dir: "' + direction + '"');
} }
function getLabel(type, name, code) {
if(convertToNative) {
return getLabelWithoutCurrency(type, name);
}
return getLabelWithCurrency(type, name, code);
}
function getLabelWithoutCurrency(type, name) {
if('category' === type) {
return null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
}
if('account' === type) {
return null === name ? translations.unknown_account : name;
}
if('budget' === type) {
return null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"';
}
console.error('[a] Cannot handle: type:"' + type + '"');
}
function getLabelWithCurrency(type, name, code) {
if('category' === type) {
return (null === name ? translations.unknown_category : translations.category + ' "' + name + '"') + ' ('+ code + ')';
}
if('account' === type) {
return (null === name ? translations.unknown_account : name) + ' (' + code + ')';
}
if('budget' === type) {
return (null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"') + ' (' + code + ')';;
}
console.error('[b] Cannot handle: type:"' + type + '"');
}
export default () => ({ export default () => ({
loading: false, loading: false,
convertToNative: false, convertToNative: false,
processedData: null,
eventListeners: {
['@convert-to-native.window'](event){
console.log('I heard that! (dashboard/sankey)');
this.convertToNative = event.detail;
convertToNative = event.detail;
this.processedData = null;
this.loadChart();
}
},
generateOptions() { generateOptions() {
let options = getDefaultChartSettings('sankey'); let options = getDefaultChartSettings('sankey');
@@ -155,120 +177,22 @@ export default () => ({
currencies = []; currencies = [];
// variables collected for the sankey chart: // variables collected for the sankey chart:
let amounts = {}; this.parseTransactionGroups(transactions);
let labels = {};
for (let i in transactions) {
if (transactions.hasOwnProperty(i)) {
let group = transactions[i];
for (let ii in group.attributes.transactions) {
if (group.attributes.transactions.hasOwnProperty(ii)) {
// properties of the transaction, used in the generation of the chart:
let transaction = group.attributes.transactions[ii];
let currencyCode = this.convertToNative ? transaction.native_currency_code : transaction.currency_code;
let amount = this.convertToNative ? parseFloat(transaction.native_amount) : parseFloat(transaction.amount);
let flowKey;
/*
Two entries in the sankey diagram for deposits:
1. From the revenue account (source) to a category (in).
2. From the category (in) to the big inbox.
*/
if ('deposit' === transaction.type) {
// nr 1
let category = getObjectName('category', transaction.category_name, 'in', currencyCode);
let revenueAccount = getObjectName('account', transaction.source_name, 'in', currencyCode);
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
labels[revenueAccount] = getLabelName('account', transaction.source_name, currencyCode);
flowKey = revenueAccount + '-' + category + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: revenueAccount,
to: category,
amount: 0
};
}
amounts[flowKey].amount += amount;
// nr 2
flowKey = category + '-' + translations.all_money + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: category,
to: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
amount: 0
};
}
amounts[flowKey].amount += amount;
}
/*
Three entries in the sankey diagram for withdrawals:
1. From the big box to a budget.
2. From a budget to a category.
3. From a category to an expense account.
*/
if ('withdrawal' === transaction.type) {
// 1.
let budget = getObjectName('budget', transaction.budget_name, 'out', currencyCode);
labels[budget] = getLabelName('budget', transaction.budget_name, currencyCode);
flowKey = translations.all_money + '-' + budget + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
to: budget,
amount: 0
};
}
amounts[flowKey].amount += amount;
// 2.
let category = getObjectName('category', transaction.category_name, 'out', currencyCode);
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
flowKey = budget + '-' + category + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: budget,
to: category,
amount: 0
};
}
amounts[flowKey].amount += amount;
// 3.
let expenseAccount = getObjectName('account', transaction.destination_name, 'out', currencyCode);
labels[expenseAccount] = getLabelName('account', transaction.destination_name, currencyCode);
flowKey = category + '-' + expenseAccount + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: category,
to: expenseAccount,
amount: 0
};
}
amounts[flowKey].amount += amount;
}
}
}
}
}
let dataSet = let dataSet =
// sankey chart has one data set. // sankey chart has one data set.
{ {
label: 'Firefly III dashboard sankey chart', label: 'Firefly III dashboard sankey chart',
data: [], data: [],
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].from : ''), colorFrom: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].from : ''),
colorTo: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].to : ''), colorTo: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].to : ''),
colorMode: 'gradient', // or 'from' or 'to' colorMode: 'gradient', // or 'from' or 'to'
labels: labels, labels: this.processedData.labels,
size: 'min', // or 'min' if flow overlap is preferred size: 'min', // or 'min' if flow overlap is preferred
}; };
for (let i in amounts) { for (let i in this.processedData.amounts) {
if (amounts.hasOwnProperty(i)) { if (this.processedData.amounts.hasOwnProperty(i)) {
let amount = amounts[i]; let amount = this.processedData.amounts[i];
dataSet.data.push({from: amount.from, to: amount.to, flow: amount.amount}); dataSet.data.push({from: amount.from, to: amount.to, flow: amount.amount});
} }
} }
@@ -276,6 +200,133 @@ export default () => ({
return options; return options;
}, },
parseTransactionGroups(groups) {
this.processedData = {
amounts: {},
labels: {}
};
for (let i in groups) {
if (groups.hasOwnProperty(i)) {
let group = groups[i];
this.parseTransactionGroup(group);
}
}
},
parseTransactionGroup(group) {
for (let ii in group.attributes.transactions) {
if (group.attributes.transactions.hasOwnProperty(ii)) {
// properties of the transaction, used in the generation of the chart:
let transaction = group.attributes.transactions[ii];
this.parseTransaction(transaction);
}
}
},
parseTransaction(transaction) {
let currencyCode = transaction.currency_code;
let amount = parseFloat(transaction.amount);
let flowKey;
if (this.convertToNative) {
currencyCode = transaction.native_currency_code;
amount = parseFloat(transaction.native_amount);
}
if ('deposit' === transaction.type) {
this.parseDeposit(transaction, currencyCode, amount);
return;
}
if ('withdrawal' === transaction.type) {
this.parseWithdrawal(transaction, currencyCode, amount);
}
},
parseWithdrawal(transaction, currencyCode, amount) {
/*
Three entries in the sankey diagram for withdrawals:
1. From the big box to a budget.
2. From a budget to a category.
3. From a category to an expense account.
*/
// first one:
let budget = getObjectName('budget', transaction.budget_name, 'out', currencyCode);
this.processedData.labels[budget] = getLabel('budget', transaction.budget_name, currencyCode);
let flowKey = translations.all_money + '-' + budget + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
to: budget,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
// second one:
let category = getObjectName('category', transaction.category_name, 'out', currencyCode);
this.processedData.labels[category] = getLabel('category', transaction.category_name, currencyCode);
flowKey = budget + '-' + category + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: budget,
to: category,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
// third one:
let expenseAccount = getObjectName('account', transaction.destination_name, 'out', currencyCode);
this.processedData.labels[expenseAccount] = getLabel('account', transaction.destination_name, currencyCode);
flowKey = category + '-' + expenseAccount + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: category,
to: expenseAccount,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
},
parseDeposit(transaction, currencyCode, amount) {
/*
Two entries in the sankey diagram for deposits:
1. From the revenue account (source) to a category (in).
2. From the category (in) to the big inbox.
*/
// this is the first one:
let category = getObjectName('category', transaction.category_name, 'in', currencyCode);
let revenueAccount = getObjectName('account', transaction.source_name, 'in', currencyCode);
let flowKey = revenueAccount + '-' + category + '-' + currencyCode;
this.processedData.labels[category] = getLabel('category', transaction.category_name, currencyCode);
this.processedData.labels[revenueAccount] = getLabel('account', transaction.source_name, currencyCode);
// create if necessary:
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: revenueAccount,
to: category,
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
// this is the second one:
flowKey = category + '-' + translations.all_money + '-' + currencyCode;
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
this.processedData.amounts[flowKey] = {
from: category,
to: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
amount: 0
};
}
this.processedData.amounts[flowKey].amount += amount;
},
drawChart(options) { drawChart(options) {
if (null !== chart) { if (null !== chart) {
chart.data.datasets = options.data.datasets; chart.data.datasets = options.data.datasets;
@@ -286,12 +337,12 @@ export default () => ({
}, },
getFreshData() { getFreshData() {
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end}); const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
const cacheValid = window.store.get('cacheValid'); const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey); let cachedData = window.store.get(cacheKey);
if (cacheValid && typeof cachedData !== 'undefined') { if (cacheValid && typeof cachedData !== 'undefined') {
transactions = cachedData; transactions = cachedData;
@@ -310,9 +361,9 @@ export default () => ({
this.downloadTransactions(params); this.downloadTransactions(params);
}, },
downloadTransactions(params) { downloadTransactions(params) {
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end}); const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {convertToNative: this.convertToNative, start: start, end: end});
//console.log('Downloading page ' + params.page + '...'); //console.log('Downloading page ' + params.page + '...');
const getter = new Get(); const getter = new Get();
@@ -348,26 +399,27 @@ export default () => ({
init() { init() {
// console.log('sankey init'); // console.log('sankey init');
transactions = []; transactions = [];
Promise.all([getVariable('convertToNative', false)]).then((values) => { Promise.all([getVariable('convert_to_native', false)]).then((values) => {
this.convertToNative = values[0]; this.convertToNative = values[0];
convertToNative = values[0]; convertToNative = values[0];
// some translations:
translations.all_money = i18next.t('firefly.all_money');
translations.category = i18next.t('firefly.category');
translations.in = i18next.t('firefly.money_flowing_in');
translations.out = i18next.t('firefly.money_flowing_out');
translations.unknown_category = i18next.t('firefly.unknown_category_plain');
translations.unknown_source = i18next.t('firefly.unknown_source_plain');
translations.unknown_dest = i18next.t('firefly.unknown_dest_plain');
translations.unknown_account = i18next.t('firefly.unknown_any_plain');
translations.unknown_budget = i18next.t('firefly.unknown_budget_plain');
translations.expense_account = i18next.t('firefly.expense_account');
translations.revenue_account = i18next.t('firefly.revenue_account');
translations.budget = i18next.t('firefly.budget');
// console.log('sankey after promises'); // some translations:
afterPromises = true; translations.all_money = i18next.t('firefly.all_money');
this.loadChart(); translations.category = i18next.t('firefly.category');
translations.in = i18next.t('firefly.money_flowing_in');
translations.out = i18next.t('firefly.money_flowing_out');
translations.unknown_category = i18next.t('firefly.unknown_category_plain');
translations.unknown_source = i18next.t('firefly.unknown_source_plain');
translations.unknown_dest = i18next.t('firefly.unknown_dest_plain');
translations.unknown_account = i18next.t('firefly.unknown_any_plain');
translations.unknown_budget = i18next.t('firefly.unknown_budget_plain');
translations.expense_account = i18next.t('firefly.expense_account');
translations.revenue_account = i18next.t('firefly.revenue_account');
translations.budget = i18next.t('firefly.budget');
// console.log('sankey after promises');
afterPromises = true;
this.loadChart();
}); });
window.store.observe('end', () => { window.store.observe('end', () => {
@@ -378,7 +430,7 @@ export default () => ({
this.transactions = []; this.transactions = [];
this.loadChart(); this.loadChart();
}); });
window.store.observe('convertToNative', (newValue) => { window.store.observe('convert_to_native', (newValue) => {
if (!afterPromises) { if (!afterPromises) {
return; return;
} }

View File

@@ -31,6 +31,99 @@ let afterPromises = false;
let apiData = []; let apiData = [];
let subscriptionData = {}; let subscriptionData = {};
function addObjectGroupInfo(data) {
let objectGroupId = parseInt(data.object_group_id);
if (!subscriptionData.hasOwnProperty(objectGroupId)) {
subscriptionData[objectGroupId] = {
id: objectGroupId,
title: null === data.object_group_title ? i18next.t('firefly.default_group_title_name_plain') : data.object_group_title,
order: parseInt(data.object_group_order),
payment_info: {},
bills: [],
};
}
}
function parsePayDates(list) {
let newList = [];
for(let i in list) {
if (list.hasOwnProperty(i)) {
let current = list[i];
// convert to date object:
newList.push(new Date(current));
}
}
return newList;
}
function parseBillInfo(data) {
let result = {
id: data.id,
name: data.attributes.name,
amount_min: data.attributes.amount_min,
amount_max: data.attributes.amount_max,
amount: (parseFloat(data.attributes.amount_max) + parseFloat(data.attributes.amount_min)) / 2,
currency_code: data.attributes.currency_code,
// paid transactions:
transactions: [],
// unpaid moments
pay_dates: parsePayDates(data.attributes.pay_dates),
paid: data.attributes.paid_dates.length > 0,
};
// set variables
result.expected_amount = formatMoney(result.amount, result.currency_code);
result.expected_times = i18next.t('firefly.subscr_expected_x_times', {
times: data.attributes.pay_dates.length,
amount: result.expected_amount
});
// console.log(result);
return result;
}
function parsePaidTransactions(paid_dates, bill) {
if( !paid_dates || paid_dates.length < 1) {
return [];
}
let result = [];
// add transactions (simpler version)
for (let i in paid_dates) {
if (paid_dates.hasOwnProperty(i)) {
const currentPayment = paid_dates[i];
// console.log(currentPayment);
// math: -100+(paid/expected)*100
let percentage = Math.round(-100 + ((parseFloat(currentPayment.amount) ) / parseFloat(bill.amount)) * 100);
let currentTransaction = {
amount: formatMoney(currentPayment.amount, currentPayment.currency_code),
percentage: percentage,
date: format(new Date(currentPayment.date), 'PP'),
foreign_amount: null,
};
if (null !== currentPayment.foreign_currency_code) {
currentTransaction.foreign_amount = currentPayment.foreign_amount;
currentTransaction.foreign_currency_code = currentPayment.foreign_currency_code;
}
result.push(currentTransaction);
}
}
return result;
}
function isInRange(bill) {
let start = new Date(window.store.get('start'));
let end = new Date(window.store.get('end'));
for(let i in bill.pay_dates) {
if (bill.pay_dates.hasOwnProperty(i)) {
let currentDate = bill.pay_dates[i];
//console.log(currentDate);
if (currentDate >= start && currentDate <= end) {
return true;
}
}
}
return false;
}
function downloadSubscriptions(params) { function downloadSubscriptions(params) {
const getter = new Get(); const getter = new Get();
return getter.list(params) return getter.list(params)
@@ -41,83 +134,25 @@ function downloadSubscriptions(params) {
for (let i in data) { for (let i in data) {
if (data.hasOwnProperty(i)) { if (data.hasOwnProperty(i)) {
let current = data[i]; let current = data[i];
//console.log(current);
if (current.attributes.active && current.attributes.pay_dates.length > 0) { if (current.attributes.active && current.attributes.pay_dates.length > 0) {
let objectGroupId = null === current.attributes.object_group_id ? 0 : current.attributes.object_group_id; // create or update object group
let objectGroupTitle = null === current.attributes.object_group_title ? i18next.t('firefly.default_group_title_name_plain') : current.attributes.object_group_title; let objectGroupId = parseInt(current.attributes.object_group_id);
let objectGroupOrder = null === current.attributes.object_group_order ? 0 : current.attributes.object_group_order; addObjectGroupInfo(current.attributes);
if (!subscriptionData.hasOwnProperty(objectGroupId)) {
subscriptionData[objectGroupId] = { // create and update the bill.
id: objectGroupId, let bill = parseBillInfo(current);
title: objectGroupTitle,
order: objectGroupOrder, // if not yet paid, and pay_dates is not in current rage, ignore it.
payment_info: {}, if (false === bill.paid && !isInRange(bill)) {
bills: [], console.warn('Bill "'+bill.name+'" is not paid and not in range, ignoring: ');
}; continue;
} }
// TODO this conversion needs to be inside some kind of a parsing class.
let bill = {
id: current.id,
name: current.attributes.name,
// amount
amount_min: current.attributes.amount_min,
amount_max: current.attributes.amount_max,
amount: (parseFloat(current.attributes.amount_max) + parseFloat(current.attributes.amount_min)) / 2,
currency_code: current.attributes.currency_code,
// native amount
// native_amount_min: current.attributes.native_amount_min,
// native_amount_max: current.attributes.native_amount_max,
// native_amount: (parseFloat(current.attributes.native_amount_max) + parseFloat(current.attributes.native_amount_min)) / 2,
// native_currency_code: current.attributes.native_currency_code,
// paid transactions: bill.transactions = parsePaidTransactions(current.attributes.paid_dates, bill);
transactions: [],
// unpaid moments
pay_dates: current.attributes.pay_dates,
paid: current.attributes.paid_dates.length > 0,
};
// set variables
bill.expected_amount = formatMoney(bill.amount, bill.currency_code);
bill.expected_times = i18next.t('firefly.subscr_expected_x_times', {
times: current.attributes.pay_dates.length,
amount: bill.expected_amount
});
// add transactions (simpler version)
for (let iii in current.attributes.paid_dates) {
if (current.attributes.paid_dates.hasOwnProperty(iii)) {
const currentPayment = current.attributes.paid_dates[iii];
let percentage = 100;
// math: -100+(paid/expected)*100
if (params.convertToNative) {
percentage = Math.round(-100 + ((parseFloat(currentPayment.native_amount) * -1) / parseFloat(bill.native_amount)) * 100);
}
if (!params.convertToNative) {
percentage = Math.round(-100 + ((parseFloat(currentPayment.amount) * -1) / parseFloat(bill.amount)) * 100);
}
// TODO fix me
currentPayment.currency_code = 'EUR';
console.log('Currency code: "'+currentPayment+'"');
console.log(currentPayment);
let currentTransaction = {
amount: formatMoney(currentPayment.amount, currentPayment.currency_code),
percentage: percentage,
date: format(new Date(currentPayment.date), 'PP'),
foreign_amount: null,
};
if (null !== currentPayment.foreign_currency_code) {
currentTransaction.foreign_amount = currentPayment.foreign_amount;
currentTransaction.foreign_currency_code = currentPayment.foreign_currency_code;
}
bill.transactions.push(currentTransaction);
}
}
subscriptionData[objectGroupId].bills.push(bill); subscriptionData[objectGroupId].bills.push(bill);
if (0 === current.attributes.paid_dates.length) { if (false === bill.paid) {
// bill is unpaid, count the "pay_dates" and multiply with the "amount". // bill is unpaid, count the "pay_dates" and multiply with the "amount".
// since bill is unpaid, this can only be in currency amount and native currency amount. // since bill is unpaid, this can only be in currency amount and native currency amount.
const totalAmount = current.attributes.pay_dates.length * bill.amount; const totalAmount = current.attributes.pay_dates.length * bill.amount;
@@ -133,6 +168,7 @@ function downloadSubscriptions(params) {
//native_unpaid: 0, //native_unpaid: 0,
}; };
} }
subscriptionData[objectGroupId].payment_info[bill.currency_code].unpaid += totalAmount; subscriptionData[objectGroupId].payment_info[bill.currency_code].unpaid += totalAmount;
//subscriptionData[objectGroupId].payment_info[bill.currency_code].native_unpaid += totalNativeAmount; //subscriptionData[objectGroupId].payment_info[bill.currency_code].native_unpaid += totalNativeAmount;
} }
@@ -269,7 +305,7 @@ export default () => ({
}, },
init() { init() {
Promise.all([getVariable('convertToNative', false)]).then((values) => { Promise.all([getVariable('convert_to_native', false)]).then((values) => {
this.convertToNative = values[0]; this.convertToNative = values[0];
afterPromises = true; afterPromises = true;
@@ -287,7 +323,7 @@ export default () => ({
this.startSubscriptions(); this.startSubscriptions();
} }
}); });
window.store.observe('convertToNative', (newValue) => { window.store.observe('convert_to_native', (newValue) => {
if (!afterPromises) { if (!afterPromises) {
return; return;
} }

View File

@@ -71,7 +71,7 @@ let index = function () {
init() { init() {
// TODO need date range. // TODO need date range.
// TODO handle page number // TODO handle page number
this.getTransactions(this.page);`` this.getTransactions(this.page);
// Your Javascript code to create the grid // Your Javascript code to create the grid
// dataTable = createGrid(document.querySelector('#grid'), gridOptions); // dataTable = createGrid(document.querySelector('#grid'), gridOptions);

View File

@@ -26,7 +26,28 @@ $danger: #CD5029 !default;
$primary: #1E6581 !default; $primary: #1E6581 !default;
$success: #64B624 !default; $success: #64B624 !default;
// admin LTE
@use "admin-lte/src/scss/adminlte" with (
$color-mode-type: $color-mode-type,
$link-decoration: $link-decoration,
$font-family-sans-serif: $font-family-sans-serif,
$danger: $danger,
$primary: $primary,
$success: $success
);
@use '@fortawesome/fontawesome-free/scss/variables' with (
$font-path: "@fortawesome/fontawesome-free/webfonts"
);
@use '@fortawesome/fontawesome-free/scss/fontawesome';
@use '@fortawesome/fontawesome-free/scss/fa' as fa;
@use '@fortawesome/fontawesome-free/scss/solid.scss' as fa-solid;
@use '@fortawesome/fontawesome-free/scss/brands.scss' as fa-brands;
@use '@fortawesome/fontawesome-free/scss/regular.scss' as fa-regular;
// some local CSS
.skip-links {display: none;} .skip-links {display: none;}
/* /*
@@ -59,16 +80,7 @@ h3.hover-expand:hover {
appearance: auto; appearance: auto;
} }
// Bootstrap // edit buttons
// @import "bootstrap/scss/bootstrap";
// admin LTE
@import "adminlte-filtered";
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
// hover buttons
.hidden-edit-button { .hidden-edit-button {
cursor: pointer; cursor: pointer;
} }
@@ -76,12 +88,12 @@ td:not(:hover) .hidden-edit-button {
visibility: hidden; visibility: hidden;
} }
// Bootstrap
// @import "bootstrap/scss/bootstrap";
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
// hover buttons
// Font awesome
//@import "~font-awesome/css/font-awesome";
$fa-font-path: "@fortawesome/fontawesome-free/webfonts";
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "@fortawesome/fontawesome-free/scss/solid.scss";
@import "@fortawesome/fontawesome-free/scss/brands.scss";
@import "@fortawesome/fontawesome-free/scss/regular.scss";

View File

@@ -27,19 +27,29 @@ export function getConfiguration(name, defaultValue = null) {
// to make things available quicker than if the store has to grab it through the API. // to make things available quicker than if the store has to grab it through the API.
// then again, it's not that slow. // then again, it's not that slow.
if (validCache && window.hasOwnProperty(name)) { if (validCache && window.hasOwnProperty(name)) {
// console.log('Get from window'); console.log('Return configuration "' + name + '" from window: ' + window[name]);
return Promise.resolve(window[name]); return Promise.resolve(window[name]);
} }
// load from store2, if it's present. // load from store2, if it's present.
const fromStore = window.store.get(name); const fromStore = window.store.get(name);
if (validCache && typeof fromStore !== 'undefined') { if (validCache && typeof fromStore !== 'undefined') {
console.log('Return configuration "' + name + '" from store: ' + fromStore);
return Promise.resolve(fromStore); return Promise.resolve(fromStore);
} }
let getter = (new Get); let getter = (new Get);
return getter.getByName(name).then((response) => { return getter.getByName(name).then((response) => {
// console.log('Get "' + name + '" from API'); // console.log('Get "' + name + '" from API');
return Promise.resolve(parseResponse(name, response)); console.log('Return configuration "' + name + '" from API: ' + parseConfigurationResponse(name, response));
}).catch(() => { return Promise.resolve(parseConfigurationResponse(name, response));
}).catch((error) => {
console.log('Returning "'+name+'" from DEFAULT: ' + defaultValue);
console.warn(error);
return defaultValue; return defaultValue;
}); });
} }
export function parseConfigurationResponse(name, response) {
let value = response.data.data.value;
window.store.set(name, value);
return value;
}

View File

@@ -27,16 +27,19 @@ export function getVariable(name, defaultValue = null) {
// to make things available quicker than if the store has to grab it through the API. // to make things available quicker than if the store has to grab it through the API.
// then again, it's not that slow. // then again, it's not that slow.
if (validCache && window.hasOwnProperty(name)) { if (validCache && window.hasOwnProperty(name)) {
console.log('Returning "'+name+'" from window: ' + window[name]);
return Promise.resolve(window[name]); return Promise.resolve(window[name]);
} }
// load from store2, if it's present. // load from store2, if it's present.
const fromStore = window.store.get(name); const fromStore = window.store.get(name);
if (validCache && typeof fromStore !== 'undefined') { if (validCache && typeof fromStore !== 'undefined') {
console.log('Returning "'+name+'" from store: ' + fromStore);
return Promise.resolve(fromStore); return Promise.resolve(fromStore);
} }
let getter = (new Get); let getter = (new Get);
return getter.getByName(name).then((response) => { return getter.getByName(name).then((response) => {
console.log('Returning "'+name+'" from server: ' + parseResponse(name, response));
return Promise.resolve(parseResponse(name, response)); return Promise.resolve(parseResponse(name, response));
}).catch((error) => { }).catch((error) => {
if('' === defaultValue) { if('' === defaultValue) {
@@ -47,6 +50,7 @@ export function getVariable(name, defaultValue = null) {
// POST it and then return it anyway. // POST it and then return it anyway.
let poster = (new Post); let poster = (new Post);
return poster.post(name, defaultValue).then((response) => { return poster.post(name, defaultValue).then((response) => {
console.log('Returning "'+name+'" from POST: ' + parseResponse(name, response));
return Promise.resolve(parseResponse(name, response)); return Promise.resolve(parseResponse(name, response));
}); });
}); });

View File

@@ -28,19 +28,25 @@ export function setVariable(name, value = null) {
// then again, it's not that slow. // then again, it's not that slow.
// set in window.x // set in window.x
// window[name] = value; window[name] = value;
// set in store: // set in store:
window.store.set(name, value); window.store.set(name, value);
// post to user preferences (because why not): // post to user preferences (because why not):
let putter = new Put(); let putter = new Put();
putter.put(name, value).then((response) => { return putter.put(name, value).then((response) => {
}).catch(() => { console.log('set "'+name+'" to value: ', value);
return Promise.resolve(response);
}).catch((error) => {
console.error(error);
// preference does not exist (yet). // preference does not exist (yet).
// POST it // POST it
let poster = (new Post); let poster = (new Post);
poster.post(name, value).then((response) => { poster.post(name, value).then((response) => {
console.log('POST "'+name+'" to value: ', value);
return Promise.resolve(response);
}); });
}); });
} }

View File

@@ -62,6 +62,36 @@ function getDefaultChartSettings(type) {
}, },
}; };
} }
if('bar' === type) {
return {
type: 'bar',
data: {
labels: [],
datasets: [],
},
options: {
maintainAspectRatio: false,
indexAxis: 'y',
// Elements options apply to all the options unless overridden in a dataset
// In this case, we are setting the border of each horizontal bar to be 2px wide
elements: {
bar: {
borderWidth: 2,
}
},
responsive: true,
plugins: {
legend: {
position: 'right',
},
title: {
display: true,
text: 'Chart.js Horizontal Bar Chart'
}
}
},
};
}
if ('line' === type) { if ('line' === type) {
return { return {
options: { options: {

View File

@@ -92,6 +92,14 @@ function getColors(type, field) {
backgroundColor: background.rgbString(), backgroundColor: background.rgbString(),
}; };
break; break;
case 'budgeted':
background = new Color(green.rgbString());
background.lighten(0.38);
colors = {
borderColor: green.rgbString(),
backgroundColor: background.rgbString(),
};
break;
case 'overspent': case 'overspent':
background = new Color(red.rgbString()); background = new Color(red.rgbString());
background.lighten(0.22); background.lighten(0.22);

View File

@@ -21,6 +21,14 @@
import {format} from "date-fns"; import {format} from "date-fns";
export default function (amount, currencyCode) { export default function (amount, currencyCode) {
if( (typeof amount !== 'number' && typeof amount !== 'string') || isNaN(amount)) {
console.warn('format-money: amount is not a number:', amount);
return '';
}
if(typeof currencyCode !== 'string' || currencyCode.length !== 3) {
console.warn('format-money: currencyCode is not a valid ISO 4217 code:', currencyCode);
return '';
}
let locale = window.__localeId__.replace('_', '-'); let locale = window.__localeId__.replace('_', '-');
return Intl.NumberFormat(locale, { return Intl.NumberFormat(locale, {

View File

@@ -234,6 +234,7 @@ return [
'advanced_options' => 'Advanced options', 'advanced_options' => 'Advanced options',
'advanced_options_explain' => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!', 'advanced_options_explain' => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!',
'here_be_dragons' => 'Hic sunt dracones', 'here_be_dragons' => 'Hic sunt dracones',
'bad_date_transaction' => 'Firefly III has detected you have transactions from before the year 1970. Please correct these transactions at your earliest convenience.',
// Webhooks // Webhooks
'webhooks' => 'Webhooks', 'webhooks' => 'Webhooks',
@@ -1866,8 +1867,8 @@ return [
'skip_help_text' => 'Use the skip field to create bi-monthly (skip = 1) or other custom intervals.', 'skip_help_text' => 'Use the skip field to create bi-monthly (skip = 1) or other custom intervals.',
'subscription' => 'Subscription', 'subscription' => 'Subscription',
'not_expected_period' => 'Not expected this period', 'not_expected_period' => 'Not expected this period',
'subscriptions_in_group' => 'Subscriptions in group "%{title}"', 'subscriptions_in_group' => 'Subscriptions in group "{{title}}"',
'subscr_expected_x_times' => 'Expect to pay %{amount} %{times} times this period', 'subscr_expected_x_times' => 'Expect to pay {{amount}} {{times}} times this period',
'not_or_not_yet' => 'Not (yet)', 'not_or_not_yet' => 'Not (yet)',
'visit_bill' => 'Visit subscription ":name" at Firefly III', 'visit_bill' => 'Visit subscription ":name" at Firefly III',
'match_between_amounts' => 'Subscription matches transactions between :low and :high.', 'match_between_amounts' => 'Subscription matches transactions between :low and :high.',

View File

@@ -7,7 +7,7 @@
@include('partials.dashboard.boxes') @include('partials.dashboard.boxes')
<!-- row with account, budget and category data --> <!-- row with account, budget and category data -->
<div class="row mb-2" x-data="accounts"> <div class="row mb-2" x-data="accounts" x-bind="eventListeners">
<!-- column with 3 charts --> <!-- column with 3 charts -->
<div class="col-xl-8 col-lg-12 col-sm-12 col-xs-12"> <div class="col-xl-8 col-lg-12 col-sm-12 col-xs-12">
<!-- row with account chart --> <!-- row with account chart -->
@@ -37,11 +37,11 @@
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title"><a href="#" title="Something">recurring? rules? tags?</a></h3> <h3 class="card-title"><a href="#" title="Something">Income + sum</a></h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<p> <p>
TODO List of income + sum
</p> </p>
</div> </div>
</div> </div>
@@ -49,7 +49,7 @@
</div> </div>
</div> </div>
<div class="modal fade" id="internalsModal" tabindex="-1" aria-labelledby="internalsModalLabel" <div x-data="index" class="modal fade" id="internalsModal" tabindex="-1" aria-labelledby="internalsModalLabel"
aria-hidden="true"> aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
@@ -58,9 +58,17 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p> <div class="row mb-3">
Buttons. <label class="col-sm-4 col-form-label">Convert to native</label>
</p> <div class="col-sm-8">
<div class="form-check form-switch form-check-inline">
<label>
<input class="form-check-input" x-model="convertToNative" type="checkbox" @change="saveNativeSettings"> <span
>Yes no</span>
</label>
</div>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ __('firefly.close') }}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ __('firefly.close') }}</button>

View File

@@ -9,24 +9,6 @@
<div class="card-body p-0" style="position: relative;height:400px;"> <div class="card-body p-0" style="position: relative;height:400px;">
<canvas id="account-chart"></canvas> <canvas id="account-chart"></canvas>
</div> </div>
<template x-if="convertToNativeAvailable">
<div class="card-footer text-end">
<template x-if="convertToNative">
<button type="button" @click="switchConvertToNative"
class="btn btn-outline-info btm-sm">
<span
class="fa-solid fa-comments-dollar"></span> {{ __('firefly.disable_auto_convert') }}
</button>
</template>
<template x-if="!convertToNative">
<button type="button" @click="switchConvertToNative"
class="btn btn-outline-info btm-sm">
<span
class="fa-solid fa-comments-dollar"></span> {{ __('firefly.enable_auto_convert') }}
</button>
</template>
</div>
</template>
</div> </div>
</div> </div>

View File

@@ -13,13 +13,13 @@
x-text="account.name"></a> x-text="account.name"></a>
<span class="small"> <span class="small">
<template x-for="balance in account.balance"> <template x-for="balance in account.balances">
<span>x</span> <template x-if="balance.type === 'current'">
<span class="text-muted">(<span x-text="balance.amount_formatted"></span>)
</span>
</template>
</template> </template>
<template x-for="balance in account.native_balance"> </span>
<span>Y</span>
</template>
</span>
</h3> </h3>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">

View File

@@ -1,4 +1,4 @@
<div class="row mb-2" x-data="boxes"> <div class="row mb-2" x-data="boxes" x-bind="eventListeners">
<div class="col-xl-3 col-lg-6 col-md-12 col-sm-12"> <div class="col-xl-3 col-lg-6 col-md-12 col-sm-12">
<div class="small-box text-bg-primary"> <div class="small-box text-bg-primary">
<div class="inner balance-box"> <div class="inner balance-box">

View File

@@ -1,4 +1,4 @@
<div class="row mb-2" x-data="budgets"> <div class="row mb-2" x-data="budgets" x-bind="eventListeners">
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">

View File

@@ -1,4 +1,4 @@
<div class="row mb-2" x-data="categories"> <div class="row mb-2" x-data="categories" x-bind="eventListeners">
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">

View File

@@ -6,7 +6,7 @@
>{{ __('firefly.income_and_expense') }}</a> >{{ __('firefly.income_and_expense') }}</a>
</h3> </h3>
</div> </div>
<div class="card-body" x-data="sankey"> <div class="card-body" x-data="sankey" x-bind="eventListeners">
<canvas id="sankey-chart"></canvas> <canvas id="sankey-chart"></canvas>
</div> </div>
</div> </div>

View File

@@ -10,6 +10,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row mb-2"> <div class="row mb-2">
<!--
<template x-for="pie in group.payment_info"> <template x-for="pie in group.payment_info">
<div :class='group.col_size'> <div :class='group.col_size'>
<canvas :id='"pie_" + group.id + "_" + pie.currency_code' <canvas :id='"pie_" + group.id + "_" + pie.currency_code'
@@ -17,6 +18,8 @@
x-init="drawPieChart(group.id, group.title, pie)"></canvas> x-init="drawPieChart(group.id, group.title, pie)"></canvas>
</div> </div>
</template> </template>
-->
Here was chart.
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">