mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-03-19 03:44:04 +00:00
Compare commits
26 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 |
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",
|
||||
|
||||
@@ -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,19 +110,37 @@ 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,
|
||||
@@ -253,4 +274,39 @@ class BudgetController extends Controller
|
||||
|
||||
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);
|
||||
|
||||
@@ -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'],
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
36
composer.lock
generated
36
composer.lock
generated
@@ -11065,16 +11065,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.20",
|
||||
"version": "2.1.21",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "a9ccfef95210f92ba6feea6e8d1eef42b5605499"
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a9ccfef95210f92ba6feea6e8d1eef42b5605499",
|
||||
"reference": "a9ccfef95210f92ba6feea6e8d1eef42b5605499",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11119,7 +11119,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-26T20:45:26+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-28',
|
||||
'build_time' => 1753673705,
|
||||
'version' => 'develop/2025-07-31',
|
||||
'build_time' => 1753936691,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 26,
|
||||
|
||||
|
||||
228
package-lock.json
generated
228
package-lock.json
generated
@@ -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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.0.tgz",
|
||||
"integrity": "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.0.tgz",
|
||||
"integrity": "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.0.tgz",
|
||||
"integrity": "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.0.tgz",
|
||||
"integrity": "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.0.tgz",
|
||||
"integrity": "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==",
|
||||
"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"
|
||||
],
|
||||
@@ -2739,9 +2746,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==",
|
||||
"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"
|
||||
],
|
||||
@@ -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.191",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz",
|
||||
"integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==",
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.0.tgz",
|
||||
"integrity": "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==",
|
||||
"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.46.0",
|
||||
"@rollup/rollup-android-arm64": "4.46.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.46.0",
|
||||
"@rollup/rollup-darwin-x64": "4.46.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.46.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.46.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.46.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.46.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.46.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.46.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.46.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.46.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.46.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.46.0",
|
||||
"@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"
|
||||
}
|
||||
},
|
||||
@@ -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": {
|
||||
@@ -12504,7 +12510,7 @@
|
||||
"@vue/compiler-sfc": "^3.5.11",
|
||||
"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",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@vue/compiler-sfc": "^3.5.11",
|
||||
"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",
|
||||
|
||||
@@ -39,7 +39,7 @@ export default () => ({
|
||||
['@convert-to-native.window'](event){
|
||||
this.convertToNative = event.detail;
|
||||
this.accountList = [];
|
||||
console.log('I heard that!');
|
||||
console.log('I heard that! (dashboard/boxes)');
|
||||
this.boxData = null;
|
||||
this.loadBoxes();
|
||||
}
|
||||
|
||||
@@ -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,7 +69,7 @@ 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 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -71,9 +71,11 @@ let index = function () {
|
||||
return {
|
||||
convertToNative: false,
|
||||
saveNativeSettings(event) {
|
||||
setVariable('convert_to_native', event.currentTarget.checked);
|
||||
this.$dispatch('convert-to-native', event.currentTarget.checked);
|
||||
console.log('saveNativeSettings + dispatch.');
|
||||
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) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,75 +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');
|
||||
|
||||
@@ -156,125 +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;
|
||||
if(this.convertToNative && (!transaction.hasOwnProperty('native_amount') || null === transaction.native_amount)) {
|
||||
// skip this transaction, it has no native amount.
|
||||
console.error('No native amount for transaction #' + group.id + ' ('+this.convertToNative+')');
|
||||
continue;
|
||||
}
|
||||
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});
|
||||
}
|
||||
}
|
||||
@@ -282,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;
|
||||
@@ -292,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;
|
||||
@@ -316,8 +361,8 @@ export default () => ({
|
||||
this.downloadTransactions(params);
|
||||
},
|
||||
downloadTransactions(params) {
|
||||
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, {convertToNative: this.convertToNative, start: start, end: end});
|
||||
|
||||
//console.log('Downloading page ' + params.page + '...');
|
||||
@@ -356,25 +401,25 @@ export default () => ({
|
||||
transactions = [];
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
convertToNative = values[0];
|
||||
convertToNative = values[0];
|
||||
|
||||
// some translations:
|
||||
translations.all_money = i18next.t('firefly.all_money');
|
||||
translations.category = i18next.t('firefly.category');
|
||||
translations.in = i18next.t('firefly.money_flowing_in');
|
||||
translations.out = i18next.t('firefly.money_flowing_out');
|
||||
translations.unknown_category = i18next.t('firefly.unknown_category_plain');
|
||||
translations.unknown_source = i18next.t('firefly.unknown_source_plain');
|
||||
translations.unknown_dest = i18next.t('firefly.unknown_dest_plain');
|
||||
translations.unknown_account = i18next.t('firefly.unknown_any_plain');
|
||||
translations.unknown_budget = i18next.t('firefly.unknown_budget_plain');
|
||||
translations.expense_account = i18next.t('firefly.expense_account');
|
||||
translations.revenue_account = i18next.t('firefly.revenue_account');
|
||||
translations.budget = i18next.t('firefly.budget');
|
||||
// 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();
|
||||
// console.log('sankey after promises');
|
||||
afterPromises = true;
|
||||
this.loadChart();
|
||||
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
|
||||
@@ -163,14 +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) {
|
||||
@@ -178,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)) {
|
||||
@@ -187,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,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'));
|
||||
@@ -256,6 +256,7 @@ export default () => ({
|
||||
//console.log(group);
|
||||
}
|
||||
}
|
||||
console.log('Subscriptions: ', this.subscriptions);
|
||||
|
||||
// then assign to this.subscriptions.
|
||||
this.loading = false;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -35,8 +35,9 @@ export function setVariable(name, value = null) {
|
||||
|
||||
// post to user preferences (because why not):
|
||||
let putter = new Put();
|
||||
putter.put(name, value).then((response) => {
|
||||
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).
|
||||
@@ -44,6 +45,8 @@ export function setVariable(name, value = null) {
|
||||
let poster = (new Post);
|
||||
poster.post(name, value).then((response) => {
|
||||
console.log('POST "'+name+'" to value: ', value);
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,18 +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>
|
||||
-->
|
||||
Here was chart.
|
||||
<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>
|
||||
@@ -33,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>
|
||||
@@ -60,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