mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-15 16:57:09 +00:00
Compare commits
43 Commits
develop-20
...
develop-20
Author | SHA1 | Date | |
---|---|---|---|
|
f3b387fc22 | ||
|
03904ffcde | ||
|
a7973190c2 | ||
|
671ff95f22 | ||
|
01181ceea9 | ||
|
97643639d1 | ||
|
424783c47b | ||
|
ea0ced70b2 | ||
|
1a633e64ef | ||
|
30da3f4399 | ||
|
6bdff95d87 | ||
|
895ae279d5 | ||
|
8b57f45963 | ||
|
a2d2b7edd3 | ||
|
3d65f00c6e | ||
|
333004c4d9 | ||
|
7451659824 | ||
|
ec89c23ace | ||
|
92d07d346f | ||
|
a7ac894af2 | ||
|
20f89e3a7c | ||
|
5d2f11c3c7 | ||
|
1eea79e431 | ||
|
1aaaac67ca | ||
|
1eb86639c9 | ||
|
28b911c4ee | ||
|
c525c70ec0 | ||
|
1f7d6e218b | ||
|
a69b6d9ce2 | ||
|
28b2ddde18 | ||
|
a16cc73c77 | ||
|
46395e350a | ||
|
f62e49090c | ||
|
af3b40a314 | ||
|
c3cea0fa9e | ||
|
6cbdb2ce70 | ||
|
b78460100d | ||
|
bf6e1cb0e1 | ||
|
6a53f5031c | ||
|
ae15ec01e8 | ||
|
fe3c7c47c4 | ||
|
68b934010c | ||
|
22852bd238 |
24
.ci/php-cs-fixer/composer.lock
generated
24
.ci/php-cs-fixer/composer.lock
generated
@@ -406,20 +406,20 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.84.0",
|
||||
"version": "v3.85.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca"
|
||||
"reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca",
|
||||
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2fb6d7f6c3398dca5786a1635b27405d73a417ba",
|
||||
"reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"clue/ndjson-react": "^1.0",
|
||||
"clue/ndjson-react": "^1.3",
|
||||
"composer/semver": "^3.4",
|
||||
"composer/xdebug-handler": "^3.0.5",
|
||||
"ext-filter": "*",
|
||||
@@ -429,12 +429,12 @@
|
||||
"fidry/cpu-core-counter": "^1.2",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"react/child-process": "^0.6.6",
|
||||
"react/event-loop": "^1.0",
|
||||
"react/promise": "^2.11 || ^3.0",
|
||||
"react/socket": "^1.0",
|
||||
"react/stream": "^1.0",
|
||||
"react/event-loop": "^1.5",
|
||||
"react/promise": "^3.2",
|
||||
"react/socket": "^1.16",
|
||||
"react/stream": "^1.4",
|
||||
"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/filesystem": "^5.4.45 || ^6.4.13 || ^7.0",
|
||||
"symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0",
|
||||
@@ -499,7 +499,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.85.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -507,7 +507,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-15T18:21:57+00:00"
|
||||
"time": "2025-07-29T22:22:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
|
1
.github/workflows/cleanup.yml
vendored
1
.github/workflows/cleanup.yml
vendored
@@ -66,7 +66,6 @@ jobs:
|
||||
'label-actions.yml',
|
||||
'lock.yml',
|
||||
'release.yml',
|
||||
'sonarcloud.yml',
|
||||
'stale.yml'
|
||||
]
|
||||
|
||||
|
@@ -24,16 +24,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Api\V1\Requests\Data\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Chart\ChartData;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
@@ -42,6 +42,7 @@ use FireflyIII\Support\Http\Api\ApiSupport;
|
||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AccountController
|
||||
@@ -86,10 +87,12 @@ class AccountController extends Controller
|
||||
// move date to end of day
|
||||
$queryParameters['start']->startOfDay();
|
||||
$queryParameters['end']->endOfDay();
|
||||
Log::debug(sprintf('dashboard(), convert to native: %s', var_export($this->convertToNative, true)));
|
||||
|
||||
// loop each account, and collect info:
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Account #%d ("%s")', $account->id, $account->name));
|
||||
$this->renderAccountData($queryParameters, $account);
|
||||
}
|
||||
|
||||
@@ -101,15 +104,22 @@ class AccountController extends Controller
|
||||
*/
|
||||
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) {
|
||||
$currency = $this->default;
|
||||
}
|
||||
$currentSet = [
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
|
||||
// the currency that belongs to the account.
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
@@ -121,18 +131,33 @@ class AccountController extends Controller
|
||||
'period' => '1D',
|
||||
'entries' => [],
|
||||
];
|
||||
$currentStart = clone $params['start'];
|
||||
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
|
||||
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'];
|
||||
}
|
||||
|
||||
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
while ($currentStart <= $params['end']) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
$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();
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
}
|
||||
$this->chartData->add($currentSet);
|
||||
}
|
||||
@@ -146,19 +171,84 @@ class AccountController extends Controller
|
||||
public function overview(DateRequest $request): JsonResponse
|
||||
{
|
||||
// parameters for chart:
|
||||
$dates = $request->getAll();
|
||||
$dates = $request->getAll();
|
||||
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = $dates['start'];
|
||||
$start = $dates['start'];
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $dates['end'];
|
||||
$end = $dates['end'];
|
||||
|
||||
// set dates to end of day + start of day:
|
||||
$start->startOfDay();
|
||||
$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();
|
||||
|
||||
/** @var Preference $frontpage */
|
||||
@@ -169,41 +259,6 @@ class AccountController extends Controller
|
||||
$frontpage->save();
|
||||
}
|
||||
|
||||
// get accounts:
|
||||
$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);
|
||||
return $frontpage->data ?? $defaultSet;
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -77,6 +78,8 @@ class BudgetController extends Controller
|
||||
|
||||
/**
|
||||
* TODO see autocomplete/accountcontroller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse
|
||||
{
|
||||
@@ -107,23 +110,41 @@ class BudgetController extends Controller
|
||||
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get all limits:
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$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 (0 === $limits->count()) {
|
||||
// return as a single item in an array
|
||||
$rows = $this->noBudgetLimits($budget, $start, $end);
|
||||
}
|
||||
if ($limits->count() > 0) {
|
||||
$rows = $this->budgetLimits($budget, $limits);
|
||||
}
|
||||
// if (0 === $limits->count()) {
|
||||
// return as a single item in an array
|
||||
// $rows = $this->noBudgetLimits($budget, $start, $end);
|
||||
// }
|
||||
|
||||
// is always an array
|
||||
$return = [];
|
||||
$return = [];
|
||||
foreach ($rows as $row) {
|
||||
$current = [
|
||||
'label' => $budget->name,
|
||||
'currency_id' => (string) $row['currency_id'],
|
||||
'currency_id' => (string)$row['currency_id'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_name' => $row['currency_name'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
@@ -131,6 +152,7 @@ class BudgetController extends Controller
|
||||
'start' => $row['start'],
|
||||
'end' => $row['end'],
|
||||
'entries' => [
|
||||
'budgeted' => $row['budgeted'],
|
||||
'spent' => $row['spent'],
|
||||
'left' => $row['left'],
|
||||
'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
|
||||
* its info.
|
||||
*
|
||||
* @param array<int, array<int, string>> $array
|
||||
*
|
||||
* @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 = [];
|
||||
|
||||
@@ -174,16 +194,17 @@ class BudgetController extends Controller
|
||||
* @var int $currencyId
|
||||
* @var array $block
|
||||
*/
|
||||
foreach ($array as $currencyId => $block) {
|
||||
foreach ($spent as $currencyId => $block) {
|
||||
$this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
|
||||
$return[$currencyId] ??= [
|
||||
'currency_id' => (string) $currencyId,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_code' => $block['currency_code'],
|
||||
'currency_name' => $block['currency_name'],
|
||||
'currency_symbol' => $block['currency_symbol'],
|
||||
'currency_decimal_places' => (int) $block['currency_decimal_places'],
|
||||
'currency_decimal_places' => (int)$block['currency_decimal_places'],
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
'budgeted' => '0',
|
||||
'spent' => '0',
|
||||
'left' => '0',
|
||||
'overspent' => '0',
|
||||
@@ -193,7 +214,7 @@ class BudgetController extends Controller
|
||||
// var_dump($return);
|
||||
/** @var array $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);
|
||||
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
||||
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) {
|
||||
// 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) {
|
||||
$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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -25,17 +25,20 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Generic\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
@@ -45,6 +48,8 @@ class CategoryController extends Controller
|
||||
use CleansChartData;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
private AccountRepositoryInterface $accountRepos;
|
||||
private CurrencyRepositoryInterface $currencyRepos;
|
||||
|
||||
@@ -79,9 +84,10 @@ class CategoryController extends Controller
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $this->parameters->get('end');
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]);
|
||||
$currencies = [];
|
||||
$return = [];
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
// get journals for entire period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
@@ -93,20 +99,40 @@ class CategoryController extends Controller
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
|
||||
$currencies[$currencyId] = $currency;
|
||||
$categoryName = $journal['category_name'] ?? (string) trans('firefly.no_category');
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
$key = sprintf('%s-%s', $categoryName, $currency->code);
|
||||
// find journal:
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
|
||||
$currencies[$journalCurrencyId] = $currency;
|
||||
$currencyId = (int)$currency->id;
|
||||
$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
|
||||
$return[$key] ??= [
|
||||
'label' => $categoryName,
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
'period' => null,
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
@@ -114,12 +140,12 @@ class CategoryController extends Controller
|
||||
];
|
||||
|
||||
// add monies
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string) $amount);
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string)$amount);
|
||||
}
|
||||
$return = array_values($return);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
@@ -76,6 +78,15 @@ class ShowController extends Controller
|
||||
$bills = $bills->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$bills = $enrichment->enrich($bills);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -147,6 +147,7 @@ class ShowController extends Controller
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -26,6 +26,7 @@ namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Jobs\SendWebhookMessage;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class WebhookEventHandler
|
||||
@@ -37,7 +38,13 @@ class WebhookEventHandler
|
||||
*/
|
||||
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!
|
||||
$messages = WebhookMessage::where('webhook_messages.sent', false)
|
||||
->get(['webhook_messages.*'])
|
||||
@@ -45,14 +52,14 @@ class WebhookEventHandler
|
||||
static fn (WebhookMessage $message) => $message->webhookAttempts()->count() <= 2
|
||||
)->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) {
|
||||
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();
|
||||
}
|
||||
if (false !== $message->sent) {
|
||||
app('log')->debug(sprintf('Skip message #%d', $message->id));
|
||||
Log::debug(sprintf('Skip message #%d', $message->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -558,7 +558,6 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
|
||||
}
|
||||
}
|
||||
|
||||
$groups = $this->parseSums($groups);
|
||||
|
||||
return new Collection($groups);
|
||||
|
@@ -154,12 +154,18 @@ class TagController extends Controller
|
||||
*/
|
||||
public function index(TagRepositoryInterface $repository)
|
||||
{
|
||||
// start with oldest tag
|
||||
// start with the oldest tag
|
||||
$first = session('first', today()) ?? today();
|
||||
$oldestTagDate = $repository->oldestTag() instanceof Tag ? $repository->oldestTag()->date : clone $first;
|
||||
$newestTagDate = $repository->newestTag() instanceof Tag ? $repository->newestTag()->date : today();
|
||||
$oldestTagDate->startOfYear();
|
||||
$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['no-date'] = $repository->getTagsInYear(null);
|
||||
|
||||
|
@@ -24,14 +24,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use Deprecated;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
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\Repositories\UserGroup\UserGroupInterface;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
@@ -55,17 +57,17 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$total = '0';
|
||||
$count = 0;
|
||||
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;
|
||||
$amount = $limit->amount;
|
||||
$perDay = bcdiv((string) $amount, (string) $diff);
|
||||
$perDay = bcdiv((string)$amount, (string)$diff);
|
||||
$total = bcadd($total, $perDay);
|
||||
++$count;
|
||||
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
|
||||
}
|
||||
$avg = $total;
|
||||
if ($count > 0) {
|
||||
$avg = bcdiv($total, (string) $count);
|
||||
$avg = bcdiv($total, (string)$count);
|
||||
}
|
||||
app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
|
||||
|
||||
@@ -93,9 +95,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
// prep data array for currency:
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetId = (int)$journal['budget_id'];
|
||||
$budgetName = $journal['budget_name'];
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$key = sprintf('%d-%d', $budgetId, $currencyId);
|
||||
|
||||
$data[$key] ??= [
|
||||
@@ -110,7 +112,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
'entries' => [],
|
||||
];
|
||||
$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;
|
||||
@@ -124,7 +126,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array
|
||||
{
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
if ($accounts instanceof Collection && $accounts->count() > 0) {
|
||||
$collector->setAccounts($accounts);
|
||||
@@ -136,15 +138,41 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$collector->setBudgets($this->getBudgets());
|
||||
}
|
||||
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$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) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetName = (string) $journal['budget_name'];
|
||||
$amount = app('steam')->negative($journal['amount']);
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
@@ -153,10 +181,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$array[$currencyId] ??= [
|
||||
'budgets' => [],
|
||||
'currency_id' => $currencyId,
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_decimal_places' => $journal['currency_decimal_places'],
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
|
||||
// info about the categories:
|
||||
@@ -168,9 +196,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
|
||||
// add journal to array:
|
||||
// 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] = [
|
||||
'amount' => app('steam')->negative($journal['amount']),
|
||||
'amount' => $amount,
|
||||
'destination_account_id' => $journal['destination_account_id'],
|
||||
'destination_account_name' => $journal['destination_account_name'],
|
||||
'source_account_id' => $journal['source_account_id'],
|
||||
|
@@ -32,6 +32,7 @@ use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use NumberFormatter;
|
||||
|
||||
/**
|
||||
@@ -66,13 +67,13 @@ class Amount
|
||||
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
|
||||
$fmt->setAttribute(NumberFormatter::MIN_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 (1 === bccomp((string) $rounded, '0')) {
|
||||
if (1 === bccomp((string)$rounded, '0')) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -106,23 +107,21 @@ class Amount
|
||||
$amount = $journal[$field] ?? '0';
|
||||
// Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
|
||||
// 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'];
|
||||
// Log::debug(sprintf('Overruled, amount is now %s', $amount));
|
||||
}
|
||||
|
||||
return (string) $amount;
|
||||
return (string)$amount;
|
||||
}
|
||||
|
||||
public function convertToNative(?User $user = null): bool
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
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');
|
||||
// Log::debug(sprintf('convertToNative [b]: %s', var_export($result, true)));
|
||||
}
|
||||
|
||||
public function getNativeCurrency(): TransactionCurrency
|
||||
@@ -180,9 +179,9 @@ class Amount
|
||||
return '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!
|
||||
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
$amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
}
|
||||
|
||||
return $amount;
|
||||
|
160
app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
Normal file
160
app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi\Enrichments;
|
||||
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SubscriptionEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private Collection $collection;
|
||||
private bool $convertToNative = false;
|
||||
private array $subscriptionIds = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $notes = [];
|
||||
private TransactionCurrency $nativeCurrency;
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->collectSubscriptionIds();
|
||||
$this->collectNotes();
|
||||
$this->collectObjectGroups();
|
||||
|
||||
$notes = $this->notes;
|
||||
$objectGroups = $this->objectGroups;
|
||||
$this->collection = $this->collection->map(function (Bill $item) use ($notes, $objectGroups) {
|
||||
$id = (int) $item->id;
|
||||
$currency = $item->transactionCurrency;
|
||||
$meta = [
|
||||
'notes' => null,
|
||||
'object_group_id' => null,
|
||||
'object_group_title' => null,
|
||||
'object_group_order' => null,
|
||||
];
|
||||
$amounts = [
|
||||
'amount_min' => Steam::bcround($item->amount_min, $currency->decimal_places),
|
||||
'amount_max' => Steam::bcround($item->amount_max, $currency->decimal_places),
|
||||
'average' => Steam::bcround(bcdiv(bcadd($item->amount_min, $item->amount_max), '2'), $currency->decimal_places),
|
||||
];
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $objectGroups[$key]['order'];
|
||||
}
|
||||
|
||||
// Add notes if available.
|
||||
if (array_key_exists($item->id, $notes)) {
|
||||
$meta['notes'] = $notes[$item->id];
|
||||
}
|
||||
|
||||
// Convert amounts to native currency if needed
|
||||
if ($this->convertToNative && $item->currency_id !== $this->nativeCurrency->id) {
|
||||
$converter = new ExchangeRateConverter();
|
||||
$amounts = [
|
||||
'amount_min' => Steam::bcround($converter->convert($item->transactionCurrency, $this->nativeCurrency, today(), $item->amount_min), $this->nativeCurrency->decimal_places),
|
||||
'amount_max' => Steam::bcround($converter->convert($item->transactionCurrency, $this->nativeCurrency, today(), $item->amount_max), $this->nativeCurrency->decimal_places),
|
||||
];
|
||||
$amounts['average'] = Steam::bcround(bcdiv(bcadd($amounts['amount_min'], $amounts['amount_max']), '2'), $this->nativeCurrency->decimal_places);
|
||||
}
|
||||
$item->amounts = $amounts;
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function enrichSingle(array|Model $model): array|Model
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection([$model]);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->subscriptionIds)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->userGroup = $user->userGroup;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
public function setConvertToNative(bool $convertToNative): void
|
||||
{
|
||||
$this->convertToNative = $convertToNative;
|
||||
}
|
||||
|
||||
public function setNative(TransactionCurrency $nativeCurrency): void
|
||||
{
|
||||
$this->nativeCurrency = $nativeCurrency;
|
||||
}
|
||||
|
||||
private function collectSubscriptionIds(): void
|
||||
{
|
||||
/** @var Bill $bill */
|
||||
foreach ($this->collection as $bill) {
|
||||
$this->subscriptionIds[] = (int) $bill->id;
|
||||
}
|
||||
$this->subscriptionIds = array_unique($this->subscriptionIds);
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
->whereIn('object_groupable_id', $this->subscriptionIds)
|
||||
->where('object_groupable_type', Bill::class)
|
||||
->get(['object_groupable_id', 'object_group_id'])
|
||||
;
|
||||
|
||||
$ids = array_unique($set->pluck('object_group_id')->toArray());
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
|
||||
}
|
||||
|
||||
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
|
||||
foreach ($groups as $group) {
|
||||
$group['id'] = (int) $group['id'];
|
||||
$group['order'] = (int) $group['order'];
|
||||
$this->objectGroups[(int)$group['id']] = $group;
|
||||
}
|
||||
}
|
||||
}
|
@@ -93,7 +93,7 @@ class Steam
|
||||
|
||||
return [];
|
||||
}
|
||||
$defaultCurrency = app('amount')->getNativeCurrency();
|
||||
$defaultCurrency = Amount::getNativeCurrency();
|
||||
if ($convertToNative) {
|
||||
if ($defaultCurrency->id === $currency?->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();
|
||||
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
|
||||
$startBalance = $this->finalAccountBalance($account, $request);
|
||||
$nativeCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup);
|
||||
$nativeCurrency = Amount::getNativeCurrencyByUserGroup($account->user->userGroup);
|
||||
$accountCurrency = $this->getAccountCurrency($account);
|
||||
$hasCurrency = $accountCurrency instanceof TransactionCurrency;
|
||||
$currency = $accountCurrency ?? $nativeCurrency;
|
||||
@@ -294,7 +294,7 @@ class Steam
|
||||
$currentBalance[$entryCurrency->code] ??= '0';
|
||||
$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) {
|
||||
$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 ($convertToNative) {
|
||||
$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) {
|
||||
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
|
||||
}
|
||||
|
||||
}
|
||||
// just set it.
|
||||
// add to final array.
|
||||
$balances[$carbonKey] = $currentBalance;
|
||||
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
|
||||
}
|
||||
|
@@ -116,14 +116,14 @@ class AccountTransformer extends AbstractTransformer
|
||||
$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(),
|
||||
];
|
||||
'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',
|
||||
|
@@ -27,7 +27,6 @@ namespace FireflyIII\Transformers;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
@@ -44,7 +43,7 @@ class BillTransformer extends AbstractTransformer
|
||||
{
|
||||
private readonly BillDateCalculator $calculator;
|
||||
private readonly bool $convertToNative;
|
||||
private readonly TransactionCurrency $default;
|
||||
private readonly TransactionCurrency $native;
|
||||
private readonly BillRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
@@ -54,7 +53,7 @@ class BillTransformer extends AbstractTransformer
|
||||
{
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
$this->calculator = app(BillDateCalculator::class);
|
||||
$this->default = Amount::getNativeCurrency();
|
||||
$this->native = Amount::getNativeCurrency();
|
||||
$this->convertToNative = Amount::convertToNative();
|
||||
}
|
||||
|
||||
@@ -66,28 +65,14 @@ class BillTransformer extends AbstractTransformer
|
||||
*/
|
||||
public function transform(Bill $bill): array
|
||||
{
|
||||
$default = $this->parameters->get('defaultCurrency') ?? $this->default;
|
||||
|
||||
$paidData = $this->paidData($bill);
|
||||
$lastPaidDate = $this->getLastPaidDate($paidData);
|
||||
$start = $this->parameters->get('start') ?? today()->subYears(10);
|
||||
$end = $this->parameters->get('end') ?? today()->addYears(10);
|
||||
$payDates = $this->calculator->getPayDates($start, $end, $bill->date, $bill->repeat_freq, $bill->skip, $lastPaidDate);
|
||||
$currency = $bill->transactionCurrency;
|
||||
$notes = $this->repository->getNoteText($bill);
|
||||
$notes = '' === $notes ? null : $notes;
|
||||
$objectGroupId = null;
|
||||
$objectGroupOrder = null;
|
||||
$objectGroupTitle = null;
|
||||
$this->repository->setUser($bill->user);
|
||||
|
||||
/** @var null|ObjectGroup $objectGroup */
|
||||
$objectGroup = $bill->objectGroups->first();
|
||||
if (null !== $objectGroup) {
|
||||
$objectGroupId = $objectGroup->id;
|
||||
$objectGroupOrder = $objectGroup->order;
|
||||
$objectGroupTitle = $objectGroup->title;
|
||||
}
|
||||
|
||||
$paidDataFormatted = [];
|
||||
$payDatesFormatted = [];
|
||||
@@ -156,15 +141,16 @@ class BillTransformer extends AbstractTransformer
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'native_currency_id' => null === $default ? null : (string)$default->id,
|
||||
'native_currency_code' => $default?->code,
|
||||
'native_currency_symbol' => $default?->symbol,
|
||||
'native_currency_decimal_places' => $default?->decimal_places,
|
||||
|
||||
'native_currency_id' => (string)$this->native->id,
|
||||
'native_currency_code' => $this->native->code,
|
||||
'native_currency_symbol' => $this->native->symbol,
|
||||
'native_currency_decimal_places' => $this->native->decimal_places,
|
||||
|
||||
'name' => $bill->name,
|
||||
'amount_min' => app('steam')->bcround($bill->amount_min, $currency->decimal_places),
|
||||
'amount_max' => app('steam')->bcround($bill->amount_max, $currency->decimal_places),
|
||||
'native_amount_min' => $this->convertToNative ? app('steam')->bcround($bill->native_amount_min, $default->decimal_places) : null,
|
||||
'native_amount_max' => $this->convertToNative ? app('steam')->bcround($bill->native_amount_max, $default->decimal_places) : null,
|
||||
'amount_min' => $bill->amounts['amount_min'],
|
||||
'amount_max' => $bill->amounts['amount_max'],
|
||||
'amount_avg' => $bill->amounts['average'],
|
||||
'date' => $bill->date->toAtomString(),
|
||||
'end_date' => $bill->end_date?->toAtomString(),
|
||||
'extension_date' => $bill->extension_date?->toAtomString(),
|
||||
@@ -172,16 +158,16 @@ class BillTransformer extends AbstractTransformer
|
||||
'skip' => $bill->skip,
|
||||
'active' => $bill->active,
|
||||
'order' => $bill->order,
|
||||
'notes' => $notes,
|
||||
'object_group_id' => null !== $objectGroupId ? (string)$objectGroupId : null,
|
||||
'object_group_order' => $objectGroupOrder,
|
||||
'object_group_title' => $objectGroupTitle,
|
||||
'notes' => $bill->meta['notes'],
|
||||
'object_group_id' => $bill->meta['object_group_id'],
|
||||
'object_group_order' => $bill->meta['object_group_order'],
|
||||
'object_group_title' => $bill->meta['object_group_title'],
|
||||
|
||||
// these fields need work:
|
||||
'next_expected_match' => $nem,
|
||||
'next_expected_match_diff' => $nemDiff,
|
||||
'pay_dates' => $payDatesFormatted,
|
||||
'paid_dates' => $paidDataFormatted,
|
||||
// 'next_expected_match' => $nem,
|
||||
// 'next_expected_match_diff' => $nemDiff,
|
||||
// 'pay_dates' => $payDatesFormatted,
|
||||
// 'paid_dates' => $paidDataFormatted,
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
@@ -235,14 +221,14 @@ class BillTransformer extends AbstractTransformer
|
||||
$result = [];
|
||||
foreach ($set as $entry) {
|
||||
$array = [
|
||||
'transaction_group_id' => (string)$entry->transaction_group_id,
|
||||
'transaction_journal_id' => (string)$entry->id,
|
||||
'date' => $entry->date->format('Y-m-d'),
|
||||
'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),
|
||||
'transaction_group_id' => (string)$entry->transaction_group_id,
|
||||
'transaction_journal_id' => (string)$entry->id,
|
||||
'date' => $entry->date->format('Y-m-d'),
|
||||
'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;
|
||||
|
@@ -121,6 +121,15 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string) $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);
|
||||
|
||||
// must be 0 (int) or NULL
|
||||
|
48
composer.lock
generated
48
composer.lock
generated
@@ -10768,16 +10768,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.5.0",
|
||||
"version": "v5.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
|
||||
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
|
||||
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10820,9 +10820,9 @@
|
||||
],
|
||||
"support": {
|
||||
"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",
|
||||
@@ -11065,16 +11065,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.19",
|
||||
"version": "2.1.21",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "473a8c30e450d87099f76313edcbb90852f9afdf"
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/473a8c30e450d87099f76313edcbb90852f9afdf",
|
||||
"reference": "473a8c30e450d87099f76313edcbb90852f9afdf",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11119,7 +11119,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-21T19:58:24+00:00"
|
||||
"time": "2025-07-28T19:35:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-deprecation-rules",
|
||||
@@ -11218,16 +11218,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "12.3.1",
|
||||
"version": "12.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "ddec29dfc128eba9c204389960f2063f3b7fa170"
|
||||
"reference": "086553c5b2e0e1e20293d782d788ab768202b621"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ddec29dfc128eba9c204389960f2063f3b7fa170",
|
||||
"reference": "ddec29dfc128eba9c204389960f2063f3b7fa170",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/086553c5b2e0e1e20293d782d788ab768202b621",
|
||||
"reference": "086553c5b2e0e1e20293d782d788ab768202b621",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11283,7 +11283,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"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": [
|
||||
{
|
||||
@@ -11303,7 +11303,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-18T08:58:13+00:00"
|
||||
"time": "2025-07-29T06:19:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
@@ -11552,16 +11552,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "12.2.7",
|
||||
"version": "12.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "8b1348b254e5959acaf1539c6bd790515fb49414"
|
||||
"reference": "49dc7c0669b97c7a36f63411635f48eedc140eb4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8b1348b254e5959acaf1539c6bd790515fb49414",
|
||||
"reference": "8b1348b254e5959acaf1539c6bd790515fb49414",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49dc7c0669b97c7a36f63411635f48eedc140eb4",
|
||||
"reference": "49dc7c0669b97c7a36f63411635f48eedc140eb4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11575,7 +11575,7 @@
|
||||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"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-invoker": "^6.0.0",
|
||||
"phpunit/php-text-template": "^5.0.0",
|
||||
@@ -11629,7 +11629,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"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": [
|
||||
{
|
||||
@@ -11653,7 +11653,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-11T04:11:13+00:00"
|
||||
"time": "2025-07-30T05:58:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
|
@@ -78,8 +78,8 @@ return [
|
||||
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2025-07-23',
|
||||
'build_time' => 1753247028,
|
||||
'version' => 'develop/2025-07-31',
|
||||
'build_time' => 1753936691,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 26,
|
||||
|
||||
|
@@ -65,6 +65,7 @@ return [
|
||||
'interest_calc_half-year',
|
||||
'interest_calc_quarterly',
|
||||
'spent',
|
||||
'budgeted',
|
||||
'administration_owner',
|
||||
'administration_you',
|
||||
'administration_role_owner',
|
||||
|
268
package-lock.json
generated
268
package-lock.json
generated
@@ -402,14 +402,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
|
||||
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
|
||||
"integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.27.6"
|
||||
"@babel/types": "^7.28.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1622,9 +1622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
|
||||
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1665,9 +1665,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
|
||||
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1699,6 +1699,13 @@
|
||||
"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": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||
@@ -2142,9 +2149,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
|
||||
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz",
|
||||
"integrity": "sha512-X48nISrSOa89zu2VMljC4XaRf8NmgTwQBVHfS2Nu5G00ZwM31oOVrAtGxZF3b6wDYf9lJsf/Eq4cCSFKIkOWPQ==",
|
||||
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -2585,9 +2592,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
|
||||
"integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
|
||||
"integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2599,9 +2606,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
|
||||
"integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
|
||||
"integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2613,9 +2620,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
|
||||
"integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
|
||||
"integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2627,9 +2634,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
|
||||
"integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
|
||||
"integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2641,9 +2648,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
|
||||
"integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
|
||||
"integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2655,9 +2662,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
|
||||
"integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
|
||||
"integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2669,9 +2676,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
|
||||
"integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
|
||||
"integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2683,9 +2690,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
|
||||
"integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
|
||||
"integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2697,9 +2704,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2711,9 +2718,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
|
||||
"integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
|
||||
"integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2725,9 +2732,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -2738,10 +2745,10 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2753,9 +2760,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2767,9 +2774,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
|
||||
"integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
|
||||
"integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2781,9 +2788,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2795,9 +2802,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2809,9 +2816,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
|
||||
"integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
|
||||
"integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2823,9 +2830,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
|
||||
"integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
|
||||
"integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2837,9 +2844,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
|
||||
"integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
|
||||
"integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2851,9 +2858,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
|
||||
"integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
|
||||
"integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3931,14 +3938,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
|
||||
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -4479,9 +4486,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001727",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
||||
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
|
||||
"version": "1.0.30001731",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
|
||||
"integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5018,22 +5025,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz",
|
||||
"integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
"@epic-web/invariant": "^1.0.0",
|
||||
"cross-spawn": "^7.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
"cross-env": "dist/bin/cross-env.js",
|
||||
"cross-env-shell": "dist/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
@@ -5694,9 +5700,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.190",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
|
||||
"integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
|
||||
"version": "1.5.192",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz",
|
||||
"integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -7832,14 +7838,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/launch-editor": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz",
|
||||
"integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==",
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.0.tgz",
|
||||
"integrity": "sha512-R/PIF14L6e2eHkhvQPu7jDRCr0msfCYCxbYiLgkkAGi0dVPWuM+RrsPu0a5dpuNe0KWGL3jpAkOlv53xGfPheQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picocolors": "^1.0.0",
|
||||
"shell-quote": "^1.8.1"
|
||||
"picocolors": "^1.1.1",
|
||||
"shell-quote": "^1.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
@@ -10117,9 +10123,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
|
||||
"integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
|
||||
"integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10133,26 +10139,26 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.45.1",
|
||||
"@rollup/rollup-android-arm64": "4.45.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.45.1",
|
||||
"@rollup/rollup-darwin-x64": "4.45.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.45.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.45.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.45.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.45.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.45.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.45.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.45.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.45.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.45.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.45.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.46.2",
|
||||
"@rollup/rollup-android-arm64": "4.46.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.46.2",
|
||||
"@rollup/rollup-darwin-x64": "4.46.2",
|
||||
"@rollup/rollup-freebsd-arm64": "4.46.2",
|
||||
"@rollup/rollup-freebsd-x64": "4.46.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.46.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.46.2",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.46.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.46.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.46.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.46.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.46.2",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -11518,15 +11524,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz",
|
||||
"integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
|
||||
"integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"picomatch": "^4.0.2",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.40.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
@@ -11838,9 +11844,9 @@
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.100.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz",
|
||||
"integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==",
|
||||
"version": "5.101.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.0.tgz",
|
||||
"integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -12502,9 +12508,9 @@
|
||||
"devDependencies": {
|
||||
"@johmun/vue-tags-input": "^2",
|
||||
"@vue/compiler-sfc": "^3.5.11",
|
||||
"axios": "^1.8",
|
||||
"axios": "^1.11",
|
||||
"bootstrap-sass": "^3",
|
||||
"cross-env": "^7.0",
|
||||
"cross-env": "^10.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery": "^3",
|
||||
"laravel-mix": "^6.0",
|
||||
@@ -12519,7 +12525,7 @@
|
||||
"resources/assets/v2": {
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@fortawesome/fontawesome-free": "^7",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"admin-lte": "^4.0.0-rc4",
|
||||
"alpinejs": "^3.13.7",
|
||||
|
@@ -16,9 +16,9 @@
|
||||
"devDependencies": {
|
||||
"@johmun/vue-tags-input": "^2",
|
||||
"@vue/compiler-sfc": "^3.5.11",
|
||||
"axios": "^1.8",
|
||||
"axios": "^1.11",
|
||||
"bootstrap-sass": "^3",
|
||||
"cross-env": "^7.0",
|
||||
"cross-env": "^10.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery": "^3",
|
||||
"laravel-mix": "^6.0",
|
||||
|
@@ -16,7 +16,7 @@
|
||||
"vite-plugin-manifest-sri": "^0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@fortawesome/fontawesome-free": "^7",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"admin-lte": "^4.0.0-rc4",
|
||||
"alpinejs": "^3.13.7",
|
||||
|
@@ -25,7 +25,7 @@ export default class Dashboard {
|
||||
dashboard(start, end) {
|
||||
let startStr = format(start, '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) {
|
||||
|
@@ -315,7 +315,7 @@ let index = function () {
|
||||
// need to find the input thing
|
||||
console.log('Clicked edit button for account on index #' + index + ' and field ' + fieldName);
|
||||
const querySelector = 'input[data-field="' + fieldName + '"][data-index="' + index + '"]';
|
||||
console.log(querySelector);
|
||||
// console.log(querySelector);
|
||||
const newValue = document.querySelectorAll(querySelector)[0].value ?? '';
|
||||
if ('' === newValue) {
|
||||
return;
|
||||
|
@@ -40,24 +40,33 @@ export default () => ({
|
||||
loadingAccounts: false,
|
||||
accountList: [],
|
||||
convertToNative: false,
|
||||
convertToNativeAvailable: false,
|
||||
chartOptions: null,
|
||||
switchConvertToNative() {
|
||||
this.convertToNative = !this.convertToNative;
|
||||
setVariable('convertToNative', this.convertToNative);
|
||||
},
|
||||
localCacheKey(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() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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');
|
||||
let cachedData = window.store.get(chartCacheKey);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
console.log('Generate from cache: ', chartCacheKey);
|
||||
this.drawChart(this.generateOptions(cachedData));
|
||||
this.loading = false;
|
||||
return;
|
||||
@@ -67,6 +76,7 @@ export default () => ({
|
||||
this.chartData = response.data;
|
||||
// cache generated options:
|
||||
window.store.set(chartCacheKey, response.data);
|
||||
console.log('Generate FRESH!');
|
||||
this.drawChart(this.generateOptions(this.chartData));
|
||||
this.loading = false;
|
||||
});
|
||||
@@ -90,18 +100,23 @@ export default () => ({
|
||||
dataset.label = current.label;
|
||||
|
||||
// use the "native" currency code and use the "native_entries" as array
|
||||
// if (this.convertToNative) {
|
||||
// currencies.push(current.native_currency_code);
|
||||
// dataset.currency_code = current.native_currency_code;
|
||||
// collection = Object.values(current.native_entries);
|
||||
// yAxis = 'y' + current.native_currency_code;
|
||||
// }
|
||||
// if (!this.convertToNative) {
|
||||
if (this.convertToNative) {
|
||||
currencies.push(current.native_currency_code);
|
||||
dataset.currency_code = current.native_currency_code;
|
||||
if(!current.hasOwnProperty('native_entries')) {
|
||||
console.error('No native entries ('+this.convertToNative+') found for account: ', current);
|
||||
}
|
||||
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;
|
||||
dataset.currency_code = current.currency_code;
|
||||
currencies.push(current.currency_code);
|
||||
collection = Object.values(current.entries);
|
||||
// }
|
||||
}
|
||||
dataset.yAxisID = yAxis;
|
||||
dataset.data = collection;
|
||||
|
||||
@@ -201,7 +216,7 @@ export default () => ({
|
||||
balance.amount_formatted = formatMoney(balance.amount, balance.currency_code);
|
||||
return balance;
|
||||
});
|
||||
console.log(parent);
|
||||
// console.log(parent);
|
||||
|
||||
|
||||
// get groups for account:
|
||||
@@ -273,13 +288,16 @@ export default () => ({
|
||||
|
||||
init() {
|
||||
// console.log('accounts init');
|
||||
Promise.all([getVariable('viewRange', '1M'), getVariable('convertToNative', false), getVariable('language', 'en_US'),
|
||||
getConfiguration('cer.enabled', false)
|
||||
Promise.all([
|
||||
getVariable('viewRange', '1M'), // 0
|
||||
getVariable('convert_to_native', false), // 1
|
||||
getVariable('language', 'en_US'), // 2
|
||||
getConfiguration('cer.enabled', false) // 3
|
||||
]).then((values) => {
|
||||
//console.log('accounts after promises');
|
||||
this.convertToNative = values[1] && values[3];
|
||||
this.convertToNativeAvailable = values[3];
|
||||
afterPromises = true;
|
||||
//console.log('convertToNative in accounts.js: ', values);
|
||||
|
||||
// main dashboard chart:
|
||||
this.loadChart();
|
||||
@@ -296,7 +314,7 @@ export default () => ({
|
||||
this.loadChart();
|
||||
this.loadAccounts();
|
||||
});
|
||||
window.store.observe('convertToNative', () => {
|
||||
window.store.observe('convert_to_native', () => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -35,11 +35,21 @@ export default () => ({
|
||||
loading: false,
|
||||
boxData: 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() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
// 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();
|
||||
|
||||
//const cacheValid = window.store.get('cacheValid');
|
||||
@@ -76,7 +86,7 @@ export default () => ({
|
||||
continue;
|
||||
}
|
||||
let key = current.key;
|
||||
console.log('NOT NATIVE');
|
||||
// console.log('NOT NATIVE');
|
||||
if (key.startsWith('balance-in-')) {
|
||||
this.balanceBox.amounts.push(formatMoney(current.monetary_value, current.currency_code));
|
||||
continue;
|
||||
@@ -153,7 +163,7 @@ export default () => ({
|
||||
init() {
|
||||
// console.log('boxes init');
|
||||
// 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');
|
||||
afterPromises = true;
|
||||
this.convertToNative = values[1];
|
||||
@@ -167,7 +177,7 @@ export default () => ({
|
||||
this.boxData = null;
|
||||
this.loadBoxes();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -48,9 +48,19 @@ export default () => ({
|
||||
}
|
||||
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) {
|
||||
if (null !== chart) {
|
||||
chart.data.datasets = options.data.datasets;
|
||||
chart.data = options.data;
|
||||
chart.update();
|
||||
return;
|
||||
}
|
||||
@@ -59,8 +69,9 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey('ds_bdg_chart', {start: start, end: end});
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
const cacheKey = getCacheKey('ds_bdg_chart', {convertToNative: this.convertToNative, start: start, end: end});
|
||||
//const cacheValid = window.store.get('cacheValid');
|
||||
const cacheValid = false;
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
@@ -80,7 +91,7 @@ export default () => ({
|
||||
},
|
||||
generateOptions(data) {
|
||||
currencies = [];
|
||||
let options = getDefaultChartSettings('column');
|
||||
let options = getDefaultChartSettings('bar');
|
||||
options.options.locale = window.store.get('locale').replace('_', '-');
|
||||
options.options.plugins = {
|
||||
tooltip: {
|
||||
@@ -94,7 +105,7 @@ export default () => ({
|
||||
if (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: [],
|
||||
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'),
|
||||
data: [],
|
||||
borderWidth: 1,
|
||||
stack: 1,
|
||||
backgroundColor: getColors('spent', 'background'),
|
||||
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?
|
||||
let label = current.label + ' (' + current.currency_code + ')';
|
||||
options.data.labels.push(label);
|
||||
if (this.convertToNative) {
|
||||
currencies.push(current.native_currency_code);
|
||||
// series 0: spent
|
||||
options.data.datasets[0].data.push(parseFloat(current.native_entries.spent) * -1);
|
||||
// series 1: left
|
||||
options.data.datasets[1].data.push(parseFloat(current.native_entries.left));
|
||||
// series 2: overspent
|
||||
options.data.datasets[2].data.push(parseFloat(current.native_entries.overspent));
|
||||
}
|
||||
if (!this.convertToNative) {
|
||||
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));
|
||||
}
|
||||
// label = current.label + ' (' + current.currency_code + ') b';
|
||||
// options.data.labels.push(label);
|
||||
|
||||
currencies.push(current.currency_code);
|
||||
// series 0: budgeted
|
||||
options.data.datasets[0].data.push(parseFloat(current.entries.budgeted));
|
||||
// series 1: spent
|
||||
options.data.datasets[1].data.push(parseFloat(current.entries.spent) * -1);
|
||||
// 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.
|
||||
@@ -160,9 +154,10 @@ export default () => ({
|
||||
options.options.scales = {
|
||||
y: {
|
||||
ticks: {
|
||||
callback: function (context) {
|
||||
return formatMoney(context, currencies[0] ?? 'EUR');
|
||||
}
|
||||
// callback: function (context) {
|
||||
// return 'abc';
|
||||
// return formatMoney(context, currencies[0] ?? 'EUR');
|
||||
// }
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -172,7 +167,7 @@ export default () => ({
|
||||
|
||||
|
||||
init() {
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
afterPromises = true;
|
||||
if (false === this.loading) {
|
||||
@@ -189,7 +184,7 @@ export default () => ({
|
||||
this.loadChart();
|
||||
}
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -33,6 +33,17 @@ let afterPromises = false;
|
||||
export default () => ({
|
||||
loading: 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) {
|
||||
currencies = [];
|
||||
let options = getDefaultChartSettings('column');
|
||||
@@ -43,11 +54,6 @@ export default () => ({
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
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)) {
|
||||
series[code] = {
|
||||
name: code,
|
||||
@@ -65,9 +71,6 @@ export default () => ({
|
||||
let yAxis = 'y';
|
||||
let current = data[i];
|
||||
let code = current.currency_code;
|
||||
if (this.convertToNative) {
|
||||
code = current.native_currency_code;
|
||||
}
|
||||
|
||||
// loop series, add 0 if not present or add actual amount.
|
||||
for (const ii in series) {
|
||||
@@ -77,10 +80,6 @@ export default () => ({
|
||||
// this series' currency matches this column's currency.
|
||||
amount = parseFloat(current.amount);
|
||||
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)) {
|
||||
// there is a value for this particular currency. The amount from this column will be added.
|
||||
@@ -147,7 +146,7 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
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');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
@@ -183,7 +182,7 @@ export default () => ({
|
||||
},
|
||||
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];
|
||||
afterPromises = true;
|
||||
this.loadChart();
|
||||
@@ -195,7 +194,7 @@ export default () => ({
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -46,7 +46,8 @@ import {
|
||||
} from "chart.js";
|
||||
import 'chartjs-adapter-date-fns';
|
||||
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
|
||||
Chart.register({
|
||||
@@ -66,7 +67,26 @@ Chart.register({
|
||||
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 = {
|
||||
index,
|
||||
dates,
|
||||
boxes,
|
||||
accounts,
|
||||
|
@@ -36,7 +36,7 @@ export default () => ({
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
// 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');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
@@ -129,7 +129,7 @@ export default () => ({
|
||||
init() {
|
||||
// console.log('piggies init');
|
||||
apiData = [];
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
|
||||
afterPromises = true;
|
||||
this.convertToNative = values[0];
|
||||
@@ -144,7 +144,7 @@ export default () => ({
|
||||
apiData = [];
|
||||
this.loadPiggyBanks();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -29,17 +29,16 @@ import i18next from "i18next";
|
||||
Chart.register({SankeyController, Flow});
|
||||
|
||||
const SANKEY_CACHE_KEY = 'ds_sankey_data';
|
||||
let currencies = [];
|
||||
let afterPromises = false;
|
||||
let chart = null;
|
||||
let transactions = [];
|
||||
let convertToNative = false;
|
||||
let translations = {
|
||||
let currencies = [];
|
||||
let afterPromises = false;
|
||||
let chart = null;
|
||||
let transactions = [];
|
||||
let convertToNative = false;
|
||||
let translations = {
|
||||
category: null,
|
||||
unknown_category: null,
|
||||
in: null,
|
||||
out: null,
|
||||
// TODO
|
||||
unknown_source: null,
|
||||
unknown_dest: null,
|
||||
unknown_account: null,
|
||||
@@ -80,74 +79,97 @@ const getColor = function (key) {
|
||||
|
||||
// little helper
|
||||
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) {
|
||||
// category
|
||||
if ('category' === type && null !== name) {
|
||||
return translations.category + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
|
||||
function getObjectNameWithoutCurrency(type, name, direction) {
|
||||
if('category' === type) {
|
||||
let catName = null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
|
||||
let directionText = 'in' === direction ? translations.in : translations.out;
|
||||
return catName + ' (' + directionText + ')';
|
||||
}
|
||||
if ('category' === type && null === name) {
|
||||
return translations.unknown_category + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('account' === type) {
|
||||
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 ('account' === type && null === name) {
|
||||
return translations.unknown_account + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('budget' === type) {
|
||||
return null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"';
|
||||
}
|
||||
if ('account' === type && null !== name) {
|
||||
return name + (convertToNative ? ' (' + code + ')' : '');
|
||||
console.error('[a] Cannot handle: type:"' + type + '", dir: "' + direction + '"');
|
||||
}
|
||||
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 + ')';
|
||||
}
|
||||
|
||||
// budget 2x
|
||||
if ('budget' === type && null !== name) {
|
||||
return translations.budget + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('account' === type) {
|
||||
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 + ', ' + code + ')';
|
||||
}
|
||||
if ('budget' === type && null === name) {
|
||||
return translations.unknown_budget + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('budget' === type) {
|
||||
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 () => ({
|
||||
loading: 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() {
|
||||
let options = getDefaultChartSettings('sankey');
|
||||
|
||||
@@ -155,120 +177,22 @@ export default () => ({
|
||||
currencies = [];
|
||||
|
||||
// variables collected for the sankey chart:
|
||||
let amounts = {};
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.parseTransactionGroups(transactions);
|
||||
|
||||
let dataSet =
|
||||
// sankey chart has one data set.
|
||||
{
|
||||
label: 'Firefly III dashboard sankey chart',
|
||||
data: [],
|
||||
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 : ''),
|
||||
colorMode: 'gradient', // or 'from' or 'to'
|
||||
labels: labels,
|
||||
size: 'min', // or 'min' if flow overlap is preferred
|
||||
};
|
||||
for (let i in amounts) {
|
||||
if (amounts.hasOwnProperty(i)) {
|
||||
let amount = amounts[i];
|
||||
// sankey chart has one data set.
|
||||
{
|
||||
label: 'Firefly III dashboard sankey chart',
|
||||
data: [],
|
||||
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 : ''),
|
||||
colorMode: 'gradient', // or 'from' or 'to'
|
||||
labels: this.processedData.labels,
|
||||
size: 'min', // or 'min' if flow overlap is preferred
|
||||
};
|
||||
for (let i in this.processedData.amounts) {
|
||||
if (this.processedData.amounts.hasOwnProperty(i)) {
|
||||
let amount = this.processedData.amounts[i];
|
||||
dataSet.data.push({from: amount.from, to: amount.to, flow: amount.amount});
|
||||
}
|
||||
}
|
||||
@@ -276,6 +200,133 @@ export default () => ({
|
||||
|
||||
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) {
|
||||
if (null !== chart) {
|
||||
chart.data.datasets = options.data.datasets;
|
||||
@@ -286,12 +337,12 @@ export default () => ({
|
||||
|
||||
},
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
|
||||
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
transactions = cachedData;
|
||||
@@ -310,9 +361,9 @@ export default () => ({
|
||||
this.downloadTransactions(params);
|
||||
},
|
||||
downloadTransactions(params) {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {convertToNative: this.convertToNative, start: start, end: end});
|
||||
|
||||
//console.log('Downloading page ' + params.page + '...');
|
||||
const getter = new Get();
|
||||
@@ -348,26 +399,27 @@ export default () => ({
|
||||
init() {
|
||||
// console.log('sankey init');
|
||||
transactions = [];
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.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');
|
||||
convertToNative = values[0];
|
||||
|
||||
// console.log('sankey after promises');
|
||||
afterPromises = true;
|
||||
this.loadChart();
|
||||
// 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');
|
||||
afterPromises = true;
|
||||
this.loadChart();
|
||||
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
@@ -378,7 +430,7 @@ export default () => ({
|
||||
this.transactions = [];
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -44,6 +44,18 @@ function addObjectGroupInfo(data) {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -55,7 +67,7 @@ function parseBillInfo(data) {
|
||||
// paid transactions:
|
||||
transactions: [],
|
||||
// unpaid moments
|
||||
pay_dates: data.attributes.pay_dates,
|
||||
pay_dates: parsePayDates(data.attributes.pay_dates),
|
||||
paid: data.attributes.paid_dates.length > 0,
|
||||
};
|
||||
// set variables
|
||||
@@ -64,6 +76,7 @@ function parseBillInfo(data) {
|
||||
times: data.attributes.pay_dates.length,
|
||||
amount: result.expected_amount
|
||||
});
|
||||
// console.log(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -96,6 +109,21 @@ function parsePaidTransactions(paid_dates, bill) {
|
||||
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) {
|
||||
const getter = new Get();
|
||||
return getter.list(params)
|
||||
@@ -113,10 +141,18 @@ function downloadSubscriptions(params) {
|
||||
|
||||
// create and update the bill.
|
||||
let bill = parseBillInfo(current);
|
||||
|
||||
// if not yet paid, and pay_dates is not in current rage, ignore it.
|
||||
if (false === bill.paid && !isInRange(bill)) {
|
||||
console.warn('Bill "'+bill.name+'" is not paid and not in range, ignoring: ');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
bill.transactions = parsePaidTransactions(current.attributes.paid_dates, 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".
|
||||
// since bill is unpaid, this can only be in currency amount and native currency amount.
|
||||
const totalAmount = current.attributes.pay_dates.length * bill.amount;
|
||||
@@ -127,13 +163,10 @@ function downloadSubscriptions(params) {
|
||||
currency_code: bill.currency_code,
|
||||
paid: 0,
|
||||
unpaid: 0,
|
||||
native_currency_code: bill.native_currency_code,
|
||||
native_paid: 0,
|
||||
//native_unpaid: 0,
|
||||
};
|
||||
}
|
||||
|
||||
subscriptionData[objectGroupId].payment_info[bill.currency_code].unpaid += totalAmount;
|
||||
//subscriptionData[objectGroupId].payment_info[bill.currency_code].native_unpaid += totalNativeAmount;
|
||||
}
|
||||
|
||||
if (current.attributes.paid_dates.length > 0) {
|
||||
@@ -141,8 +174,6 @@ function downloadSubscriptions(params) {
|
||||
if (current.attributes.paid_dates.hasOwnProperty(ii)) {
|
||||
// bill is paid!
|
||||
// since bill is paid, 3 possible currencies:
|
||||
// native, currency, foreign currency.
|
||||
// foreign currency amount (converted to native or not) will be ignored.
|
||||
let currentJournal = current.attributes.paid_dates[ii];
|
||||
// new array for the currency
|
||||
if (!subscriptionData[objectGroupId].payment_info.hasOwnProperty(currentJournal.currency_code)) {
|
||||
@@ -150,15 +181,10 @@ function downloadSubscriptions(params) {
|
||||
currency_code: bill.currency_code,
|
||||
paid: 0,
|
||||
unpaid: 0,
|
||||
// native_currency_code: bill.native_currency_code,
|
||||
// native_paid: 0,
|
||||
//native_unpaid: 0,
|
||||
};
|
||||
}
|
||||
const amount = parseFloat(currentJournal.amount) * -1;
|
||||
// const nativeAmount = parseFloat(currentJournal.native_amount) * -1;
|
||||
subscriptionData[objectGroupId].payment_info[currentJournal.currency_code].paid += amount;
|
||||
// subscriptionData[objectGroupId].payment_info[currentJournal.currency_code].native_paid += nativeAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,6 +207,17 @@ export default () => ({
|
||||
loading: false,
|
||||
convertToNative: false,
|
||||
subscriptions: [],
|
||||
formatMoney(amount, currencyCode) {
|
||||
return formatMoney(amount, currencyCode);
|
||||
},
|
||||
eventListeners: {
|
||||
['@convert-to-native.window'](event){
|
||||
console.log('I heard that! (dashboard/subscriptions)');
|
||||
this.convertToNative = event.detail;
|
||||
this.startSubscriptions();
|
||||
}
|
||||
},
|
||||
|
||||
startSubscriptions() {
|
||||
this.loading = true;
|
||||
let start = new Date(window.store.get('start'));
|
||||
@@ -219,6 +256,7 @@ export default () => ({
|
||||
//console.log(group);
|
||||
}
|
||||
}
|
||||
console.log('Subscriptions: ', this.subscriptions);
|
||||
|
||||
// then assign to this.subscriptions.
|
||||
this.loading = false;
|
||||
@@ -268,7 +306,7 @@ export default () => ({
|
||||
},
|
||||
|
||||
init() {
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
afterPromises = true;
|
||||
|
||||
@@ -286,7 +324,7 @@ export default () => ({
|
||||
this.startSubscriptions();
|
||||
}
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -71,7 +71,7 @@ let index = function () {
|
||||
init() {
|
||||
// TODO need date range.
|
||||
// TODO handle page number
|
||||
this.getTransactions(this.page);``
|
||||
this.getTransactions(this.page);
|
||||
|
||||
// Your Javascript code to create the grid
|
||||
// dataTable = createGrid(document.querySelector('#grid'), gridOptions);
|
||||
|
@@ -26,7 +26,28 @@ $danger: #CD5029 !default;
|
||||
$primary: #1E6581 !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;}
|
||||
|
||||
/*
|
||||
@@ -59,16 +80,7 @@ h3.hover-expand:hover {
|
||||
appearance: auto;
|
||||
}
|
||||
|
||||
// Bootstrap
|
||||
// @import "bootstrap/scss/bootstrap";
|
||||
|
||||
// admin LTE
|
||||
@import "adminlte-filtered";
|
||||
|
||||
|
||||
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
|
||||
|
||||
// hover buttons
|
||||
// edit buttons
|
||||
.hidden-edit-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -76,12 +88,12 @@ td:not(:hover) .hidden-edit-button {
|
||||
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";
|
||||
|
@@ -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.
|
||||
// then again, it's not that slow.
|
||||
if (validCache && window.hasOwnProperty(name)) {
|
||||
// console.log('Get from window');
|
||||
console.log('Return configuration "' + name + '" from window: ' + window[name]);
|
||||
return Promise.resolve(window[name]);
|
||||
}
|
||||
// load from store2, if it's present.
|
||||
const fromStore = window.store.get(name);
|
||||
if (validCache && typeof fromStore !== 'undefined') {
|
||||
console.log('Return configuration "' + name + '" from store: ' + fromStore);
|
||||
return Promise.resolve(fromStore);
|
||||
}
|
||||
let getter = (new Get);
|
||||
return getter.getByName(name).then((response) => {
|
||||
// console.log('Get "' + name + '" from API');
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
}).catch(() => {
|
||||
console.log('Return configuration "' + name + '" from API: ' + parseConfigurationResponse(name, response));
|
||||
return Promise.resolve(parseConfigurationResponse(name, response));
|
||||
}).catch((error) => {
|
||||
console.log('Returning "'+name+'" from DEFAULT: ' + defaultValue);
|
||||
console.warn(error);
|
||||
return defaultValue;
|
||||
});
|
||||
}
|
||||
export function parseConfigurationResponse(name, response) {
|
||||
let value = response.data.data.value;
|
||||
window.store.set(name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
// then again, it's not that slow.
|
||||
if (validCache && window.hasOwnProperty(name)) {
|
||||
console.log('Returning "'+name+'" from window: ' + window[name]);
|
||||
return Promise.resolve(window[name]);
|
||||
}
|
||||
// load from store2, if it's present.
|
||||
const fromStore = window.store.get(name);
|
||||
if (validCache && typeof fromStore !== 'undefined') {
|
||||
console.log('Returning "'+name+'" from store: ' + fromStore);
|
||||
return Promise.resolve(fromStore);
|
||||
}
|
||||
let getter = (new Get);
|
||||
|
||||
return getter.getByName(name).then((response) => {
|
||||
console.log('Returning "'+name+'" from server: ' + parseResponse(name, response));
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
}).catch((error) => {
|
||||
if('' === defaultValue) {
|
||||
@@ -47,6 +50,7 @@ export function getVariable(name, defaultValue = null) {
|
||||
// POST it and then return it anyway.
|
||||
let poster = (new Post);
|
||||
return poster.post(name, defaultValue).then((response) => {
|
||||
console.log('Returning "'+name+'" from POST: ' + parseResponse(name, response));
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
});
|
||||
});
|
||||
|
@@ -28,19 +28,25 @@ export function setVariable(name, value = null) {
|
||||
// then again, it's not that slow.
|
||||
|
||||
// set in window.x
|
||||
// window[name] = value;
|
||||
window[name] = value;
|
||||
|
||||
// set in store:
|
||||
window.store.set(name, value);
|
||||
|
||||
// post to user preferences (because why not):
|
||||
let putter = new Put();
|
||||
putter.put(name, value).then((response) => {
|
||||
}).catch(() => {
|
||||
return putter.put(name, value).then((response) => {
|
||||
console.log('set "'+name+'" to value: ', value);
|
||||
return Promise.resolve(response);
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
// preference does not exist (yet).
|
||||
// POST it
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
@@ -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) {
|
||||
return {
|
||||
options: {
|
||||
|
@@ -92,6 +92,14 @@ function getColors(type, field) {
|
||||
backgroundColor: background.rgbString(),
|
||||
};
|
||||
break;
|
||||
case 'budgeted':
|
||||
background = new Color(green.rgbString());
|
||||
background.lighten(0.38);
|
||||
colors = {
|
||||
borderColor: green.rgbString(),
|
||||
backgroundColor: background.rgbString(),
|
||||
};
|
||||
break;
|
||||
case 'overspent':
|
||||
background = new Color(red.rgbString());
|
||||
background.lighten(0.22);
|
||||
|
@@ -21,6 +21,14 @@
|
||||
import {format} from "date-fns";
|
||||
|
||||
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('_', '-');
|
||||
|
||||
return Intl.NumberFormat(locale, {
|
||||
|
@@ -234,6 +234,7 @@ return [
|
||||
'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!',
|
||||
'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',
|
||||
@@ -1863,6 +1864,9 @@ return [
|
||||
'remove_budgeted_amount' => 'Remove budgeted amount in :currency',
|
||||
|
||||
// bills:
|
||||
'left_to_pay_lc' => 'left to pay',
|
||||
'less_than_expected' => 'less than expected',
|
||||
'more_than_expected' => 'more than expected',
|
||||
'skip_help_text' => 'Use the skip field to create bi-monthly (skip = 1) or other custom intervals.',
|
||||
'subscription' => 'Subscription',
|
||||
'not_expected_period' => 'Not expected this period',
|
||||
|
@@ -7,7 +7,7 @@
|
||||
@include('partials.dashboard.boxes')
|
||||
|
||||
<!-- 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 -->
|
||||
<div class="col-xl-8 col-lg-12 col-sm-12 col-xs-12">
|
||||
<!-- row with account chart -->
|
||||
@@ -37,11 +37,11 @@
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<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 class="card-body">
|
||||
<p>
|
||||
TODO
|
||||
List of income + sum
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
</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">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
@@ -58,9 +58,17 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Buttons.
|
||||
</p>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-4 col-form-label">Convert to native</label>
|
||||
<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 class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ __('firefly.close') }}</button>
|
||||
|
@@ -9,24 +9,6 @@
|
||||
<div class="card-body p-0" style="position: relative;height:400px;">
|
||||
<canvas id="account-chart"></canvas>
|
||||
</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>
|
||||
|
@@ -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="small-box text-bg-primary">
|
||||
<div class="inner balance-box">
|
||||
|
@@ -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="card">
|
||||
<div class="card-header">
|
||||
|
@@ -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="card">
|
||||
<div class="card-header">
|
||||
|
@@ -3,10 +3,8 @@
|
||||
<template x-for="group in piggies">
|
||||
<div class="card mb-2">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="{{ route('piggy-banks.index') }}"
|
||||
title="{{ __('firefly.go_to_piggies') }}">{{ __('firefly.piggy_banks') }}
|
||||
(<span
|
||||
x-text="group.title"></span>)</a></h3>
|
||||
<h3 class="card-title"><a href="{{ route('piggy-banks.index') }}" title="{{ __('firefly.go_to_piggies') }}">{{ __('firefly.piggy_banks') }}
|
||||
(<span x-text="group.title"></span>)</a></h3>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<template x-for="piggy in group.piggies">
|
||||
|
@@ -6,7 +6,7 @@
|
||||
>{{ __('firefly.income_and_expense') }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body" x-data="sankey">
|
||||
<div class="card-body" x-data="sankey" x-bind="eventListeners">
|
||||
<canvas id="sankey-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="col" x-data="subscriptions">
|
||||
<div class="col" x-data="subscriptions" x-bind="eventListeners">
|
||||
<template x-for="group in subscriptions">
|
||||
<div class="card mb-2">
|
||||
<div class="card-header">
|
||||
@@ -9,15 +9,21 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<template x-for="pi in group.payment_info">
|
||||
<div class="row mb-2">
|
||||
<template x-for="pie in group.payment_info">
|
||||
<div :class='group.col_size'>
|
||||
<canvas :id='"pie_" + group.id + "_" + pie.currency_code'
|
||||
:width="group.width"
|
||||
x-init="drawPieChart(group.id, group.title, pie)"></canvas>
|
||||
<div class="col">
|
||||
<div class="progress" role="progressbar" aria-label="Example with label"
|
||||
:aria-valuenow="(pi.paid*-1/pi.unpaid)*100" aria-valuemin="0" aria-valuemax="100">
|
||||
<div class="progress-bar progress-bar-striped" :style="'width: ' + (pi.paid*-1/pi.unpaid)*100 + '%'">
|
||||
<span x-text="formatMoney(pi.paid*-1,pi.currency_code)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p>
|
||||
<small>~ <span x-text="formatMoney(pi.unpaid, pi.currency_code)"></span> {{ __('firefly.left_to_pay_lc') }}</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="row mb-2">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
@@ -30,9 +36,7 @@
|
||||
<template x-for="bill in group.bills">
|
||||
<tr>
|
||||
<td>
|
||||
<a :href="'{{ route('subscriptions.show',['']) }}/' + bill.id" :title="bill.name">
|
||||
<span x-text="bill.name"></span>
|
||||
</a>
|
||||
<a :href="'{{ route('subscriptions.show',['']) }}/' + bill.id" :title="bill.name"><span x-text="bill.name"></span></a>
|
||||
<template x-if="bill.paid">
|
||||
<small class="text-muted"><br>{{ __('firefly.paid') }}</small>
|
||||
</template>
|
||||
@@ -57,8 +61,17 @@
|
||||
<ul class="list-unstyled">
|
||||
<template x-for="transaction in bill.transactions">
|
||||
<li>
|
||||
<span x-text="transaction.amount"></span>
|
||||
(<span x-text="transaction.percentage"></span>%)
|
||||
<span :title="transaction.amount" x-text="transaction.amount"></span>
|
||||
<template x-if="transaction.percentage < 0">
|
||||
<span>
|
||||
(<span :title="transaction.percentage + '% {{ __("firefly.less_than_expected") }}'" x-text="transaction.percentage"></span>%)
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="transaction.percentage > 0">
|
||||
<span>
|
||||
(<span :title="transaction.percentage + '% {{ __("firefly.more_than_expected") }}'" x-text="transaction.percentage"></span>%)
|
||||
</span>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
Reference in New Issue
Block a user