mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-21 12:54:32 +00:00
Compare commits
96 Commits
v6.2.20
...
develop-20
Author | SHA1 | Date | |
---|---|---|---|
|
a476b4259c | ||
|
0c03ec5ddd | ||
|
2d95b7f8ef | ||
|
34cc79ce4f | ||
|
6ff9943fe0 | ||
|
a0166b45e4 | ||
|
d27b035b20 | ||
|
f3b387fc22 | ||
|
03904ffcde | ||
|
a7973190c2 | ||
|
671ff95f22 | ||
|
01181ceea9 | ||
|
97643639d1 | ||
|
424783c47b | ||
|
ea0ced70b2 | ||
|
1a633e64ef | ||
|
30da3f4399 | ||
|
6bdff95d87 | ||
|
895ae279d5 | ||
|
8b57f45963 | ||
|
a2d2b7edd3 | ||
|
3d65f00c6e | ||
|
333004c4d9 | ||
|
7451659824 | ||
|
ec89c23ace | ||
|
92d07d346f | ||
|
a7ac894af2 | ||
|
20f89e3a7c | ||
|
5d2f11c3c7 | ||
|
1eea79e431 | ||
|
1aaaac67ca | ||
|
1eb86639c9 | ||
|
28b911c4ee | ||
|
c525c70ec0 | ||
|
1f7d6e218b | ||
|
a69b6d9ce2 | ||
|
28b2ddde18 | ||
|
a16cc73c77 | ||
|
46395e350a | ||
|
f62e49090c | ||
|
af3b40a314 | ||
|
c3cea0fa9e | ||
|
6cbdb2ce70 | ||
|
b78460100d | ||
|
bf6e1cb0e1 | ||
|
6a53f5031c | ||
|
ae15ec01e8 | ||
|
fe3c7c47c4 | ||
|
68b934010c | ||
|
22852bd238 | ||
|
5b3b1804f3 | ||
|
f2588eb343 | ||
|
64a643ceec | ||
|
1add505644 | ||
|
9663eb6a19 | ||
|
f30a24a02f | ||
|
68655d60a6 | ||
|
63b0efcd81 | ||
|
93284682c8 | ||
|
3bafcb6ad2 | ||
|
942d027556 | ||
|
a60882d5f5 | ||
|
680f554981 | ||
|
20e4dc07ce | ||
|
184d8eb027 | ||
|
59725b088a | ||
|
32fca4a9f5 | ||
|
7dccf6ec48 | ||
|
917665feac | ||
|
06c50b68c2 | ||
|
7035c399d8 | ||
|
7c0ac5805c | ||
|
3424741583 | ||
|
baf0297994 | ||
|
31d06752fa | ||
|
8a27154798 | ||
|
6d87e38ec0 | ||
|
ccdc30a6fb | ||
|
90005538d3 | ||
|
f4e0428ebc | ||
|
bd1326eca9 | ||
|
bdfa834251 | ||
|
4a9aeb4e44 | ||
|
3886c0fbde | ||
|
d998eff56e | ||
|
d73df9bf0a | ||
|
754f2f3a34 | ||
|
43fd7c928a | ||
|
05768c2e73 | ||
|
3feb2c9955 | ||
|
7d9f3ac473 | ||
|
8a5755c8f1 | ||
|
a75a760019 | ||
|
78d1a130d2 | ||
|
79fd43f32b | ||
|
7a7bd65a27 |
@@ -33,6 +33,7 @@ $finder = PhpCsFixer\Finder::create()
|
||||
|
||||
|
||||
$config = (new PhpCsFixer\Config())
|
||||
// ->setUnsupportedPhpVersionAllowed(true) // use this when PHP 8.5 comes out.
|
||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||
;
|
||||
return $config->setRules(
|
||||
|
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.76.0",
|
||||
"version": "v3.85.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "0e3c484cef0ae9314b0f85986a36296087432c40"
|
||||
"reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/0e3c484cef0ae9314b0f85986a36296087432c40",
|
||||
"reference": "0e3c484cef0ae9314b0f85986a36296087432c40",
|
||||
"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.76.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.85.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -507,7 +507,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-30T14:15:06+00:00"
|
||||
"time": "2025-07-29T22:22:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
|
@@ -26,7 +26,7 @@ SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
cd $SCRIPT_DIR/php-cs-fixer
|
||||
composer update --quiet
|
||||
rm -f .php-cs-fixer.cache
|
||||
PHP_CS_FIXER_IGNORE_ENV=true ./vendor/bin/php-cs-fixer fix \
|
||||
./vendor/bin/php-cs-fixer fix \
|
||||
--config $SCRIPT_DIR/php-cs-fixer/.php-cs-fixer.php \
|
||||
--format=txt \
|
||||
-v \
|
||||
|
1
.github/workflows/cleanup.yml
vendored
1
.github/workflows/cleanup.yml
vendored
@@ -66,7 +66,6 @@ jobs:
|
||||
'label-actions.yml',
|
||||
'lock.yml',
|
||||
'release.yml',
|
||||
'sonarcloud.yml',
|
||||
'stale.yml'
|
||||
]
|
||||
|
||||
|
71
.github/workflows/sonarcloud.yml
vendored
71
.github/workflows/sonarcloud.yml
vendored
@@ -1,71 +0,0 @@
|
||||
name: 'Code - Run Sonarcloud'
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
APP_KEY: TestTestTestTestTestTestTestTest
|
||||
jobs:
|
||||
sonarcloud:
|
||||
name: SonarCloud
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup PHP with Xdebug
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
coverage: xdebug
|
||||
extensions: >-
|
||||
bcmath
|
||||
curl
|
||||
fileinfo
|
||||
iconv
|
||||
intl
|
||||
json
|
||||
sqlite3
|
||||
mbstring
|
||||
openssl
|
||||
pdo
|
||||
session
|
||||
simplexml
|
||||
sodium
|
||||
tokenizer
|
||||
xml
|
||||
xmlwriter
|
||||
|
||||
- name: Copy standard configuration
|
||||
run: cp .env.testing .env
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --prefer-dist --no-interaction --no-progress --no-scripts
|
||||
|
||||
- name: "Create database file"
|
||||
run: |
|
||||
touch storage/database/database.sqlite
|
||||
wget -q https://github.com/firefly-iii/test-fixtures/raw/refs/heads/main/test-database.sqlite -O storage/database/database.sqlite
|
||||
|
||||
- name: "Upgrades the database to the latest version"
|
||||
run: |
|
||||
php artisan firefly-iii:upgrade-database
|
||||
chmod 600 storage/oauth-public.key
|
||||
chmod 600 storage/oauth-private.key
|
||||
|
||||
- name: "Integrity Database Report"
|
||||
run: php artisan firefly-iii:report-integrity
|
||||
|
||||
- name: "Run tests with coverage"
|
||||
run: composer coverage
|
||||
|
||||
- name: Fix code coverage paths
|
||||
run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' coverage.xml
|
||||
|
||||
- name: SonarCloud Scan
|
||||
uses: SonarSource/sonarqube-scan-action@v5.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_PERSONAL_ACCESS_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
@@ -24,16 +24,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Api\V1\Requests\Data\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Chart\ChartData;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
@@ -42,6 +42,7 @@ use FireflyIII\Support\Http\Api\ApiSupport;
|
||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AccountController
|
||||
@@ -86,10 +87,12 @@ class AccountController extends Controller
|
||||
// move date to end of day
|
||||
$queryParameters['start']->startOfDay();
|
||||
$queryParameters['end']->endOfDay();
|
||||
Log::debug(sprintf('dashboard(), convert to native: %s', var_export($this->convertToNative, true)));
|
||||
|
||||
// loop each account, and collect info:
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Account #%d ("%s")', $account->id, $account->name));
|
||||
$this->renderAccountData($queryParameters, $account);
|
||||
}
|
||||
|
||||
@@ -101,15 +104,22 @@ class AccountController extends Controller
|
||||
*/
|
||||
private function renderAccountData(array $params, Account $account): void
|
||||
{
|
||||
$currency = $this->repository->getAccountCurrency($account);
|
||||
Log::debug(sprintf('Now in %s(array, #%d)', __METHOD__, $account->id));
|
||||
$currency = $this->repository->getAccountCurrency($account);
|
||||
$currentStart = clone $params['start'];
|
||||
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
|
||||
|
||||
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
$nativePrevious = null;
|
||||
if (!$currency instanceof TransactionCurrency) {
|
||||
$currency = $this->default;
|
||||
}
|
||||
$currentSet = [
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
|
||||
// the currency that belongs to the account.
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
@@ -121,18 +131,33 @@ class AccountController extends Controller
|
||||
'period' => '1D',
|
||||
'entries' => [],
|
||||
];
|
||||
$currentStart = clone $params['start'];
|
||||
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
|
||||
if ($this->convertToNative) {
|
||||
$currentSet['native_entries'] = [];
|
||||
$currentSet['native_currency_id'] = (string)$this->nativeCurrency->id;
|
||||
$currentSet['native_currency_code'] = $this->nativeCurrency->code;
|
||||
$currentSet['native_currency_symbol'] = $this->nativeCurrency->symbol;
|
||||
$currentSet['native_currency_decimal_places'] = $this->nativeCurrency->decimal_places;
|
||||
$nativePrevious = array_values($range)[0]['native_balance'];
|
||||
}
|
||||
|
||||
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
while ($currentStart <= $params['end']) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
|
||||
|
||||
// do the same for the native balance, if relevant:
|
||||
$nativeBalance = null;
|
||||
if ($this->convertToNative) {
|
||||
$nativeBalance = array_key_exists($format, $range) ? $range[$format]['native_balance'] : $nativePrevious;
|
||||
$nativePrevious = $nativeBalance;
|
||||
$currentSet['native_entries'][$label] = $nativeBalance;
|
||||
}
|
||||
|
||||
$currentStart->addDay();
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
}
|
||||
$this->chartData->add($currentSet);
|
||||
}
|
||||
@@ -146,19 +171,84 @@ class AccountController extends Controller
|
||||
public function overview(DateRequest $request): JsonResponse
|
||||
{
|
||||
// parameters for chart:
|
||||
$dates = $request->getAll();
|
||||
$dates = $request->getAll();
|
||||
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = $dates['start'];
|
||||
$start = $dates['start'];
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $dates['end'];
|
||||
$end = $dates['end'];
|
||||
|
||||
// set dates to end of day + start of day:
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
|
||||
// user's preferences
|
||||
$frontPageIds = $this->getFrontPageAccountIds();
|
||||
$accounts = $this->repository->getAccountsById($frontPageIds);
|
||||
$chartData = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Rendering chart data for account %s (%d)', $account->name, $account->id));
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
|
||||
$currentStart = clone $start;
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
$nativePrevious = null;
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'start_date' => $start->toAtomString(),
|
||||
'end_date' => $end->toAtomString(),
|
||||
'type' => 'line', // line, area or bar
|
||||
'yAxisID' => 0, // 0, 1, 2
|
||||
'entries' => [],
|
||||
];
|
||||
|
||||
// add "native_entries" if convertToNative is true:
|
||||
if ($this->convertToNative) {
|
||||
$currentSet['native_entries'] = [];
|
||||
$currentSet['native_currency_id'] = (string)$this->nativeCurrency->id;
|
||||
$currentSet['native_currency_code'] = $this->nativeCurrency->code;
|
||||
$currentSet['native_currency_symbol'] = $this->nativeCurrency->symbol;
|
||||
$currentSet['native_currency_decimal_places'] = $this->nativeCurrency->decimal_places;
|
||||
$nativePrevious = array_values($range)[0]['native_balance'];
|
||||
|
||||
}
|
||||
|
||||
// also get the native balance if convertToNative is true:
|
||||
while ($currentStart <= $end) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
|
||||
// balance is based on "balance" from the $range variable.
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
|
||||
// do the same for the native balance, if relevant:
|
||||
$nativeBalance = null;
|
||||
if ($this->convertToNative) {
|
||||
$nativeBalance = array_key_exists($format, $range) ? $range[$format]['native_balance'] : $nativePrevious;
|
||||
$nativePrevious = $nativeBalance;
|
||||
$currentSet['native_entries'][$label] = $nativeBalance;
|
||||
}
|
||||
|
||||
$currentStart->addDay();
|
||||
|
||||
}
|
||||
$chartData[] = $currentSet;
|
||||
}
|
||||
|
||||
return response()->json($chartData);
|
||||
}
|
||||
|
||||
private function getFrontPageAccountIds(): array
|
||||
{
|
||||
$defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
|
||||
|
||||
/** @var Preference $frontpage */
|
||||
@@ -169,41 +259,6 @@ class AccountController extends Controller
|
||||
$frontpage->save();
|
||||
}
|
||||
|
||||
// get accounts:
|
||||
$accounts = $this->repository->getAccountsById($frontpage->data);
|
||||
$chartData = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
|
||||
$field = $this->convertToNative && $currency->id !== $this->nativeCurrency->id ? 'native_balance' : 'balance';
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'start_date' => $start->toAtomString(),
|
||||
'end_date' => $end->toAtomString(),
|
||||
'type' => 'line', // line, area or bar
|
||||
'yAxisID' => 0, // 0, 1, 2
|
||||
'entries' => [],
|
||||
];
|
||||
// TODO this code is also present in the V2 chart account controller so this method is due to be deprecated.
|
||||
$currentStart = clone $start;
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
|
||||
$previous = array_values($range)[0][$field];
|
||||
while ($currentStart <= $end) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
$balance = array_key_exists($format, $range) ? $range[$format][$field] : $previous;
|
||||
$previous = $balance;
|
||||
$currentStart->addDay();
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
}
|
||||
$chartData[] = $currentSet;
|
||||
}
|
||||
|
||||
return response()->json($chartData);
|
||||
return $frontpage->data ?? $defaultSet;
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -77,6 +78,8 @@ class BudgetController extends Controller
|
||||
|
||||
/**
|
||||
* TODO see autocomplete/accountcontroller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse
|
||||
{
|
||||
@@ -107,23 +110,41 @@ class BudgetController extends Controller
|
||||
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get all limits:
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
|
||||
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
|
||||
|
||||
/**
|
||||
* @var int $currencyId
|
||||
* @var array $row
|
||||
*/
|
||||
foreach ($expenses as $currencyId => $row) {
|
||||
// budgeted, left and overspent are now 0.
|
||||
$limit = $this->filterLimit($currencyId, $limits);
|
||||
if (null !== $limit) {
|
||||
$row['budgeted'] = $limit->amount;
|
||||
$row['left'] = bcsub($row['budgeted'], bcmul($row['spent'], '-1'));
|
||||
$row['overspent'] = bcmul($row['left'], '-1');
|
||||
$row['left'] = 1 === bccomp($row['left'], '0') ? $row['left'] : '0';
|
||||
$row['overspent'] = 1 === bccomp($row['overspent'], '0') ? $row['overspent'] : '0';
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
|
||||
// if no limits
|
||||
if (0 === $limits->count()) {
|
||||
// return as a single item in an array
|
||||
$rows = $this->noBudgetLimits($budget, $start, $end);
|
||||
}
|
||||
if ($limits->count() > 0) {
|
||||
$rows = $this->budgetLimits($budget, $limits);
|
||||
}
|
||||
// if (0 === $limits->count()) {
|
||||
// return as a single item in an array
|
||||
// $rows = $this->noBudgetLimits($budget, $start, $end);
|
||||
// }
|
||||
|
||||
// is always an array
|
||||
$return = [];
|
||||
$return = [];
|
||||
foreach ($rows as $row) {
|
||||
$current = [
|
||||
'label' => $budget->name,
|
||||
'currency_id' => (string) $row['currency_id'],
|
||||
'currency_id' => (string)$row['currency_id'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_name' => $row['currency_name'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
@@ -131,6 +152,7 @@ class BudgetController extends Controller
|
||||
'start' => $row['start'],
|
||||
'end' => $row['end'],
|
||||
'entries' => [
|
||||
'budgeted' => $row['budgeted'],
|
||||
'spent' => $row['spent'],
|
||||
'left' => $row['left'],
|
||||
'overspent' => $row['overspent'],
|
||||
@@ -159,11 +181,9 @@ class BudgetController extends Controller
|
||||
* Shared between the "noBudgetLimits" function and "processLimit". Will take a single set of expenses and return
|
||||
* its info.
|
||||
*
|
||||
* @param array<int, array<int, string>> $array
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function processExpenses(int $budgetId, array $array, Carbon $start, Carbon $end): array
|
||||
private function processExpenses(int $budgetId, array $spent, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
@@ -174,16 +194,17 @@ class BudgetController extends Controller
|
||||
* @var int $currencyId
|
||||
* @var array $block
|
||||
*/
|
||||
foreach ($array as $currencyId => $block) {
|
||||
foreach ($spent as $currencyId => $block) {
|
||||
$this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
|
||||
$return[$currencyId] ??= [
|
||||
'currency_id' => (string) $currencyId,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_code' => $block['currency_code'],
|
||||
'currency_name' => $block['currency_name'],
|
||||
'currency_symbol' => $block['currency_symbol'],
|
||||
'currency_decimal_places' => (int) $block['currency_decimal_places'],
|
||||
'currency_decimal_places' => (int)$block['currency_decimal_places'],
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
'budgeted' => '0',
|
||||
'spent' => '0',
|
||||
'left' => '0',
|
||||
'overspent' => '0',
|
||||
@@ -193,7 +214,7 @@ class BudgetController extends Controller
|
||||
// var_dump($return);
|
||||
/** @var array $journal */
|
||||
foreach ($currentBudgetArray['transaction_journals'] as $journal) {
|
||||
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], (string) $journal['amount']);
|
||||
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], (string)$journal['amount']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,16 +261,52 @@ class BudgetController extends Controller
|
||||
$filtered = array_filter($spent, fn ($entry) => $entry['currency_id'] === $limitCurrencyId);
|
||||
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
||||
if (1 === count($result)) {
|
||||
$compare = bccomp($limit->amount, (string) app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||
$compare = bccomp($limit->amount, (string)app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||
$result[$limitCurrencyId]['budgeted'] = $limit->amount;
|
||||
if (1 === $compare) {
|
||||
// convert this amount into the native currency:
|
||||
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, (string) $result[$limitCurrencyId]['spent']);
|
||||
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, (string)$result[$limitCurrencyId]['spent']);
|
||||
}
|
||||
if ($compare <= 0) {
|
||||
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, (string) $result[$limitCurrencyId]['spent']));
|
||||
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, (string)$result[$limitCurrencyId]['spent']));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function filterLimit(int $currencyId, Collection $limits): ?BudgetLimit
|
||||
{
|
||||
$amount = '0';
|
||||
$limit = null;
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
/** @var BudgetLimit $current */
|
||||
foreach ($limits as $current) {
|
||||
if (true === $this->convertToNative) {
|
||||
if ($current->transaction_currency_id === $this->nativeCurrency->id) {
|
||||
// simply add it.
|
||||
$amount = bcadd($amount, (string)$current->amount);
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
if ($current->transaction_currency_id !== $this->nativeCurrency->id) {
|
||||
// convert and then add it.
|
||||
$converted = $converter->convert($current->transactionCurrency, $this->nativeCurrency, $limit->start_date, $limit->amount);
|
||||
$amount = bcadd($amount, $converted);
|
||||
Log::debug(sprintf('Budgeted in limit #%d: %s %s, converted to %s %s', $current->id, $current->transactionCurrency->code, $current->amount, $this->nativeCurrency->code, $converted));
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
}
|
||||
if ($current->transaction_currency_id === $currencyId) {
|
||||
$limit = $current;
|
||||
}
|
||||
}
|
||||
if (null !== $limit && true === $this->convertToNative) {
|
||||
// convert and add all amounts.
|
||||
$limit->amount = app('steam')->positive($amount);
|
||||
Log::debug(sprintf('Final amount in limit with converted amount %s', $limit->amount));
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
}
|
||||
|
@@ -25,17 +25,20 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Generic\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
@@ -45,6 +48,8 @@ class CategoryController extends Controller
|
||||
use CleansChartData;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
private AccountRepositoryInterface $accountRepos;
|
||||
private CurrencyRepositoryInterface $currencyRepos;
|
||||
|
||||
@@ -79,9 +84,10 @@ class CategoryController extends Controller
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $this->parameters->get('end');
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]);
|
||||
$currencies = [];
|
||||
$return = [];
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
// get journals for entire period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
@@ -93,20 +99,40 @@ class CategoryController extends Controller
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
|
||||
$currencies[$currencyId] = $currency;
|
||||
$categoryName = $journal['category_name'] ?? (string) trans('firefly.no_category');
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
$key = sprintf('%s-%s', $categoryName, $currency->code);
|
||||
// find journal:
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
|
||||
$currencies[$journalCurrencyId] = $currency;
|
||||
$currencyId = (int)$currency->id;
|
||||
$currencyName = (string)$currency->name;
|
||||
$currencyCode = (string)$currency->code;
|
||||
$currencySymbol = (string)$currency->symbol;
|
||||
$currencyDecimalPlaces = (int)$currency->decimal_places;
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
|
||||
// overrule if necessary:
|
||||
if ($this->convertToNative && $journalCurrencyId !== $this->nativeCurrency->id) {
|
||||
$currencyId = (int)$this->nativeCurrency->id;
|
||||
$currencyName = (string)$this->nativeCurrency->name;
|
||||
$currencyCode = (string)$this->nativeCurrency->code;
|
||||
$currencySymbol = (string)$this->nativeCurrency->symbol;
|
||||
$currencyDecimalPlaces = (int)$this->nativeCurrency->decimal_places;
|
||||
$convertedAmount = $converter->convert($currency, $this->nativeCurrency, $journal['date'], $amount);
|
||||
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->nativeCurrency->code, $convertedAmount));
|
||||
$amount = $convertedAmount;
|
||||
}
|
||||
|
||||
|
||||
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
||||
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
||||
// create arrays
|
||||
$return[$key] ??= [
|
||||
'label' => $categoryName,
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
'period' => null,
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
@@ -114,12 +140,12 @@ class CategoryController extends Controller
|
||||
];
|
||||
|
||||
// add monies
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string) $amount);
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string)$amount);
|
||||
}
|
||||
$return = array_values($return);
|
||||
|
||||
// order by amount
|
||||
usort($return, static fn (array $a, array $b) => (float) $a['amount'] < (float) $b['amount'] ? 1 : -1);
|
||||
usort($return, static fn (array $a, array $b) => (float)$a['amount'] < (float)$b['amount'] ? 1 : -1);
|
||||
|
||||
return response()->json($this->clean($return));
|
||||
}
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
@@ -76,6 +78,17 @@ 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);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bills = $enrichment->enrich($bills);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
@@ -96,6 +109,17 @@ class ShowController extends Controller
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -29,7 +29,9 @@ use FireflyIII\Api\V1\Requests\Models\Bill\StoreRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -72,6 +74,17 @@ class StoreController extends Controller
|
||||
$bill = $this->repository->store($data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Bill\UpdateRequest;
|
||||
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 League\Fractal\Resource\Item;
|
||||
|
||||
@@ -67,6 +69,17 @@ class UpdateController extends Controller
|
||||
$bill = $this->repository->update($bill, $data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\User;
|
||||
@@ -79,6 +80,17 @@ class ListController extends Controller
|
||||
$count = $collection->count();
|
||||
$bills = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bills = $enrichment->enrich($bills);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.currencies.bills', [$objectGroup->id]).$this->buildParams());
|
||||
|
@@ -147,6 +147,7 @@ class ShowController extends Controller
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -43,6 +43,7 @@ use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use FireflyIII\Transformers\AvailableBudgetTransformer;
|
||||
@@ -182,6 +183,17 @@ class ListController extends Controller
|
||||
$count = $collection->count();
|
||||
$bills = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bills = $enrichment->enrichSingle($bills);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.currencies.bills', [$currency->code]).$this->buildParams());
|
||||
|
@@ -58,7 +58,7 @@ class AutocompleteRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -60,8 +60,8 @@ class ChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before_or_equal:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after_or_equal:start',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17|before_or_equal:end',
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start',
|
||||
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
|
@@ -65,9 +65,9 @@ class DateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -55,8 +55,8 @@ class DateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ class SingleDateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -66,8 +66,8 @@ class Request extends FormRequest
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -80,9 +80,9 @@ class StoreRequest extends FormRequest
|
||||
'amount_max' => ['required', new IsValidPositiveAmount()],
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'date' => 'date|required|after:1900-01-01|before:2099-12-31',
|
||||
'end_date' => 'nullable|date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'extension_date' => 'nullable|date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|required|after:1970-01-02|before:2038-01-17',
|
||||
'end_date' => 'nullable|date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'extension_date' => 'nullable|date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly|required',
|
||||
'skip' => 'min:0|max:31|numeric',
|
||||
'active' => [new IsBoolean()],
|
||||
|
@@ -81,9 +81,9 @@ class UpdateRequest extends FormRequest
|
||||
'amount_max' => ['nullable', new IsValidPositiveAmount()],
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end_date' => 'date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'extension_date' => 'date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end_date' => 'date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'extension_date' => 'date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly',
|
||||
'skip' => 'min:0|max:31|numeric',
|
||||
'active' => [new IsBoolean()],
|
||||
|
@@ -67,8 +67,8 @@ class UpdateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
|
@@ -45,7 +45,7 @@ class DestroyRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ class StoreRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'rate' => 'required|numeric|gt:0',
|
||||
'from' => 'required|exists:transaction_currencies,code',
|
||||
'to' => 'required|exists:transaction_currencies,code',
|
||||
|
@@ -50,7 +50,7 @@ class UpdateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'rate' => 'required|numeric|gt:0',
|
||||
];
|
||||
}
|
||||
|
@@ -154,7 +154,7 @@ class UpdateRequest extends FormRequest
|
||||
return [
|
||||
'title' => sprintf('min:1|max:255|uniqueObjectForUser:recurrences,title,%d', $recurrence->id),
|
||||
'description' => 'min:1|max:32768',
|
||||
'first_date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'first_date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'apply_rules' => [new IsBoolean()],
|
||||
'active' => [new IsBoolean()],
|
||||
'repeat_until' => 'nullable|date',
|
||||
|
@@ -71,8 +71,8 @@ class TestRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -65,8 +65,8 @@ class TriggerRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -65,8 +65,8 @@ class TestRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -69,8 +69,8 @@ class TriggerRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ class StoreRequest extends FormRequest
|
||||
$rules = [
|
||||
'tag' => 'required|min:1|uniqueObjectForUser:tags,tag|max:1024',
|
||||
'description' => 'min:1|nullable|max:32768',
|
||||
'date' => 'date|nullable|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
|
@@ -66,7 +66,7 @@ class UpdateRequest extends FormRequest
|
||||
$rules = [
|
||||
'tag' => 'min:1|max:1024|uniqueObjectForUser:tags,tag,'.$tag->id,
|
||||
'description' => 'min:1|nullable|max:32768',
|
||||
'date' => 'date|nullable|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
|
@@ -73,7 +73,7 @@ class CronRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'force' => 'in:true,false',
|
||||
'date' => 'nullable|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'nullable|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -116,7 +116,7 @@ class Controller extends BaseController
|
||||
app('log')->warning(sprintf('Ignored invalid date "%s" in API v2 controller parameter check: %s', substr((string) $date, 0, 20), $e->getMessage()));
|
||||
}
|
||||
// out of range? set to null.
|
||||
if ($obj instanceof Carbon && ($obj->year <= 1900 || $obj->year > 2099)) {
|
||||
if ($obj instanceof Carbon && ($obj->year <= 1970 || $obj->year > 2038)) {
|
||||
app('log')->warning(sprintf('Refuse to use date "%s" in API v2 controller parameter check: %s', $field, $obj->toAtomString()));
|
||||
$obj = null;
|
||||
}
|
||||
|
@@ -86,7 +86,7 @@ class AutocompleteRequest extends FormRequest
|
||||
$valid = array_keys($this->types);
|
||||
|
||||
return [
|
||||
'date' => 'nullable|date|after:1900-01-01|before:2100-01-01',
|
||||
'date' => 'nullable|date|after:1970-01-02|before:2038-01-17',
|
||||
'query' => 'nullable|string',
|
||||
'size' => 'nullable|integer|min:1|max:100',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
|
@@ -60,8 +60,8 @@ class BalanceChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
'accounts.*' => 'required|exists:accounts,id',
|
||||
'period' => sprintf('required|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
];
|
||||
|
@@ -61,8 +61,8 @@ class ChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before_or_equal:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after_or_equal:start',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17|before_or_equal:end',
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start',
|
||||
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
|
@@ -60,8 +60,8 @@ class DashboardChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
'preselected' => sprintf('in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
];
|
||||
|
@@ -55,8 +55,8 @@ class DateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ class SingleDateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -63,9 +63,9 @@ class IndexRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -109,8 +109,8 @@ class InfiniteListRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:start|after:1970-01-02|before:2038-01-17',
|
||||
'start_row' => 'integer|min:0|max:4294967296',
|
||||
'end_row' => 'integer|min:0|max:4294967296|gt:start_row',
|
||||
];
|
||||
|
@@ -84,8 +84,8 @@ class ListRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:start|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -75,15 +75,15 @@ class TagFactory
|
||||
$latitude = 0.0 === (float) $data['latitude'] ? null : (float) $data['latitude']; // intentional float
|
||||
$longitude = 0.0 === (float) $data['longitude'] ? null : (float) $data['longitude']; // intentional float
|
||||
$array = [
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->userGroup->id,
|
||||
'tag' => trim((string) $data['tag']),
|
||||
'tagMode' => 'nothing',
|
||||
'date' => $data['date'],
|
||||
'description' => $data['description'],
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'zoomLevel' => null,
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->userGroup->id,
|
||||
'tag' => trim((string) $data['tag']),
|
||||
'tag_mode' => 'nothing',
|
||||
'date' => $data['date'],
|
||||
'description' => $data['description'],
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'zoomLevel' => null,
|
||||
];
|
||||
|
||||
/** @var null|Tag $tag */
|
||||
|
@@ -26,6 +26,7 @@ namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Jobs\SendWebhookMessage;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class WebhookEventHandler
|
||||
@@ -37,7 +38,13 @@ class WebhookEventHandler
|
||||
*/
|
||||
public function sendWebhookMessages(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
if (false === config('firefly.feature_flags.webhooks') || false === config('firefly.allow_webhooks')) {
|
||||
Log::info('Webhook event handler is disabled, do not run sendWebhookMessages().');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// kick off the job!
|
||||
$messages = WebhookMessage::where('webhook_messages.sent', false)
|
||||
->get(['webhook_messages.*'])
|
||||
@@ -45,14 +52,14 @@ class WebhookEventHandler
|
||||
static fn (WebhookMessage $message) => $message->webhookAttempts()->count() <= 2
|
||||
)->splice(0, 5)
|
||||
;
|
||||
app('log')->debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count()));
|
||||
Log::debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count()));
|
||||
foreach ($messages as $message) {
|
||||
if (false === $message->sent) {
|
||||
app('log')->debug(sprintf('Send message #%d', $message->id));
|
||||
Log::debug(sprintf('Send message #%d', $message->id));
|
||||
SendWebhookMessage::dispatch($message)->afterResponse();
|
||||
}
|
||||
if (false !== $message->sent) {
|
||||
app('log')->debug(sprintf('Skip message #%d', $message->id));
|
||||
Log::debug(sprintf('Skip message #%d', $message->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -434,6 +434,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
public function findNothing(): GroupCollectorInterface
|
||||
{
|
||||
Log::warning('The search engine was instructed to FIND NOTHING. This may be a bug.');
|
||||
$this->query->where('transaction_groups.id', -1);
|
||||
|
||||
return $this;
|
||||
@@ -557,7 +558,6 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
|
||||
}
|
||||
}
|
||||
|
||||
$groups = $this->parseSums($groups);
|
||||
|
||||
return new Collection($groups);
|
||||
|
@@ -88,7 +88,9 @@ class IndexController extends Controller
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$start->subDay();
|
||||
|
||||
// #10618 go to the end of the previous day.
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
Log::debug(sprintf('inactive start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
@@ -164,8 +166,8 @@ class IndexController extends Controller
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
|
||||
// 2025-05-11 removed this so start is exactly the start of the month.
|
||||
// $start->subDay();
|
||||
// #10618 go to the end of the previous day.
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
Log::debug(sprintf('index start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
|
@@ -29,7 +29,9 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Application;
|
||||
@@ -76,11 +78,26 @@ class IndexController extends Controller
|
||||
$collection = $this->repository->getBills();
|
||||
$total = $collection->count();
|
||||
|
||||
|
||||
|
||||
$parameters = new ParameterBag();
|
||||
// sub one day from temp start so the last paid date is one day before it should be.
|
||||
$tempStart = clone $start;
|
||||
// 2023-06-23 do not sub one day from temp start, fix is in BillTransformer::payDates instead
|
||||
// $tempStart->subDay();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->defaultCurrency);
|
||||
$enrichment->setStart($tempStart);
|
||||
$enrichment->setEnd($end);
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
|
||||
$parameters->set('start', $tempStart);
|
||||
$parameters->set('end', $end);
|
||||
$parameters->set('convertToNative', $this->convertToNative);
|
||||
|
@@ -30,9 +30,11 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
|
||||
use FireflyIII\Transformers\AttachmentTransformer;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -142,6 +144,17 @@ class ShowController extends Controller
|
||||
$parameters->set('start', $start);
|
||||
$parameters->set('end', $end);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->defaultCurrency);
|
||||
$enrichment->setStart($start);
|
||||
$enrichment->setEnd($end);
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($parameters);
|
||||
|
@@ -107,7 +107,7 @@ class RecurrenceController extends Controller
|
||||
$repetitionMoment = str_ireplace('ndom,', '', $request->get('type'));
|
||||
}
|
||||
if ('yearly' === $repetitionType) {
|
||||
$repetitionMoment = explode(',', (string) $request->get('type'))[1] ?? '2018-01-01';
|
||||
$repetitionMoment = explode(',', (string) $request->get('type'))[1] ?? '2025-01-01';
|
||||
}
|
||||
$actualStart->startOfDay();
|
||||
$repetition = new RecurrenceRepetition();
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -41,8 +41,8 @@ class SelectTransactionsRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
|
||||
'accounts' => 'required',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -74,7 +74,7 @@ class TagFormRequest extends FormRequest
|
||||
'tag' => $tagRule,
|
||||
'id' => $idRule,
|
||||
'description' => 'max:32768|min:1|nullable',
|
||||
'date' => 'date|nullable|after:1984-09-17',
|
||||
'date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
|
@@ -40,7 +40,7 @@ class Tag extends Model
|
||||
use ReturnsIntegerUserIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tagMode'];
|
||||
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tag_mode'];
|
||||
|
||||
protected $hidden = ['zoomLevel', 'latitude', 'longitude'];
|
||||
|
||||
|
@@ -312,11 +312,23 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
|
||||
Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString()));
|
||||
|
||||
return $bill->transactionJournals()
|
||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
|
||||
->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
|
||||
->where('transactions.amount', '>', 0)
|
||||
->before($end)->after($start)->get(
|
||||
[
|
||||
'transaction_journals.id',
|
||||
'transaction_journals.date',
|
||||
'transaction_journals.transaction_group_id',
|
||||
'transactions.transaction_currency_id',
|
||||
'currency.code AS transaction_currency_code',
|
||||
'currency.decimal_places AS transaction_currency_decimal_places',
|
||||
'transactions.foreign_currency_id',
|
||||
'foreign_currency.code AS foreign_currency_code',
|
||||
'foreign_currency.decimal_places AS foreign_currency_decimal_places',
|
||||
'transactions.amount',
|
||||
'transactions.foreign_amount',
|
||||
]
|
||||
)
|
||||
;
|
||||
|
@@ -24,14 +24,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use Deprecated;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
@@ -55,17 +57,17 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$total = '0';
|
||||
$count = 0;
|
||||
foreach ($budget->budgetlimits as $limit) {
|
||||
$diff = (int) $limit->start_date->diffInDays($limit->end_date, true);
|
||||
$diff = (int)$limit->start_date->diffInDays($limit->end_date, true);
|
||||
$diff = 0 === $diff ? 1 : $diff;
|
||||
$amount = $limit->amount;
|
||||
$perDay = bcdiv((string) $amount, (string) $diff);
|
||||
$perDay = bcdiv((string)$amount, (string)$diff);
|
||||
$total = bcadd($total, $perDay);
|
||||
++$count;
|
||||
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
|
||||
}
|
||||
$avg = $total;
|
||||
if ($count > 0) {
|
||||
$avg = bcdiv($total, (string) $count);
|
||||
$avg = bcdiv($total, (string)$count);
|
||||
}
|
||||
app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
|
||||
|
||||
@@ -93,9 +95,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
// prep data array for currency:
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetId = (int)$journal['budget_id'];
|
||||
$budgetName = $journal['budget_name'];
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$key = sprintf('%d-%d', $budgetId, $currencyId);
|
||||
|
||||
$data[$key] ??= [
|
||||
@@ -110,7 +112,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
'entries' => [],
|
||||
];
|
||||
$date = $journal['date']->format($carbonFormat);
|
||||
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string) $journal['amount']);
|
||||
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string)$journal['amount']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
@@ -124,7 +126,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array
|
||||
{
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
if ($accounts instanceof Collection && $accounts->count() > 0) {
|
||||
$collector->setAccounts($accounts);
|
||||
@@ -136,15 +138,41 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$collector->setBudgets($this->getBudgets());
|
||||
}
|
||||
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
|
||||
// if needs conversion to native.
|
||||
$convertToNative = Amount::convertToNative($this->user);
|
||||
$nativeCurrency = Amount::getNativeCurrencyByUserGroup($this->userGroup);
|
||||
$currencyId = (int) $nativeCurrency->id;
|
||||
$currencyCode = $nativeCurrency->code;
|
||||
$currencyName = $nativeCurrency->name;
|
||||
$currencySymbol = $nativeCurrency->symbol;
|
||||
$currencyDecimalPlaces = $nativeCurrency->decimal_places;
|
||||
$converter = new ExchangeRateConverter();
|
||||
$currencies = [
|
||||
$currencyId => $nativeCurrency,
|
||||
];
|
||||
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetName = (string) $journal['budget_name'];
|
||||
$amount = app('steam')->negative($journal['amount']);
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
if (false === $convertToNative) {
|
||||
$currencyId = $journalCurrencyId;
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
}
|
||||
if (true === $convertToNative && $journalCurrencyId !== $currencyId) {
|
||||
$currencies[$journalCurrencyId] ??= TransactionCurrency::find($journalCurrencyId);
|
||||
$amount = $converter->convert($currencies[$journalCurrencyId], $nativeCurrency, $journal['date'], $amount);
|
||||
}
|
||||
|
||||
// catch "no category" entries.
|
||||
$budgetId = (int)$journal['budget_id'];
|
||||
$budgetName = (string)$journal['budget_name'];
|
||||
|
||||
// catch "no budget" entries.
|
||||
if (0 === $budgetId) {
|
||||
continue;
|
||||
}
|
||||
@@ -153,10 +181,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$array[$currencyId] ??= [
|
||||
'budgets' => [],
|
||||
'currency_id' => $currencyId,
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_decimal_places' => $journal['currency_decimal_places'],
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
|
||||
// info about the categories:
|
||||
@@ -168,9 +196,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
|
||||
// add journal to array:
|
||||
// only a subset of the fields.
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$journalId = (int)$journal['transaction_journal_id'];
|
||||
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [
|
||||
'amount' => app('steam')->negative($journal['amount']),
|
||||
'amount' => $amount,
|
||||
'destination_account_id' => $journal['destination_account_id'],
|
||||
'destination_account_name' => $journal['destination_account_name'],
|
||||
'source_account_id' => $journal['source_account_id'],
|
||||
|
@@ -32,6 +32,7 @@ use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use NumberFormatter;
|
||||
|
||||
/**
|
||||
@@ -66,13 +67,13 @@ class Amount
|
||||
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
|
||||
$fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
|
||||
$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
|
||||
$result = (string) $fmt->format((float) $rounded); // intentional float
|
||||
$result = (string)$fmt->format((float)$rounded); // intentional float
|
||||
|
||||
if (true === $coloured) {
|
||||
if (1 === bccomp((string) $rounded, '0')) {
|
||||
if (1 === bccomp((string)$rounded, '0')) {
|
||||
return sprintf('<span class="text-success money-positive">%s</span>', $result);
|
||||
}
|
||||
if (-1 === bccomp((string) $rounded, '0')) {
|
||||
if (-1 === bccomp((string)$rounded, '0')) {
|
||||
return sprintf('<span class="text-danger money-negative">%s</span>', $result);
|
||||
}
|
||||
|
||||
@@ -106,23 +107,21 @@ class Amount
|
||||
$amount = $journal[$field] ?? '0';
|
||||
// Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
|
||||
// fallback, the transaction has a foreign amount in $currency.
|
||||
if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int) $journal['foreign_currency_id']) {
|
||||
if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
|
||||
$amount = $journal['foreign_amount'];
|
||||
// Log::debug(sprintf('Overruled, amount is now %s', $amount));
|
||||
}
|
||||
|
||||
return (string) $amount;
|
||||
return (string)$amount;
|
||||
}
|
||||
|
||||
public function convertToNative(?User $user = null): bool
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
return true === Preferences::get('convert_to_native', false)->data && true === config('cer.enabled');
|
||||
// Log::debug(sprintf('convertToNative [a]: %s', var_export($result, true)));
|
||||
}
|
||||
|
||||
return true === Preferences::getForUser($user, 'convert_to_native', false)->data && true === config('cer.enabled');
|
||||
// Log::debug(sprintf('convertToNative [b]: %s', var_export($result, true)));
|
||||
}
|
||||
|
||||
public function getNativeCurrency(): TransactionCurrency
|
||||
@@ -180,9 +179,9 @@ class Amount
|
||||
return '0';
|
||||
}
|
||||
$amount = $sourceTransaction->{$field} ?? '0';
|
||||
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
|
||||
if ((int)$sourceTransaction->foreign_currency_id === $currency->id) {
|
||||
// use foreign amount instead!
|
||||
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
$amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
}
|
||||
|
||||
return $amount;
|
||||
|
@@ -50,9 +50,9 @@ class CurrencyForm
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @phpstan-param view-string $view
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function currencyField(string $name, string $view, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
|
407
app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
Normal file
407
app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi\Enrichments;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Models\BillDateCalculator;
|
||||
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 ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $subscriptionIds = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $paidDates = [];
|
||||
private array $notes = [];
|
||||
private array $payDates = [];
|
||||
private TransactionCurrency $nativeCurrency;
|
||||
private BillDateCalculator $calculator;
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->calculator = app(BillDateCalculator::class);
|
||||
$this->collection = $collection;
|
||||
$this->collectSubscriptionIds();
|
||||
$this->collectNotes();
|
||||
$this->collectObjectGroups();
|
||||
$this->collectPaidDates();
|
||||
$this->collectPayDates();
|
||||
|
||||
$notes = $this->notes;
|
||||
$objectGroups = $this->objectGroups;
|
||||
$paidDates = $this->paidDates;
|
||||
$payDates = $this->payDates;
|
||||
$this->collection = $this->collection->map(function (Bill $item) use ($notes, $objectGroups, $paidDates, $payDates) {
|
||||
$id = (int)$item->id;
|
||||
$currency = $item->transactionCurrency;
|
||||
$nem = $this->getNextExpectedMatch($payDates[$id] ?? []);
|
||||
|
||||
$meta = [
|
||||
'notes' => null,
|
||||
'object_group_id' => null,
|
||||
'object_group_title' => null,
|
||||
'object_group_order' => null,
|
||||
'last_paid_date' => $this->getLastPaidDate($paidDates[$id] ?? []),
|
||||
'paid_dates' => $this->filterPaidDates($paidDates[$id] ?? []),
|
||||
'pay_dates' => $payDates[$id] ?? [],
|
||||
'nem' => $nem,
|
||||
'nem_diff' => $this->getNextExpectedMatchDiff($nem, $payDates[$id] ?? []),
|
||||
];
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectPaidDates(): void
|
||||
{
|
||||
Log::debug('Now in collectPaidDates for bills');
|
||||
if (null === $this->start || null === $this->end) {
|
||||
Log::debug('Parameters are NULL, return empty array');
|
||||
$this->paidDates = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 2023-07-1 sub one day from the start date to fix a possible bug (see #7704)
|
||||
// 2023-07-18 this particular date is used to search for the last paid date.
|
||||
// 2023-07-18 the cloned $searchDate is used to grab the correct transactions.
|
||||
/** @var Carbon $start */
|
||||
$start = clone $this->start;
|
||||
$searchStart = clone $start;
|
||||
$start->subDay();
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone $this->end;
|
||||
$searchEnd = clone $end;
|
||||
|
||||
// move the search dates to the start of the day.
|
||||
$searchStart->startOfDay();
|
||||
$searchEnd->endOfDay();
|
||||
|
||||
Log::debug(sprintf('Parameters are start: %s, end: %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('Search parameters are: start: %s, end: %s', $searchStart->format('Y-m-d H:i:s'), $searchEnd->format('Y-m-d H:i:s')));
|
||||
|
||||
// Get from database when bills were paid.
|
||||
$set = $this->user->transactionJournals()
|
||||
->whereIn('bill_id', $this->subscriptionIds)
|
||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
|
||||
->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
|
||||
->where('transactions.amount', '>', 0)
|
||||
->before($searchEnd)->after($searchStart)->get(
|
||||
[
|
||||
'transaction_journals.id',
|
||||
'transaction_journals.date',
|
||||
'transaction_journals.transaction_group_id',
|
||||
'transactions.transaction_currency_id',
|
||||
'currency.code AS transaction_currency_code',
|
||||
'currency.decimal_places AS transaction_currency_decimal_places',
|
||||
'transactions.foreign_currency_id',
|
||||
'foreign_currency.code AS foreign_currency_code',
|
||||
'foreign_currency.decimal_places AS foreign_currency_decimal_places',
|
||||
'transactions.amount',
|
||||
'transactions.foreign_amount',
|
||||
]
|
||||
)
|
||||
;
|
||||
Log::debug(sprintf('Count %d entries in set', $set->count()));
|
||||
|
||||
// for each bill, do a loop.
|
||||
/** @var Bill $subscription */
|
||||
foreach ($this->collection as $subscription) {
|
||||
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.
|
||||
Log::debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d')));
|
||||
$lastPaidDate = $this->lastPaidDate($subscription, $set, $start);
|
||||
Log::debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d')));
|
||||
|
||||
// At this point the "next match" is exactly after the last time the bill was paid.
|
||||
$result = [];
|
||||
foreach ($set as $entry) {
|
||||
$array = [
|
||||
'transaction_group_id' => (string)$entry->transaction_group_id,
|
||||
'transaction_journal_id' => (string)$entry->id,
|
||||
'date' => $entry->date->toAtomString(),
|
||||
'date_object' => $entry->date,
|
||||
'currency_id' => $entry->transaction_currency_id,
|
||||
'currency_code' => $entry->transaction_currency_code,
|
||||
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
|
||||
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
|
||||
];
|
||||
if (null !== $entry->foreign_amount && null !== $entry->foreign_currency_code) {
|
||||
$array['foreign_currency_id'] = $entry->foreign_currency_id;
|
||||
$array['foreign_currency_code'] = $entry->foreign_currency_code;
|
||||
$array['foreign_currency_decimal_places'] = $entry->foreign_currency_decimal_places;
|
||||
$array['foreign_amount'] = Steam::bcround($entry->foreign_amount, $entry->foreign_currency_decimal_places);
|
||||
}
|
||||
|
||||
$result[] = $array;
|
||||
}
|
||||
$this->paidDates[$subscription->id] = $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest date in the set, or start when set is empty.
|
||||
*/
|
||||
protected function lastPaidDate(Bill $subscription, Collection $dates, Carbon $default): Carbon
|
||||
{
|
||||
$filtered = $dates->filter(function (TransactionJournal $journal) use ($subscription) {
|
||||
return $journal->bill_id === $subscription->id;
|
||||
});
|
||||
if (0 === $filtered->count()) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$latest = $filtered->first()->date;
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($filtered as $journal) {
|
||||
if ($journal->date->gte($latest)) {
|
||||
$latest = $journal->date;
|
||||
}
|
||||
}
|
||||
|
||||
return $latest;
|
||||
}
|
||||
|
||||
private function getLastPaidDate(array $paidData): ?Carbon
|
||||
{
|
||||
Log::debug('getLastPaidDate()');
|
||||
$return = null;
|
||||
foreach ($paidData as $entry) {
|
||||
if (null !== $return) {
|
||||
/** @var Carbon $current */
|
||||
$current = $entry['date_object'];
|
||||
if ($current->gt($return)) {
|
||||
$return = clone $current;
|
||||
}
|
||||
Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
if (null === $return) {
|
||||
/** @var Carbon $return */
|
||||
$return = $entry['date_object'];
|
||||
Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function collectPayDates(): void
|
||||
{
|
||||
/** @var Bill $subscription */
|
||||
foreach ($this->collection as $subscription) {
|
||||
$id = (int)$subscription->id;
|
||||
$lastPaidDate = $this->getLastPaidDate($paidDates[$id] ?? []);
|
||||
$payDates = $this->calculator->getPayDates($this->start, $this->end, $subscription->date, $subscription->repeat_freq, $subscription->skip, $lastPaidDate);
|
||||
$payDatesFormatted = [];
|
||||
foreach ($payDates as $string) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
|
||||
if (!$date instanceof Carbon) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$payDatesFormatted[] = $date->toAtomString();
|
||||
}
|
||||
$this->payDates[$id] = $payDatesFormatted;
|
||||
}
|
||||
}
|
||||
|
||||
private function filterPaidDates(array $entries): array
|
||||
{
|
||||
return array_map(function (array $entry) {
|
||||
unset($entry['date_object']);
|
||||
|
||||
return $entry;
|
||||
}, $entries);
|
||||
}
|
||||
|
||||
private function getNextExpectedMatch(array $payDates): ?Carbon
|
||||
{
|
||||
// next expected match
|
||||
$nem = null;
|
||||
$firstPayDate = $payDates[0] ?? null;
|
||||
|
||||
if (null !== $firstPayDate) {
|
||||
$nemDate = Carbon::parse($firstPayDate, config('app.timezone'));
|
||||
if (!$nemDate instanceof Carbon) {
|
||||
$nemDate = today(config('app.timezone'));
|
||||
}
|
||||
$nem = $nemDate;
|
||||
|
||||
// nullify again when it's outside the current view range.
|
||||
if (
|
||||
(null !== $this->start && $nemDate->lt($this->start))
|
||||
|| (null !== $this->end && $nemDate->gt($this->end))
|
||||
) {
|
||||
$nem = null;
|
||||
$nemDate = null;
|
||||
$firstPayDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $nem;
|
||||
}
|
||||
|
||||
private function getNextExpectedMatchDiff(?Carbon $nem, array $payDates): string
|
||||
{
|
||||
if (null === $nem) {
|
||||
return trans('firefly.not_expected_period');
|
||||
}
|
||||
$nemDiff = trans('firefly.not_expected_period');
|
||||
// converting back and forth is bad code but OK.
|
||||
if ($nem->isToday()) {
|
||||
$nemDiff = trans('firefly.today');
|
||||
}
|
||||
|
||||
$current = $payDates[0] ?? null;
|
||||
if (null !== $current && !$nem->isToday()) {
|
||||
$temp2 = Carbon::parse($current, config('app.timezone'));
|
||||
if (!$temp2 instanceof Carbon) {
|
||||
$temp2 = today(config('app.timezone'));
|
||||
}
|
||||
$nemDiff = trans('firefly.bill_expected_date', ['date' => $temp2->diffForHumans(today(config('app.timezone')), CarbonInterface::DIFF_RELATIVE_TO_NOW)]);
|
||||
}
|
||||
unset($temp2);
|
||||
|
||||
return $nemDiff;
|
||||
}
|
||||
}
|
@@ -149,7 +149,9 @@ class TransactionGroupEnrichment implements EnrichmentInterface
|
||||
continue;
|
||||
}
|
||||
if (in_array($name, $this->dateFields, true)) {
|
||||
$this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data);
|
||||
Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data));
|
||||
$this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data, config('app.timezone'));
|
||||
Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@@ -257,16 +257,16 @@ trait ConvertsDataTypes
|
||||
try {
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $value);
|
||||
} catch (InvalidDateException $e) { // @phpstan-ignore-line
|
||||
app('log')->error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
} catch (InvalidFormatException $e) { // @phpstan-ignore-line
|
||||
app('log')->error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
}
|
||||
if (!$carbon instanceof Carbon) {
|
||||
app('log')->error(sprintf('[2] "%s" is of an invalid format.', $value));
|
||||
Log::error(sprintf('[2] "%s" is of an invalid format.', $value));
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -278,11 +278,11 @@ trait ConvertsDataTypes
|
||||
try {
|
||||
$carbon = Carbon::parse($value);
|
||||
} catch (InvalidDateException $e) { // @phpstan-ignore-line
|
||||
app('log')->error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
} catch (InvalidFormatException $e) {
|
||||
app('log')->error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -306,6 +306,7 @@ trait ConvertsDataTypes
|
||||
protected function dateFromValue(?string $string): ?Carbon
|
||||
{
|
||||
if (null === $string) {
|
||||
|
||||
return null;
|
||||
}
|
||||
if ('' === $string) {
|
||||
@@ -319,11 +320,11 @@ trait ConvertsDataTypes
|
||||
// @ignoreException
|
||||
}
|
||||
if (!$carbon instanceof Carbon) {
|
||||
app('log')->debug(sprintf('Invalid date: %s', $string));
|
||||
Log::debug(sprintf('Invalid date: %s', $string));
|
||||
|
||||
return null;
|
||||
}
|
||||
app('log')->debug(sprintf('Date object: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()));
|
||||
Log::debug(sprintf('Date object: %s (%s) from "%s"', $carbon->toW3cString(), $carbon->getTimezone(), $string));
|
||||
|
||||
return $carbon;
|
||||
}
|
||||
@@ -383,7 +384,7 @@ trait ConvertsDataTypes
|
||||
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
}
|
||||
if (!$result instanceof Carbon) {
|
||||
app('log')->debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -93,7 +93,7 @@ class Steam
|
||||
|
||||
return [];
|
||||
}
|
||||
$defaultCurrency = app('amount')->getNativeCurrency();
|
||||
$defaultCurrency = Amount::getNativeCurrency();
|
||||
if ($convertToNative) {
|
||||
if ($defaultCurrency->id === $currency?->id) {
|
||||
Log::debug(sprintf('Unset [%s] for account #%d (no longer unset "native_balance")', $defaultCurrency->code, $account->id));
|
||||
@@ -224,7 +224,7 @@ class Steam
|
||||
$request->subDay()->endOfDay();
|
||||
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
|
||||
$startBalance = $this->finalAccountBalance($account, $request);
|
||||
$nativeCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup);
|
||||
$nativeCurrency = Amount::getNativeCurrencyByUserGroup($account->user->userGroup);
|
||||
$accountCurrency = $this->getAccountCurrency($account);
|
||||
$hasCurrency = $accountCurrency instanceof TransactionCurrency;
|
||||
$currency = $accountCurrency ?? $nativeCurrency;
|
||||
@@ -294,7 +294,7 @@ class Steam
|
||||
$currentBalance[$entryCurrency->code] ??= '0';
|
||||
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string) $currentBalance[$entryCurrency->code]);
|
||||
|
||||
// if not convert to native, add the amount to "balance", do nothing else.
|
||||
// if not requested to convert to native, add the amount to "balance", do nothing else.
|
||||
if (!$convertToNative) {
|
||||
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
|
||||
}
|
||||
@@ -302,13 +302,13 @@ class Steam
|
||||
// if there is a request to convert, convert to "native_balance" and use "balance" for whichever amount is in the native currency.
|
||||
if ($convertToNative) {
|
||||
$nativeSumOfDay = $converter->convert($entryCurrency, $nativeCurrency, $carbon, $sumOfDay);
|
||||
$currentBalance['native_balance'] = bcadd((string) $currentBalance['native_balance'], $nativeSumOfDay);
|
||||
$currentBalance['native_balance'] = bcadd((string) ($currentBalance['native_balance'] ?? '0'), $nativeSumOfDay);
|
||||
// if it's the same currency as the entry, also add to balance (see other code).
|
||||
if ($currency->id === $entryCurrency->id) {
|
||||
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
|
||||
}
|
||||
|
||||
}
|
||||
// just set it.
|
||||
// add to final array.
|
||||
$balances[$carbonKey] = $currentBalance;
|
||||
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
|
||||
}
|
||||
|
@@ -67,8 +67,8 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
|
||||
// get account type:
|
||||
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
|
||||
$accountType = (string)config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = (string)config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
|
||||
|
||||
$liabilityDirection = $account->meta['liability_direction'] ?? null;
|
||||
@@ -89,10 +89,10 @@ class AccountTransformer extends AbstractTransformer
|
||||
$native = null;
|
||||
}
|
||||
|
||||
$decimalPlaces = (int) $account->meta['currency']?->decimal_places;
|
||||
$decimalPlaces = (int)$account->meta['currency']?->decimal_places;
|
||||
$decimalPlaces = 0 === $decimalPlaces ? 2 : $decimalPlaces;
|
||||
$openingBalance = Steam::bcround($openingBalance, $decimalPlaces);
|
||||
$includeNetWorth = 1 === (int) ($account->meta['include_net_worth'] ?? 0);
|
||||
$openingBalanceRounded = Steam::bcround($openingBalance, $decimalPlaces);
|
||||
$includeNetWorth = 1 === (int)($account->meta['include_net_worth'] ?? 0);
|
||||
$longitude = $account->meta['location']['longitude'] ?? null;
|
||||
$latitude = $account->meta['location']['latitude'] ?? null;
|
||||
$zoomLevel = $account->meta['location']['zoom_level'] ?? null;
|
||||
@@ -112,8 +112,55 @@ class AccountTransformer extends AbstractTransformer
|
||||
$currentBalance = Steam::bcround($finalBalance['balance'] ?? '0', $decimalPlaces);
|
||||
$nativeCurrentBalance = $this->convertToNative ? Steam::bcround($finalBalance['native_balance'] ?? '0', $native->decimal_places) : null;
|
||||
|
||||
// set up balances array:
|
||||
$balances = [];
|
||||
$balances[]
|
||||
= [
|
||||
'type' => 'current',
|
||||
'amount' => $currentBalance,
|
||||
'currency_id' => $account->meta['currency_id'] ?? null,
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'date' => $date->toAtomString(),
|
||||
];
|
||||
if (null !== $nativeCurrentBalance) {
|
||||
$balances[] = [
|
||||
'type' => 'native_current',
|
||||
'amount' => $nativeCurrentBalance,
|
||||
'currency_id' => $native instanceof TransactionCurrency ? (string)$native->id : null,
|
||||
'currency_code' => $native?->code,
|
||||
'currency_symbol' => $native?->symbol,
|
||||
'ccurrency_decimal_places' => $native?->decimal_places,
|
||||
'date' => $date->toAtomString(),
|
||||
|
||||
];
|
||||
}
|
||||
if (null !== $openingBalance) {
|
||||
$balances[] = [
|
||||
'type' => 'opening',
|
||||
'amount' => $openingBalanceRounded,
|
||||
'currency_id' => $account->meta['currency_id'] ?? null,
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'date' => $openingBalanceDate,
|
||||
];
|
||||
}
|
||||
if (null !== $account->virtual_balance) {
|
||||
$balances[] = [
|
||||
'type' => 'virtual',
|
||||
'amount' => Steam::bcround($account->virtual_balance, $decimalPlaces),
|
||||
'currency_id' => $account->meta['currency_id'] ?? null,
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'date' => $date->toAtomString(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (string) $account->id,
|
||||
'id' => (string)$account->id,
|
||||
'created_at' => $account->created_at->toAtomString(),
|
||||
'updated_at' => $account->updated_at->toAtomString(),
|
||||
'active' => $account->active,
|
||||
@@ -125,7 +172,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'native_currency_id' => $native instanceof TransactionCurrency ? (string) $native->id : null,
|
||||
'native_currency_id' => $native instanceof TransactionCurrency ? (string)$native->id : null,
|
||||
'native_currency_code' => $native?->code,
|
||||
'native_currency_symbol' => $native?->symbol,
|
||||
'native_currency_decimal_places' => $native?->decimal_places,
|
||||
@@ -140,7 +187,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'bic' => $account->meta['BIC'] ?? null,
|
||||
'virtual_balance' => Steam::bcround($account->virtual_balance, $decimalPlaces),
|
||||
'native_virtual_balance' => $this->convertToNative ? Steam::bcround($account->native_virtual_balance, $native->decimal_places) : null,
|
||||
'opening_balance' => $openingBalance,
|
||||
'opening_balance' => $openingBalanceRounded,
|
||||
'native_opening_balance' => $nativeOpeningBalance,
|
||||
'opening_balance_date' => $openingBalanceDate,
|
||||
'liability_type' => $liabilityType,
|
||||
@@ -153,6 +200,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'latitude' => $latitude,
|
||||
'zoom_level' => $zoomLevel,
|
||||
'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null,
|
||||
'balances' => $balances,
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
@@ -165,7 +213,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
private function getAccountRole(Account $account, string $accountType): ?string
|
||||
{
|
||||
$accountRole = $account->meta['account_role'] ?? null;
|
||||
if ('asset' !== $accountType || '' === (string) $accountRole) {
|
||||
if ('asset' !== $accountType || '' === (string)$accountRole) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -201,7 +249,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
$monthlyPaymentDate = $object->toAtomString();
|
||||
}
|
||||
if (10 !== strlen((string) $monthlyPaymentDate)) {
|
||||
if (10 !== strlen((string)$monthlyPaymentDate)) {
|
||||
$monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString();
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ class AttachmentTransformer extends AbstractTransformer
|
||||
private readonly AttachmentRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* BillTransformer constructor.
|
||||
* AttachmentTransformer constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
@@ -24,145 +24,51 @@ declare(strict_types=1);
|
||||
|
||||
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;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Models\BillDateCalculator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class BillTransformer
|
||||
*/
|
||||
class BillTransformer extends AbstractTransformer
|
||||
{
|
||||
private readonly BillDateCalculator $calculator;
|
||||
private readonly bool $convertToNative;
|
||||
private readonly TransactionCurrency $default;
|
||||
private readonly BillRepositoryInterface $repository;
|
||||
private readonly TransactionCurrency $native;
|
||||
|
||||
/**
|
||||
* BillTransformer constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
$this->calculator = app(BillDateCalculator::class);
|
||||
$this->default = Amount::getNativeCurrency();
|
||||
$this->convertToNative = Amount::convertToNative();
|
||||
$this->native = Amount::getNativeCurrency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the bill.
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveMethodLength")
|
||||
* @SuppressWarnings("PHPMD.NPathComplexity")
|
||||
*/
|
||||
public function transform(Bill $bill): array
|
||||
{
|
||||
$default = $this->parameters->get('defaultCurrency') ?? $this->default;
|
||||
$currency = $bill->transactionCurrency;
|
||||
|
||||
$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 = [];
|
||||
foreach ($paidData as $object) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone'));
|
||||
if (!$date instanceof Carbon) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$object['date'] = $date->toAtomString();
|
||||
$paidDataFormatted[] = $object;
|
||||
}
|
||||
|
||||
foreach ($payDates as $string) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
|
||||
if (!$date instanceof Carbon) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$payDatesFormatted[] = $date->toAtomString();
|
||||
}
|
||||
// next expected match
|
||||
$nem = null;
|
||||
$nemDate = null;
|
||||
$nemDiff = trans('firefly.not_expected_period');
|
||||
$firstPayDate = $payDates[0] ?? null;
|
||||
|
||||
if (null !== $firstPayDate) {
|
||||
$nemDate = Carbon::createFromFormat('!Y-m-d', $firstPayDate, config('app.timezone'));
|
||||
if (!$nemDate instanceof Carbon) {
|
||||
$nemDate = today(config('app.timezone'));
|
||||
}
|
||||
$nem = $nemDate->toAtomString();
|
||||
|
||||
// nullify again when it's outside the current view range.
|
||||
if (
|
||||
(null !== $this->parameters->get('start') && $nemDate->lt($this->parameters->get('start')))
|
||||
|| (null !== $this->parameters->get('end') && $nemDate->gt($this->parameters->get('end')))
|
||||
) {
|
||||
$nem = null;
|
||||
$nemDate = null;
|
||||
$firstPayDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
// converting back and forth is bad code but OK.
|
||||
if (null !== $nemDate) {
|
||||
if ($nemDate->isToday()) {
|
||||
$nemDiff = trans('firefly.today');
|
||||
}
|
||||
|
||||
$current = $payDatesFormatted[0] ?? null;
|
||||
if (null !== $current && !$nemDate->isToday()) {
|
||||
$temp2 = Carbon::createFromFormat('Y-m-d\TH:i:sP', $current);
|
||||
if (!$temp2 instanceof Carbon) {
|
||||
$temp2 = today(config('app.timezone'));
|
||||
}
|
||||
$nemDiff = trans('firefly.bill_expected_date', ['date' => $temp2->diffForHumans(today(config('app.timezone')), CarbonInterface::DIFF_RELATIVE_TO_NOW)]);
|
||||
}
|
||||
unset($temp2);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $bill->id,
|
||||
'created_at' => $bill->created_at->toAtomString(),
|
||||
'updated_at' => $bill->updated_at->toAtomString(),
|
||||
'currency_id' => (string) $bill->transaction_currency_id,
|
||||
'currency_id' => (string)$bill->transaction_currency_id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_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(),
|
||||
@@ -170,16 +76,21 @@ 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'],
|
||||
|
||||
|
||||
'paid_dates' => $bill->meta['paid_dates'],
|
||||
'pay_dates' => $bill->meta['pay_dates'],
|
||||
'next_expected_match' => $bill->meta['nem']?->toAtomString(),
|
||||
'next_expected_match_diff' => $bill->meta['nem_diff'],
|
||||
|
||||
// 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,
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
@@ -188,102 +99,4 @@ class BillTransformer extends AbstractTransformer
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data the bill was paid and predict the next expected match.
|
||||
*/
|
||||
protected function paidData(Bill $bill): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in paidData for bill #%d', $bill->id));
|
||||
if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) {
|
||||
app('log')->debug('parameters are NULL, return empty array');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// 2023-07-1 sub one day from the start date to fix a possible bug (see #7704)
|
||||
// 2023-07-18 this particular date is used to search for the last paid date.
|
||||
// 2023-07-18 the cloned $searchDate is used to grab the correct transactions.
|
||||
/** @var Carbon $start */
|
||||
$start = clone $this->parameters->get('start');
|
||||
$searchStart = clone $start;
|
||||
$start->subDay();
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone $this->parameters->get('end');
|
||||
$searchEnd = clone $end;
|
||||
|
||||
// move the search dates to the start of the day.
|
||||
$searchStart->startOfDay();
|
||||
$searchEnd->endOfDay();
|
||||
|
||||
app('log')->debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('Search parameters are: start: %s', $searchStart->format('Y-m-d')));
|
||||
|
||||
// Get from database when bill was paid.
|
||||
$set = $this->repository->getPaidDatesInRange($bill, $searchStart, $searchEnd);
|
||||
app('log')->debug(sprintf('Count %d entries in getPaidDatesInRange()', $set->count()));
|
||||
|
||||
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.
|
||||
app('log')->debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d')));
|
||||
$lastPaidDate = $this->lastPaidDate($set, $start);
|
||||
app('log')->debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d')));
|
||||
|
||||
// At this point the "next match" is exactly after the last time the bill was paid.
|
||||
$result = [];
|
||||
foreach ($set as $entry) {
|
||||
$result[] = [
|
||||
'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,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest date in the set, or start when set is empty.
|
||||
*/
|
||||
protected function lastPaidDate(Collection $dates, Carbon $default): Carbon
|
||||
{
|
||||
if (0 === $dates->count()) {
|
||||
return $default;
|
||||
}
|
||||
$latest = $dates->first()->date;
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($dates as $journal) {
|
||||
if ($journal->date->gte($latest)) {
|
||||
$latest = $journal->date;
|
||||
}
|
||||
}
|
||||
|
||||
return $latest;
|
||||
}
|
||||
|
||||
private function getLastPaidDate(array $paidData): ?Carbon
|
||||
{
|
||||
app('log')->debug('getLastPaidDate()');
|
||||
$return = null;
|
||||
foreach ($paidData as $entry) {
|
||||
if (null !== $return) {
|
||||
/** @var Carbon $current */
|
||||
$current = $entry['date_object'];
|
||||
if ($current->gt($return)) {
|
||||
$return = clone $current;
|
||||
}
|
||||
app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
if (null === $return) {
|
||||
/** @var Carbon $return */
|
||||
$return = $entry['date_object'];
|
||||
app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
app('log')->debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -121,6 +121,15 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string) $transaction['foreign_amount'])) {
|
||||
$foreignAmount = app('steam')->positive($transaction['foreign_amount']);
|
||||
}
|
||||
|
||||
// set native amount to the normal amount if the currency matches.
|
||||
if ($transaction['native_currency']['id'] ?? null === $transaction['currency_id']) {
|
||||
$transaction['native_amount'] = $amount;
|
||||
}
|
||||
|
||||
if (array_key_exists('native_amount', $transaction) && null !== $transaction['native_amount']) {
|
||||
$transaction['native_amount'] = app('steam')->positive($transaction['native_amount']);
|
||||
}
|
||||
$type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value);
|
||||
|
||||
// must be 0 (int) or NULL
|
||||
@@ -220,12 +229,12 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
'sepa_ci' => $transaction['meta']['sepa_ci'] ?? null,
|
||||
'sepa_batch_id' => $transaction['meta']['sepa_batch_id'] ?? null,
|
||||
|
||||
'interest_date' => $transaction['meta_date']['interest_date'] ?? null,
|
||||
'book_date' => $transaction['meta_date']['book_date'] ?? null,
|
||||
'process_date' => $transaction['meta_date']['process_date'] ?? null,
|
||||
'due_date' => $transaction['meta_date']['due_date'] ?? null,
|
||||
'payment_date' => $transaction['meta_date']['payment_date'] ?? null,
|
||||
'invoice_date' => $transaction['meta_date']['invoice_date'] ?? null,
|
||||
'interest_date' => array_key_exists('interest_date', $transaction['meta_date']) ? $transaction['meta_date']['interest_date']->toW3CString() : null,
|
||||
'book_date' => array_key_exists('book_date', $transaction['meta_date']) ? $transaction['meta_date']['book_date']->toW3CString() : null,
|
||||
'process_date' => array_key_exists('process_date', $transaction['meta_date']) ? $transaction['meta_date']['process_date']->toW3CString() : null,
|
||||
'due_date' => array_key_exists('due_date', $transaction['meta_date']) ? $transaction['meta_date']['due_date']->toW3CString() : null,
|
||||
'payment_date' => array_key_exists('payment_date', $transaction['meta_date']) ? $transaction['meta_date']['payment_date']->toW3CString() : null,
|
||||
'invoice_date' => array_key_exists('invoice_date', $transaction['meta_date']) ? $transaction['meta_date']['invoice_date']->toW3CString() : null,
|
||||
// location data
|
||||
'longitude' => $transaction['location']['longitude'],
|
||||
'latitude' => $transaction['location']['latitude'],
|
||||
|
15
changelog.md
15
changelog.md
@@ -3,6 +3,21 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 6.2.21 - 2025-07-18
|
||||
|
||||
### Added
|
||||
|
||||
- Debug information to search engine.
|
||||
|
||||
### Changed
|
||||
|
||||
- Rename table field for postgres databases.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 10581](https://github.com/firefly-iii/firefly-iii/issues/10581) (Epoch doesn't fit in a PHP integer error on 32-bit systems due to strict date validation) reported by @kksandr7
|
||||
- [Discussion 10601](https://github.com/orgs/firefly-iii/discussions/10601) (Edit a transaction will result in an incorrect date) started by @MasterZhang007
|
||||
|
||||
## 6.2.20 - 2025-07-02
|
||||
|
||||
### Changed
|
||||
|
227
composer.lock
generated
227
composer.lock
generated
@@ -1879,16 +1879,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.19.3",
|
||||
"version": "v12.21.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262"
|
||||
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/4e6ec689ef704bb4bd282f29d9dd658dfb4fb262",
|
||||
"reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
|
||||
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2090,7 +2090,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-06-18T12:56:23+00:00"
|
||||
"time": "2025-07-22T15:41:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/passport",
|
||||
@@ -2170,16 +2170,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
"version": "v0.3.5",
|
||||
"version": "v0.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/prompts.git",
|
||||
"reference": "57b8f7efe40333cdb925700891c7d7465325d3b1"
|
||||
"reference": "86a8b692e8661d0fb308cec64f3d176821323077"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/57b8f7efe40333cdb925700891c7d7465325d3b1",
|
||||
"reference": "57b8f7efe40333cdb925700891c7d7465325d3b1",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077",
|
||||
"reference": "86a8b692e8661d0fb308cec64f3d176821323077",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2223,22 +2223,22 @@
|
||||
"description": "Add beautiful and user-friendly forms to your command-line applications.",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/prompts/issues",
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.5"
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.6"
|
||||
},
|
||||
"time": "2025-02-11T13:34:40+00:00"
|
||||
"time": "2025-07-07T14:17:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sanctum",
|
||||
"version": "v4.1.1",
|
||||
"version": "v4.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sanctum.git",
|
||||
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5"
|
||||
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sanctum/zipball/a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
|
||||
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
|
||||
"url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
|
||||
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2289,7 +2289,7 @@
|
||||
"issues": "https://github.com/laravel/sanctum/issues",
|
||||
"source": "https://github.com/laravel/sanctum"
|
||||
},
|
||||
"time": "2025-04-23T13:03:38+00:00"
|
||||
"time": "2025-07-09T19:45:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
@@ -2354,16 +2354,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/slack-notification-channel",
|
||||
"version": "v3.5.0",
|
||||
"version": "v3.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/slack-notification-channel.git",
|
||||
"reference": "b9448136c2e93f51f0d603d05d6bf64412e5727c"
|
||||
"reference": "642677a57490eebccb7e9fb666f5a5379c6e3459"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/b9448136c2e93f51f0d603d05d6bf64412e5727c",
|
||||
"reference": "b9448136c2e93f51f0d603d05d6bf64412e5727c",
|
||||
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/642677a57490eebccb7e9fb666f5a5379c6e3459",
|
||||
"reference": "642677a57490eebccb7e9fb666f5a5379c6e3459",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2413,9 +2413,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/slack-notification-channel/issues",
|
||||
"source": "https://github.com/laravel/slack-notification-channel/tree/v3.5.0"
|
||||
"source": "https://github.com/laravel/slack-notification-channel/tree/v3.6.0"
|
||||
},
|
||||
"time": "2025-02-23T14:43:55+00:00"
|
||||
"time": "2025-06-26T16:51:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/ui",
|
||||
@@ -2619,16 +2619,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.7.0",
|
||||
"version": "2.7.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
|
||||
"reference": "10732241927d3971d28e7ea7b5712721fa2296ca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca",
|
||||
"reference": "10732241927d3971d28e7ea7b5712721fa2296ca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2657,7 +2657,7 @@
|
||||
"symfony/process": "^5.4 | ^6.0 | ^7.0",
|
||||
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
|
||||
"unleashedtech/php-coding-standard": "^3.1.1",
|
||||
"vimeo/psalm": "^4.24.0 || ^5.0.0"
|
||||
"vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
|
||||
@@ -2722,7 +2722,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-05T12:20:28+00:00"
|
||||
"time": "2025-07-20T12:47:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
@@ -5101,16 +5101,16 @@
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v3.0.1",
|
||||
"version": "v3.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/predis/predis.git",
|
||||
"reference": "34fb0a7da0330df1bab4280fcac4afdeeccc3edf"
|
||||
"reference": "202e0c5322b906ec4c761c0cefebad6d0959a699"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/predis/predis/zipball/34fb0a7da0330df1bab4280fcac4afdeeccc3edf",
|
||||
"reference": "34fb0a7da0330df1bab4280fcac4afdeeccc3edf",
|
||||
"url": "https://api.github.com/repos/predis/predis/zipball/202e0c5322b906ec4c761c0cefebad6d0959a699",
|
||||
"reference": "202e0c5322b906ec4c761c0cefebad6d0959a699",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5152,7 +5152,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/predis/predis/issues",
|
||||
"source": "https://github.com/predis/predis/tree/v3.0.1"
|
||||
"source": "https://github.com/predis/predis/tree/v3.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5160,7 +5160,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-16T18:30:32+00:00"
|
||||
"time": "2025-07-22T15:37:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
@@ -9840,16 +9840,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "barryvdh/laravel-debugbar",
|
||||
"version": "v3.15.4",
|
||||
"version": "v3.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-debugbar.git",
|
||||
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106"
|
||||
"reference": "f265cf5e38577d42311f1a90d619bcd3740bea23"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106",
|
||||
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f265cf5e38577d42311f1a90d619bcd3740bea23",
|
||||
"reference": "f265cf5e38577d42311f1a90d619bcd3740bea23",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9857,7 +9857,7 @@
|
||||
"illuminate/session": "^9|^10|^11|^12",
|
||||
"illuminate/support": "^9|^10|^11|^12",
|
||||
"php": "^8.1",
|
||||
"php-debugbar/php-debugbar": "~2.1.1",
|
||||
"php-debugbar/php-debugbar": "~2.2.0",
|
||||
"symfony/finder": "^6|^7"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -9877,7 +9877,7 @@
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "3.15-dev"
|
||||
"dev-master": "3.16-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -9909,7 +9909,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
|
||||
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4"
|
||||
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9921,24 +9921,24 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-16T06:32:06+00:00"
|
||||
"time": "2025-07-14T11:56:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "barryvdh/laravel-ide-helper",
|
||||
"version": "v3.5.5",
|
||||
"version": "v3.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
|
||||
"reference": "8d441ec99f8612b942b55f5183151d91591b618a"
|
||||
"reference": "8d00250cba25728373e92c1d8dcebcbf64623d29"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/8d441ec99f8612b942b55f5183151d91591b618a",
|
||||
"reference": "8d441ec99f8612b942b55f5183151d91591b618a",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/8d00250cba25728373e92c1d8dcebcbf64623d29",
|
||||
"reference": "8d00250cba25728373e92c1d8dcebcbf64623d29",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"barryvdh/reflection-docblock": "^2.3",
|
||||
"barryvdh/reflection-docblock": "^2.4",
|
||||
"composer/class-map-generator": "^1.0",
|
||||
"ext-json": "*",
|
||||
"illuminate/console": "^11.15 || ^12",
|
||||
@@ -10003,7 +10003,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
|
||||
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.5.5"
|
||||
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.6.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10015,20 +10015,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-11T13:59:46+00:00"
|
||||
"time": "2025-07-17T20:11:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "barryvdh/reflection-docblock",
|
||||
"version": "v2.3.1",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/ReflectionDocBlock.git",
|
||||
"reference": "b6ff9f93603561f50e53b64310495d20b8dff5d8"
|
||||
"reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/b6ff9f93603561f50e53b64310495d20b8dff5d8",
|
||||
"reference": "b6ff9f93603561f50e53b64310495d20b8dff5d8",
|
||||
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/d103774cbe7e94ddee7e4870f97f727b43fe7201",
|
||||
"reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10065,9 +10065,9 @@
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.3.1"
|
||||
"source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.4.0"
|
||||
},
|
||||
"time": "2025-01-18T19:26:32+00:00"
|
||||
"time": "2025-07-17T06:07:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "cloudcreativity/json-api-testing",
|
||||
@@ -10471,16 +10471,16 @@
|
||||
},
|
||||
{
|
||||
"name": "larastan/larastan",
|
||||
"version": "v3.5.0",
|
||||
"version": "v3.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/larastan/larastan.git",
|
||||
"reference": "e8ccd73008487ba91da9877b373f8c447743f1ce"
|
||||
"reference": "6431d010dd383a9279eb8874a76ddb571738564a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/e8ccd73008487ba91da9877b373f8c447743f1ce",
|
||||
"reference": "e8ccd73008487ba91da9877b373f8c447743f1ce",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/6431d010dd383a9279eb8874a76ddb571738564a",
|
||||
"reference": "6431d010dd383a9279eb8874a76ddb571738564a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10548,7 +10548,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/larastan/larastan/issues",
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.5.0"
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.6.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10556,7 +10556,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-19T22:41:50+00:00"
|
||||
"time": "2025-07-11T06:52:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel-json-api/testing",
|
||||
@@ -10708,16 +10708,16 @@
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.13.1",
|
||||
"version": "1.13.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
|
||||
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
|
||||
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
|
||||
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10756,7 +10756,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10764,20 +10764,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-29T12:36:36+00:00"
|
||||
"time": "2025-07-05T12:25:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.5.0",
|
||||
"version": "v5.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
|
||||
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
|
||||
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10820,9 +10820,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0"
|
||||
},
|
||||
"time": "2025-05-31T08:24:38+00:00"
|
||||
"time": "2025-07-27T20:03:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@@ -10944,16 +10944,16 @@
|
||||
},
|
||||
{
|
||||
"name": "php-debugbar/php-debugbar",
|
||||
"version": "v2.1.6",
|
||||
"version": "v2.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-debugbar/php-debugbar.git",
|
||||
"reference": "16fa68da5617220594aa5e33fa9de415f94784a0"
|
||||
"reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/16fa68da5617220594aa5e33fa9de415f94784a0",
|
||||
"reference": "16fa68da5617220594aa5e33fa9de415f94784a0",
|
||||
"url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35",
|
||||
"reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10961,6 +10961,9 @@
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/var-dumper": "^4|^5|^6|^7"
|
||||
},
|
||||
"replace": {
|
||||
"maximebf/debugbar": "self.version"
|
||||
},
|
||||
"require-dev": {
|
||||
"dbrekelmans/bdi": "^1",
|
||||
"phpunit/phpunit": "^8|^9",
|
||||
@@ -10975,7 +10978,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
"dev-master": "2.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -11008,9 +11011,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-debugbar/php-debugbar/issues",
|
||||
"source": "https://github.com/php-debugbar/php-debugbar/tree/v2.1.6"
|
||||
"source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4"
|
||||
},
|
||||
"time": "2025-02-21T17:47:03+00:00"
|
||||
"time": "2025-07-22T14:01:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/extension-installer",
|
||||
@@ -11062,16 +11065,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.17",
|
||||
"version": "2.1.21",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "89b5ef665716fa2a52ecd2633f21007a6a349053"
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053",
|
||||
"reference": "89b5ef665716fa2a52ecd2633f21007a6a349053",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11116,7 +11119,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-21T20:55:28+00:00"
|
||||
"time": "2025-07-28T19:35:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-deprecation-rules",
|
||||
@@ -11167,16 +11170,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a"
|
||||
"reference": "f9f77efa9de31992a832ff77ea52eb42d675b094"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
|
||||
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094",
|
||||
"reference": "f9f77efa9de31992a832ff77ea52eb42d675b094",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11209,22 +11212,22 @@
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6"
|
||||
},
|
||||
"time": "2025-03-18T11:42:40+00:00"
|
||||
"time": "2025-07-21T12:19:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"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": {
|
||||
@@ -11280,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": [
|
||||
{
|
||||
@@ -11300,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",
|
||||
@@ -11549,16 +11552,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "12.2.5",
|
||||
"version": "12.2.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "b71849b29f7a8d7574e4401873cb8b539896613f"
|
||||
"reference": "442c06d0a952a5dbffba181cff969b91fdc9bada"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b71849b29f7a8d7574e4401873cb8b539896613f",
|
||||
"reference": "b71849b29f7a8d7574e4401873cb8b539896613f",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/442c06d0a952a5dbffba181cff969b91fdc9bada",
|
||||
"reference": "442c06d0a952a5dbffba181cff969b91fdc9bada",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11568,11 +11571,11 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"myclabs/deep-copy": "^1.13.1",
|
||||
"myclabs/deep-copy": "^1.13.3",
|
||||
"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",
|
||||
@@ -11626,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.5"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11650,25 +11653,25 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-27T04:37:55+00:00"
|
||||
"time": "2025-07-31T07:12:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rectorphp/rector.git",
|
||||
"reference": "d513dea45a94394b660e15c155d1fa27826f8e30"
|
||||
"reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/d513dea45a94394b660e15c155d1fa27826f8e30",
|
||||
"reference": "d513dea45a94394b660e15c155d1fa27826f8e30",
|
||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/40a71441dd73fa150a66102f5ca1364c44fc8fff",
|
||||
"reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4|^8.0",
|
||||
"phpstan/phpstan": "^2.1.17"
|
||||
"phpstan/phpstan": "^2.1.18"
|
||||
},
|
||||
"conflict": {
|
||||
"rector/rector-doctrine": "*",
|
||||
@@ -11702,7 +11705,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/rectorphp/rector/issues",
|
||||
"source": "https://github.com/rectorphp/rector/tree/2.1.0"
|
||||
"source": "https://github.com/rectorphp/rector/tree/2.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11710,7 +11713,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-24T20:26:57+00:00"
|
||||
"time": "2025-07-17T19:30:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@@ -78,9 +78,10 @@ return [
|
||||
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => '6.2.20',
|
||||
'version' => 'develop/2025-07-31',
|
||||
'build_time' => 1753950885,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 25,
|
||||
'db_version' => 26,
|
||||
|
||||
// generic settings
|
||||
'maxUploadSize' => 1073741824, // 1 GB
|
||||
|
@@ -65,6 +65,7 @@ return [
|
||||
'interest_calc_half-year',
|
||||
'interest_calc_quarterly',
|
||||
'spent',
|
||||
'budgeted',
|
||||
'administration_owner',
|
||||
'administration_you',
|
||||
'administration_role_owner',
|
||||
|
47
database/migrations/2025_07_10_065736_rename_tag_mode.php
Normal file
47
database/migrations/2025_07_10_065736_rename_tag_mode.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
try {
|
||||
// normal case
|
||||
Schema::table('tags', static function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('tags', 'tagMode') && !Schema::hasColumn('piggy_banks', 'tag_mode')) {
|
||||
$table->renameColumn('tagMode', 'tag_mode');
|
||||
}
|
||||
});
|
||||
// lower case just in case (haha)
|
||||
Schema::table('tags', static function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('tags', 'tagmode') && !Schema::hasColumn('piggy_banks', 'tag_mode')) {
|
||||
$table->renameColumn('tagmode', 'tag_mode');
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error(sprintf('Could not rename column: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
try {
|
||||
Schema::table('tags', static function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('tags', 'tag_mode') && !Schema::hasColumn('piggy_banks', 'tagMode')) {
|
||||
$table->renameColumn('tag_mode', 'tagMode');
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error(sprintf('Could not rename column: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
};
|
882
package-lock.json
generated
882
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,9 @@
|
||||
"devDependencies": {
|
||||
"@johmun/vue-tags-input": "^2",
|
||||
"@vue/compiler-sfc": "^3.5.11",
|
||||
"axios": "^1.8",
|
||||
"axios": "^1.11",
|
||||
"bootstrap-sass": "^3",
|
||||
"cross-env": "^7.0",
|
||||
"cross-env": "^10.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery": "^3",
|
||||
"laravel-mix": "^6.0",
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"administrations_index_menu": "Administracions financeres",
|
||||
"expires_at": "Caduca a les",
|
||||
"temp_administrations_introduction": "Firefly III aviat podr\u00e0 gestionar diverses administracions financeres. Ara mateix, nom\u00e9s en teniu una. Podeu definir el t\u00edtol d'aquesta administraci\u00f3 i la seva moneda nativa. Aix\u00f2 substitueix la configuraci\u00f3 anterior on definiu la vostra \"moneda predeterminada\". Aquesta configuraci\u00f3 ara est\u00e0 vinculada a l'administraci\u00f3 financera i pot ser diferent per a cada administraci\u00f3.",
|
||||
"administration_currency_form_help": "It may take a long time for the page to load if you change the native currency because transaction may need to be converted to your (new) native currency.",
|
||||
"administration_currency_form_help": "La p\u00e0gina pot trigar molt a carregar-se si canvieu la moneda nativa, ja que pot ser que la transacci\u00f3 s'hagi de convertir a la vostra (nova) moneda nativa.",
|
||||
"administrations_page_edit_sub_title_js": "Edita l'administraci\u00f3 financera \"{title}\"",
|
||||
"table": "Taula",
|
||||
"welcome_back": "Qu\u00e8 est\u00e0 passant?",
|
||||
@@ -16,10 +16,10 @@
|
||||
"select_source_account": "Per favor, selecciona o escriu un nom de compte d'origen v\u00e0lid",
|
||||
"split_transaction_title": "Descripci\u00f3 de la transacci\u00f3 dividida",
|
||||
"errors_submission": "Hi ha hagut un error amb el teu enviament. Per favor, revisa els errors de sota.",
|
||||
"is_reconciled": "Is reconciled",
|
||||
"is_reconciled": "Est\u00e0 reconciliat",
|
||||
"split": "Dividir",
|
||||
"single_split": "Divisi\u00f3",
|
||||
"not_enough_currencies": "Not enough currencies",
|
||||
"not_enough_currencies": "No hi ha prou monedes",
|
||||
"not_enough_currencies_enabled": "Si tens nom\u00e9s una divisa habilitada, no cal afegir tipus de canvi.",
|
||||
"transaction_stored_link": "La <a href=\"transactions\/show\/{ID}\">Transacci\u00f3 #{ID} (\"{title}\")<\/a> s'ha desat.",
|
||||
"webhook_stored_link": "S'ha desat <a href=\"webhooks\/show\/{ID}\">el Webook #{ID} (\"{title}\")<\/a> correctament.",
|
||||
@@ -43,7 +43,7 @@
|
||||
"submit": "Enviar",
|
||||
"amount": "Import",
|
||||
"date": "Data",
|
||||
"is_reconciled_fields_dropped": "Because this transaction is reconciled, you will not be able to update the accounts, nor the amount(s) unless you remove the reconciliation flag.",
|
||||
"is_reconciled_fields_dropped": "Com que aquesta transacci\u00f3 est\u00e0 reconciliada, no podreu actualitzar els comptes ni l'import o imports tret que elimineu el senyalador de reconciliaci\u00f3.",
|
||||
"tags": "Etiquetes",
|
||||
"no_budget": "(cap pressupost)",
|
||||
"no_bill": "(cap subscripci\u00f3)",
|
||||
@@ -141,9 +141,9 @@
|
||||
"visit_webhook_url": "Visitar l'URL del webhook",
|
||||
"reset_webhook_secret": "Reiniciar el secret del webhook",
|
||||
"header_exchange_rates": "Tipus de canvi",
|
||||
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">the documentation<\/a>.",
|
||||
"exchange_rates_intro": "Firefly III permet descarregar i utilitzar tipus de canvi. M\u00e9s informaci\u00f3 sobre aix\u00f2 a la <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">documentaci\u00f3<\/a>.",
|
||||
"exchange_rates_from_to": "Entre {from} i {to} (i a la inversa)",
|
||||
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
|
||||
"exchange_rates_intro_rates": "Firefly III utilitza els tipus de canvi seg\u00fcents. L'invers es calcula autom\u00e0ticament quan no es proporciona. Si no existeix cap tipus de canvi per a la data de la transacci\u00f3, Firefly III tornar\u00e0 enrere en el temps per trobar-ne un. Si no n'hi ha cap, s'utilitzar\u00e0 el tipus \"1\".",
|
||||
"header_exchange_rates_rates": "Tipus de canvi",
|
||||
"header_exchange_rates_table": "Taula amb els tipus de canvi",
|
||||
"help_rate_form": "El dia d'avui, quants {to} obtindr\u00e0s amb un {from}?",
|
||||
|
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"firefly": {
|
||||
"administrations_page_title": "Financial administrations",
|
||||
"administrations_index_menu": "Financial administrations",
|
||||
"administrations_page_title": "Spr\u00e1va financ\u00ed",
|
||||
"administrations_index_menu": "Spr\u00e1va financ\u00ed",
|
||||
"expires_at": "Expires at",
|
||||
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its native currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
|
||||
"administration_currency_form_help": "It may take a long time for the page to load if you change the native currency because transaction may need to be converted to your (new) native currency.",
|
||||
"administrations_page_edit_sub_title_js": "Edit financial administration \"{title}\"",
|
||||
"temp_administrations_introduction": "Firefly III brzy z\u00edsk\u00e1 mo\u017enost spravovat v\u00edce spr\u00e1v financ\u00ed. Moment\u00e1ln\u011b m\u00e1te pouze jednu. M\u016f\u017eete nastavit n\u00e1zev t\u00e9to spr\u00e1vy a jej\u00ed m\u00edstn\u00ed m\u011bnu. Toto nahrazuje p\u0159edchoz\u00ed nastaven\u00ed, kde jste nastavovali \u201ev\u00fdchoz\u00ed m\u011bnu\u201c. Toto nastaven\u00ed je nyn\u00ed v\u00e1z\u00e1no na spr\u00e1vu financ\u00ed a m\u016f\u017ee se li\u0161it pro ka\u017edou spr\u00e1vu.",
|
||||
"administration_currency_form_help": "Pokud zm\u011bn\u00edte m\u00edstn\u00ed m\u011bnu, m\u016f\u017ee na\u010dten\u00ed str\u00e1nky trvat d\u00e9le, proto\u017ee transakce mo\u017en\u00e1 bude pot\u0159eba p\u0159ev\u00e9st na va\u0161i (novou) m\u00edstn\u00ed m\u011bnu.",
|
||||
"administrations_page_edit_sub_title_js": "Upravit spr\u00e1vu financ\u00ed \u201e{title}\u201c",
|
||||
"table": "Tabulka",
|
||||
"welcome_back": "Jak to jde?",
|
||||
"flash_error": "Chyba!",
|
||||
@@ -20,14 +20,14 @@
|
||||
"split": "Rozd\u011blit",
|
||||
"single_split": "Rozd\u011blit",
|
||||
"not_enough_currencies": "Not enough currencies",
|
||||
"not_enough_currencies_enabled": "If you have just one currency enabled, there is no need to add exchange rates.",
|
||||
"not_enough_currencies_enabled": "Pokud m\u00e1te povolenou pouze jednu m\u011bnu, nemus\u00edte p\u0159id\u00e1vat sm\u011bnn\u00e9 kurzy.",
|
||||
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakce #{ID} (\"{title}\")<\/a> byla ulo\u017eena.",
|
||||
"webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID} (\"{title}\")<\/a> byl ulo\u017een.",
|
||||
"webhook_updated_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID}<\/a> (\"{title}\") byl aktualizov\u00e1n.",
|
||||
"transaction_updated_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID}<\/a> (\"{title}\") has been updated.",
|
||||
"transaction_new_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakce #{ID}<\/a> byla ulo\u017eena.",
|
||||
"transaction_journal_information": "Informace o transakci",
|
||||
"submission_options": "Submission options",
|
||||
"submission_options": "Mo\u017enosti polo\u017eky",
|
||||
"apply_rules_checkbox": "Aplikovat pravidla",
|
||||
"fire_webhooks_checkbox": "Spustit webhooky",
|
||||
"no_budget_pointer": "<a href=\"budgets\">Zde<\/a> si m\u016f\u017eete spravovat rozpo\u010dty, kter\u00e9 v\u00e1m mohou pomoci sledovat v\u00fddaje.",
|
||||
@@ -36,17 +36,17 @@
|
||||
"hidden_fields_preferences": "Dal\u0161\u00ed vlastnosti transakc\u00ed m\u016f\u017eete zaznamenat kdy\u017e je zapnete v <a href=\"preferences\">nastaven\u00ed<\/a>.",
|
||||
"destination_account": "C\u00edlov\u00fd \u00fa\u010det",
|
||||
"add_another_split": "P\u0159idat dal\u0161\u00ed roz\u00fa\u010dtov\u00e1n\u00ed",
|
||||
"submission": "Submission",
|
||||
"submission": "Polo\u017eka",
|
||||
"stored_journal": "\u00dasp\u011b\u0161n\u011b vytvo\u0159ena nov\u00e1 transakce \u201e:description\u201c",
|
||||
"create_another": "Po ulo\u017een\u00ed se vr\u00e1tit sem pro vytvo\u0159en\u00ed dal\u0161\u00ed.",
|
||||
"reset_after": "Po odesl\u00e1n\u00ed vymazat obsah formul\u00e1\u0159e",
|
||||
"submit": "Odeslat",
|
||||
"submit": "Potvrdit",
|
||||
"amount": "\u010c\u00e1stka",
|
||||
"date": "Datum",
|
||||
"is_reconciled_fields_dropped": "Because this transaction is reconciled, you will not be able to update the accounts, nor the amount(s) unless you remove the reconciliation flag.",
|
||||
"tags": "\u0160t\u00edtky",
|
||||
"no_budget": "(\u017e\u00e1dn\u00fd rozpo\u010det)",
|
||||
"no_bill": "(no subscription)",
|
||||
"no_bill": "(bez pravideln\u00e9 platby)",
|
||||
"category": "Kategorie",
|
||||
"attachments": "P\u0159\u00edlohy",
|
||||
"notes": "Pozn\u00e1mky",
|
||||
@@ -62,7 +62,7 @@
|
||||
"destination_account_reconciliation": "C\u00edlov\u00fd \u00fa\u010det odsouhlasen\u00e9 transakce nelze upravit.",
|
||||
"source_account_reconciliation": "Nem\u016f\u017eete upravovat zdrojov\u00fd \u00fa\u010det srovn\u00e1vac\u00ed transakce.",
|
||||
"budget": "Rozpo\u010det",
|
||||
"bill": "Subscription",
|
||||
"bill": "Pravideln\u00e1 platba",
|
||||
"you_create_withdrawal": "Vytv\u00e1\u0159\u00edte v\u00fdb\u011br.",
|
||||
"you_create_transfer": "Vytv\u00e1\u0159\u00edte p\u0159evod.",
|
||||
"you_create_deposit": "Vytv\u00e1\u0159\u00edte vklad.",
|
||||
@@ -72,27 +72,27 @@
|
||||
"profile_whoops": "Omlouv\u00e1me se, tohle n\u011bjak nefunguje",
|
||||
"profile_something_wrong": "Something went wrong!",
|
||||
"profile_try_again": "Something went wrong. Please try again.",
|
||||
"profile_oauth_clients": "OAuth Clients",
|
||||
"profile_oauth_clients": "Klienti OAuth",
|
||||
"profile_oauth_no_clients": "Zat\u00edm jste nevytvo\u0159ili OAuth klienty.",
|
||||
"profile_oauth_clients_header": "Klienti",
|
||||
"profile_oauth_client_id": "ID z\u00e1kazn\u00edka",
|
||||
"profile_oauth_client_id": "ID klienta",
|
||||
"profile_oauth_client_name": "Jm\u00e9no",
|
||||
"profile_oauth_client_secret": "Tajn\u00fd kl\u00ed\u010d",
|
||||
"profile_oauth_client_secret": "Tajn\u00e9",
|
||||
"profile_oauth_create_new_client": "Vytvo\u0159it nov\u00e9ho klienta",
|
||||
"profile_oauth_create_client": "Vytvo\u0159it klienta",
|
||||
"profile_oauth_edit_client": "Upravit klienta",
|
||||
"profile_oauth_name_help": "N\u011bco \u010demu va\u0161i u\u017eivatel\u00e9 budou d\u016fv\u011b\u0159ovat.",
|
||||
"profile_oauth_redirect_url": "P\u0159esm\u011brovat URL adresu",
|
||||
"profile_oauth_clients_external_auth": "Pokud pro ov\u011b\u0159ov\u00e1n\u00ed pou\u017e\u00edv\u00e1te extern\u00ed slu\u017ebu, nap\u0159\u00edklad Authelia, OAuth klienti nemus\u00ed fungovat spr\u00e1vn\u011b. M\u00edsto toho m\u016f\u017eete pou\u017e\u00edt Personal Access Token.",
|
||||
"profile_oauth_clients_external_auth": "Pokud pro ov\u011b\u0159ov\u00e1n\u00ed pou\u017e\u00edv\u00e1te extern\u00ed slu\u017ebu, nap\u0159\u00edklad Authelia, OAuth klienti nemus\u00ed fungovat spr\u00e1vn\u011b. M\u00edsto toho m\u016f\u017eete pou\u017e\u00edt osobn\u00ed p\u0159\u00edstupov\u00fd token.",
|
||||
"profile_oauth_redirect_url_help": "Callback URL va\u0161\u00ed aplikace.",
|
||||
"profile_authorized_apps": "Authorized applications",
|
||||
"profile_authorized_clients": "Autorizovan\u00ed klienti",
|
||||
"profile_scopes": "Scopes",
|
||||
"profile_revoke": "Revoke",
|
||||
"profile_personal_access_tokens": "Personal Access Token",
|
||||
"profile_personal_access_token": "Personal Access Token",
|
||||
"profile_personal_access_token_explanation": "Tohle je v\u00e1\u0161 nov\u00fd p\u0159\u00edstupov\u00fd token. Tohle je naposled kdy ho vid\u00edte, tak\u017ee ho neztra\u0165te! M\u016f\u017eete ho pou\u017e\u00edt pro vol\u00e1n\u00ed API.",
|
||||
"profile_no_personal_access_token": "Je\u0161t\u011b jste nevytvo\u0159ili \u017e\u00e1dn\u00e9 p\u0159\u00edstupov\u00e9 tokeny.",
|
||||
"profile_personal_access_tokens": "Osobn\u00ed p\u0159\u00edstupov\u00fd token",
|
||||
"profile_personal_access_token": "Osobn\u00ed p\u0159\u00edstupov\u00e9 tokeny",
|
||||
"profile_personal_access_token_explanation": "Zde je v\u00e1\u0161 nov\u00fd osobn\u00ed p\u0159\u00edstupov\u00fd token. Toto je jedin\u00fd okam\u017eik, kdy jej uvid\u00edte, tak\u017ee ho neztra\u0165te! Nyn\u00ed m\u016f\u017eete tento token pou\u017e\u00edt k odes\u00edl\u00e1n\u00ed po\u017eadavk\u016f na API.",
|
||||
"profile_no_personal_access_token": "Zat\u00edm jste nevytvo\u0159ili \u017e\u00e1dn\u00e9 osobn\u00ed p\u0159\u00edstupov\u00e9 tokeny.",
|
||||
"profile_create_new_token": "Vytvo\u0159it nov\u00fd token",
|
||||
"profile_create_token": "Vytvo\u0159it token",
|
||||
"profile_create": "Vytvo\u0159it",
|
||||
@@ -123,7 +123,7 @@
|
||||
"create_new_webhook": "Vytvo\u0159it nov\u00fd webhook",
|
||||
"webhooks": "Webhooky",
|
||||
"webhook_trigger_form_help": "Ur\u010dit, na kterou ud\u00e1lost se spust\u00ed webhook",
|
||||
"webhook_response_form_help": "Ur\u010dit, co mus\u00ed webhook odeslat do URL.",
|
||||
"webhook_response_form_help": "Ur\u010dete, co mus\u00ed webhook odeslat do URL.",
|
||||
"webhook_delivery_form_help": "V jak\u00e9m form\u00e1tu mus\u00ed webhook pos\u00edlat data.",
|
||||
"webhook_active_form_help": "Webhook mus\u00ed b\u00fdt aktivn\u00ed, nebo nebude zavol\u00e1n.",
|
||||
"edit_webhook_js": "Upravit webhook \"{title}\"",
|
||||
@@ -140,12 +140,12 @@
|
||||
"response": "Odpov\u011b\u010f",
|
||||
"visit_webhook_url": "Nav\u0161t\u00edvit URL webhooku",
|
||||
"reset_webhook_secret": "Restartovat tajn\u00fd kl\u00ed\u010d webhooku",
|
||||
"header_exchange_rates": "Exchange rates",
|
||||
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">the documentation<\/a>.",
|
||||
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)",
|
||||
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
|
||||
"header_exchange_rates_rates": "Exchange rates",
|
||||
"header_exchange_rates_table": "Table with exchange rates",
|
||||
"header_exchange_rates": "Sm\u011bnn\u00e9 kurzy",
|
||||
"exchange_rates_intro": "Firefly III podporuje stahov\u00e1n\u00ed a pou\u017e\u00edv\u00e1n\u00ed sm\u011bnn\u00fdch kurz\u016f. V\u00edce informac\u00ed o t\u00e9to funkci najdete <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">v dokumentaci<\/a>.",
|
||||
"exchange_rates_from_to": "Mezi {from} a {to} (a opa\u010dn\u011b)",
|
||||
"exchange_rates_intro_rates": "Firefly III pou\u017e\u00edv\u00e1 n\u00e1sleduj\u00edc\u00ed sm\u011bnn\u00e9 kurzy. Inverzn\u00ed kurz je automaticky vypo\u010d\u00edt\u00e1n, pokud nen\u00ed zad\u00e1n. Pokud pro datum transakce neexistuje sm\u011bnn\u00fd kurz, Firefly III se vr\u00e1t\u00ed zp\u011bt v \u010dase a pokus\u00ed se n\u011bjak\u00fd naj\u00edt. Pokud \u017e\u00e1dn\u00fd nenajde, pou\u017eije se kurz \u201e1\u201c.",
|
||||
"header_exchange_rates_rates": "Sm\u011bnn\u00e9 kurzy",
|
||||
"header_exchange_rates_table": "Tabulka se sm\u011bnn\u00fdmi kurzy",
|
||||
"help_rate_form": "On this day, how many {to} will you get for one {from}?",
|
||||
"add_new_rate": "Add a new exchange rate",
|
||||
"save_new_rate": "Save new rate"
|
||||
@@ -154,7 +154,7 @@
|
||||
"url": "URL",
|
||||
"active": "Aktivn\u00ed",
|
||||
"interest_date": "\u00darokov\u00e9 datum",
|
||||
"administration_currency": "Native currency",
|
||||
"administration_currency": "M\u00edstn\u00ed m\u011bna",
|
||||
"title": "N\u00e1zev",
|
||||
"date": "Datum",
|
||||
"book_date": "Datum rezervace",
|
||||
|
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"firefly": {
|
||||
"administrations_page_title": "Financial administrations",
|
||||
"administrations_index_menu": "Financial administrations",
|
||||
"expires_at": "Expires at",
|
||||
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its native currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
|
||||
"administration_currency_form_help": "It may take a long time for the page to load if you change the native currency because transaction may need to be converted to your (new) native currency.",
|
||||
"administrations_page_edit_sub_title_js": "Edit financial administration \"{title}\"",
|
||||
"administrations_page_title": "Administraciones financieras",
|
||||
"administrations_index_menu": "Administraciones financieras",
|
||||
"expires_at": "Expira el",
|
||||
"temp_administrations_introduction": "Firefly III pronto tendr\u00e1 la capacidad de gestionar m\u00faltiples administraciones financieras. Ahora mismo, solo tienes uno. Puedes establecer el t\u00edtulo de esta administraci\u00f3n y su moneda nativa. Esto reemplaza la configuraci\u00f3n anterior donde establecer\u00eda su \"moneda predeterminada\". Esta situaci\u00f3n est\u00e1 vinculada ahora a la administraci\u00f3n financiera y puede ser diferente por administraci\u00f3n.",
|
||||
"administration_currency_form_help": "Puede tardar mucho tiempo en cargar la p\u00e1gina si cambia la moneda nativa porque la transacci\u00f3n puede necesitar ser convertida a su (nueva) moneda nativa.",
|
||||
"administrations_page_edit_sub_title_js": "Editar administraci\u00f3n financiera \"{title}\"",
|
||||
"table": "Mesa",
|
||||
"welcome_back": "\u00bfQu\u00e9 est\u00e1 pasando?",
|
||||
"flash_error": "\u00a1Error!",
|
||||
@@ -16,11 +16,11 @@
|
||||
"select_source_account": "Por favor, seleccione o escriba un nombre de cuenta de origen v\u00e1lido",
|
||||
"split_transaction_title": "Descripci\u00f3n de la transacci\u00f3n dividida",
|
||||
"errors_submission": "Hubo un problema con su env\u00edo. Por favor, compruebe los siguientes errores.",
|
||||
"is_reconciled": "Is reconciled",
|
||||
"is_reconciled": "Est\u00e1 reconciliado",
|
||||
"split": "Separar",
|
||||
"single_split": "Divisi\u00f3n",
|
||||
"not_enough_currencies": "Not enough currencies",
|
||||
"not_enough_currencies_enabled": "If you have just one currency enabled, there is no need to add exchange rates.",
|
||||
"not_enough_currencies": "No hay suficientes monedas",
|
||||
"not_enough_currencies_enabled": "Si solo tiene una divisa habilitada, no es necesario a\u00f1adir tipos de cambio.",
|
||||
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">La transacci\u00f3n #{ID} (\"{title}\")<\/a> ha sido almacenada.",
|
||||
"webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">El webhook #{ID} (\"{title}\")<\/a> ha sido almacenado.",
|
||||
"webhook_updated_link": "<a href=\"webhooks\/show\/{ID}\">El webhook #{ID} (\"{title}\")<\/a> ha sido actualizado.",
|
||||
@@ -43,10 +43,10 @@
|
||||
"submit": "Enviar",
|
||||
"amount": "Cantidad",
|
||||
"date": "Fecha",
|
||||
"is_reconciled_fields_dropped": "Because this transaction is reconciled, you will not be able to update the accounts, nor the amount(s) unless you remove the reconciliation flag.",
|
||||
"is_reconciled_fields_dropped": "Debido a que esta transacci\u00f3n est\u00e1 reconciliada, no podr\u00e1 actualizar las cuentas, ni la cantidad(es) a menos que elimine la bandera de reconciliaci\u00f3n.",
|
||||
"tags": "Etiquetas",
|
||||
"no_budget": "(sin presupuesto)",
|
||||
"no_bill": "(no subscription)",
|
||||
"no_bill": "(sin suscripci\u00f3n)",
|
||||
"category": "Categor\u00eda",
|
||||
"attachments": "Archivos adjuntos",
|
||||
"notes": "Notas",
|
||||
@@ -62,7 +62,7 @@
|
||||
"destination_account_reconciliation": "No puedes editar la cuenta de destino de una transacci\u00f3n de reconciliaci\u00f3n.",
|
||||
"source_account_reconciliation": "No puedes editar la cuenta de origen de una transacci\u00f3n de reconciliaci\u00f3n.",
|
||||
"budget": "Presupuesto",
|
||||
"bill": "Subscription",
|
||||
"bill": "Suscripci\u00f3n",
|
||||
"you_create_withdrawal": "Est\u00e1 creando un gasto.",
|
||||
"you_create_transfer": "Est\u00e1 creando una transferencia.",
|
||||
"you_create_deposit": "Est\u00e1 creando un ingreso.",
|
||||
@@ -140,11 +140,11 @@
|
||||
"response": "Respuesta",
|
||||
"visit_webhook_url": "Visita la URL del webhook",
|
||||
"reset_webhook_secret": "Restablecer secreto del webhook",
|
||||
"header_exchange_rates": "Exchange rates",
|
||||
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">the documentation<\/a>.",
|
||||
"header_exchange_rates": "Tipos de cambio",
|
||||
"exchange_rates_intro": "Firefly III soporta la descarga y el uso de tipos de cambio. Lee m\u00e1s sobre esto en <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">la documentaci\u00f3n<\/a>.",
|
||||
"exchange_rates_from_to": "Entre {from} y {to} (y viceversa)",
|
||||
"exchange_rates_intro_rates": "Firefly III utiliza los siguientes tipos de cambio. El inverso se calcula autom\u00e1ticamente cuando no se proporciona. Si no existe un tipo de cambio para la fecha de la transacci\u00f3n, Firefly III retroceder\u00e1 en el tiempo para encontrar uno. Si no hay ninguno presente, se usar\u00e1 la tasa \"1\".",
|
||||
"header_exchange_rates_rates": "Exchange rates",
|
||||
"header_exchange_rates_rates": "Tipos de cambio",
|
||||
"header_exchange_rates_table": "Tabla con tipos de cambio",
|
||||
"help_rate_form": "En este d\u00eda, \u00bfcu\u00e1nto {to} conseguir\u00e1s por un {from}?",
|
||||
"add_new_rate": "Agregar un nuevo tipo de cambio",
|
||||
@@ -174,7 +174,7 @@
|
||||
"list": {
|
||||
"title": "T\u00edtulo",
|
||||
"active": "\u00bfEst\u00e1 Activo?",
|
||||
"native_currency": "Native currency",
|
||||
"native_currency": "Moneda nativa",
|
||||
"trigger": "Disparador",
|
||||
"response": "Respuesta",
|
||||
"delivery": "Entrega",
|
||||
|
@@ -9,16 +9,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1",
|
||||
"laravel-vite-plugin": "^1",
|
||||
"laravel-vite-plugin": "^2",
|
||||
"patch-package": "^8",
|
||||
"sass": "^1",
|
||||
"vite": "^6",
|
||||
"vite": "^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-beta3",
|
||||
"admin-lte": "^4.0.0-rc4",
|
||||
"alpinejs": "^3.13.7",
|
||||
"bootstrap": "^5",
|
||||
"bootstrap5-autocomplete": "^1",
|
||||
|
@@ -25,7 +25,7 @@ export default class Dashboard {
|
||||
dashboard(start, end) {
|
||||
let startStr = format(start, 'y-MM-dd');
|
||||
let endStr = format(end, 'y-MM-dd');
|
||||
return api.get('/api/v1/chart/account/dashboard', {params: {fix: true, start: startStr, end: endStr}});
|
||||
return api.get('/api/v1/chart/account/dashboard', {params: {start: startStr, end: endStr}});
|
||||
}
|
||||
|
||||
expense(start, end) {
|
||||
|
6
resources/assets/v2/src/boot/bootstrap.js
vendored
6
resources/assets/v2/src/boot/bootstrap.js
vendored
@@ -48,6 +48,10 @@ window.bootstrap = bootstrap;
|
||||
|
||||
// always grab the preference "marker" from Firefly III.
|
||||
getFreshVariable('lastActivity').then((serverValue) => {
|
||||
if(null === serverValue) {
|
||||
console.log('Server value is null in getFreshVariable.');
|
||||
throw new Error('401 in getFreshVariable.');
|
||||
}
|
||||
const localValue = store.get('lastActivity');
|
||||
store.set('cacheValid', localValue === serverValue);
|
||||
store.set('lastActivity', serverValue);
|
||||
@@ -78,6 +82,8 @@ getFreshVariable('lastActivity').then((serverValue) => {
|
||||
window.bootstrapped = true;
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error('Error while bootstrapping: ' + error);
|
||||
});
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
@@ -30,3 +30,4 @@
|
||||
border:0;
|
||||
}
|
||||
|
||||
.skip-links {display: none;}
|
||||
|
@@ -315,7 +315,7 @@ let index = function () {
|
||||
// need to find the input thing
|
||||
console.log('Clicked edit button for account on index #' + index + ' and field ' + fieldName);
|
||||
const querySelector = 'input[data-field="' + fieldName + '"][data-index="' + index + '"]';
|
||||
console.log(querySelector);
|
||||
// console.log(querySelector);
|
||||
const newValue = document.querySelectorAll(querySelector)[0].value ?? '';
|
||||
if ('' === newValue) {
|
||||
return;
|
||||
@@ -352,12 +352,15 @@ let index = function () {
|
||||
|
||||
// filter instructions
|
||||
let filters = {};
|
||||
let type = this.filters.type;
|
||||
let active = this.filters.active;
|
||||
for (let k in this.filters) {
|
||||
if (this.filters.hasOwnProperty(k) && null !== this.filters[k]) {
|
||||
filters[k] = this.filters[k];
|
||||
//filters.push({column: k, filter: this.filters[k]});
|
||||
}
|
||||
}
|
||||
delete filters.type;
|
||||
|
||||
// get start and end from the store:
|
||||
const start = new Date(window.store.get('start'));
|
||||
@@ -367,24 +370,23 @@ let index = function () {
|
||||
let params = {
|
||||
sort: sorting,
|
||||
filter: filters,
|
||||
active: active,
|
||||
currentMoment: today,
|
||||
// type: type,
|
||||
page: {number: this.page},
|
||||
type: type,
|
||||
page: this.page,
|
||||
startPeriod: start,
|
||||
endPeriod: end
|
||||
};
|
||||
|
||||
if (!this.tableColumns.balance_difference.enabled) {
|
||||
delete params.startPeriod;
|
||||
delete params.enPeriod;
|
||||
delete params.endPeriod;
|
||||
}
|
||||
this.accounts = [];
|
||||
let groupedAccounts = {};
|
||||
// one page only.o
|
||||
(new Get()).index(params).then(response => {
|
||||
console.log(response);
|
||||
this.totalPages = response.meta.pagination.total_pages;
|
||||
console.log('a');
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
if (response.data.hasOwnProperty(i)) {
|
||||
let current = response.data[i];
|
||||
@@ -399,18 +401,14 @@ let index = function () {
|
||||
account_number: null === current.attributes.account_number ? '' : current.attributes.account_number,
|
||||
current_balance: current.attributes.current_balance,
|
||||
currency_code: current.attributes.currency_code,
|
||||
//native_current_balance: current.attributes.native_current_balance,
|
||||
//native_currency_code: current.attributes.native_currency_code,
|
||||
last_activity: null === current.attributes.last_activity ? '' : format(new Date(current.attributes.last_activity), i18next.t('config.month_and_day_fns')),
|
||||
//balance_difference: current.attributes.balance_difference,
|
||||
//native_balance_difference: current.attributes.native_balance_difference,
|
||||
liability_type: current.attributes.liability_type,
|
||||
liability_direction: current.attributes.liability_direction,
|
||||
interest: current.attributes.interest,
|
||||
interest_period: current.attributes.interest_period,
|
||||
//current_debt: current.attributes.current_debt,
|
||||
balance: current.attributes.balance,
|
||||
native_balance: current.attributes.native_balance,
|
||||
balances: current.attributes.balances,
|
||||
};
|
||||
// get group info:
|
||||
let groupId = current.attributes.object_group_id;
|
||||
|
@@ -40,24 +40,33 @@ export default () => ({
|
||||
loadingAccounts: false,
|
||||
accountList: [],
|
||||
convertToNative: false,
|
||||
convertToNativeAvailable: false,
|
||||
chartOptions: null,
|
||||
switchConvertToNative() {
|
||||
this.convertToNative = !this.convertToNative;
|
||||
setVariable('convertToNative', this.convertToNative);
|
||||
},
|
||||
localCacheKey(type) {
|
||||
return 'ds_accounts_' + type;
|
||||
},
|
||||
|
||||
eventListeners: {
|
||||
['@convert-to-native.window'](event){
|
||||
console.log('I heard that! (dashboard/accounts)');
|
||||
this.convertToNative = event.detail;
|
||||
this.accountList = [];
|
||||
chartData = null;
|
||||
this.loadChart();
|
||||
this.loadAccounts();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const chartCacheKey = getCacheKey(this.localCacheKey('chart'), {start: start, end: end})
|
||||
const chartCacheKey = getCacheKey(this.localCacheKey('chart'), {convertToNative: this.convertToNative, start: start, end: end})
|
||||
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(chartCacheKey);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
console.log('Generate from cache: ', chartCacheKey);
|
||||
this.drawChart(this.generateOptions(cachedData));
|
||||
this.loading = false;
|
||||
return;
|
||||
@@ -67,6 +76,7 @@ export default () => ({
|
||||
this.chartData = response.data;
|
||||
// cache generated options:
|
||||
window.store.set(chartCacheKey, response.data);
|
||||
console.log('Generate FRESH!');
|
||||
this.drawChart(this.generateOptions(this.chartData));
|
||||
this.loading = false;
|
||||
});
|
||||
@@ -90,18 +100,23 @@ export default () => ({
|
||||
dataset.label = current.label;
|
||||
|
||||
// use the "native" currency code and use the "native_entries" as array
|
||||
// if (this.convertToNative) {
|
||||
// currencies.push(current.native_currency_code);
|
||||
// dataset.currency_code = current.native_currency_code;
|
||||
// collection = Object.values(current.native_entries);
|
||||
// yAxis = 'y' + current.native_currency_code;
|
||||
// }
|
||||
// if (!this.convertToNative) {
|
||||
if (this.convertToNative) {
|
||||
currencies.push(current.native_currency_code);
|
||||
dataset.currency_code = current.native_currency_code;
|
||||
if(!current.hasOwnProperty('native_entries')) {
|
||||
console.error('No native entries ('+this.convertToNative+') found for account: ', current);
|
||||
}
|
||||
if(current.hasOwnProperty('native_entries')) {
|
||||
collection = Object.values(current.native_entries);
|
||||
}
|
||||
yAxis = 'y' + current.native_currency_code;
|
||||
}
|
||||
if (!this.convertToNative) {
|
||||
yAxis = 'y' + current.currency_code;
|
||||
dataset.currency_code = current.currency_code;
|
||||
currencies.push(current.currency_code);
|
||||
collection = Object.values(current.entries);
|
||||
// }
|
||||
}
|
||||
dataset.yAxisID = yAxis;
|
||||
dataset.data = collection;
|
||||
|
||||
@@ -196,6 +211,14 @@ export default () => ({
|
||||
(new Get).show(accountId, new Date(window.store.get('end'))).then((response) => {
|
||||
let parent = response.data.data;
|
||||
|
||||
// apply function to each element of parent:
|
||||
parent.attributes.balances = parent.attributes.balances.map((balance) => {
|
||||
balance.amount_formatted = formatMoney(balance.amount, balance.currency_code);
|
||||
return balance;
|
||||
});
|
||||
// console.log(parent);
|
||||
|
||||
|
||||
// get groups for account:
|
||||
const params = {
|
||||
page: 1,
|
||||
@@ -243,8 +266,7 @@ export default () => ({
|
||||
name: parent.attributes.name,
|
||||
order: parent.attributes.order,
|
||||
id: parent.id,
|
||||
balance: parent.attributes.balance,
|
||||
//native_balance: parent.attributes.native_balance,
|
||||
balances: parent.attributes.balances,
|
||||
groups: groups,
|
||||
});
|
||||
// console.log(parent.attributes);
|
||||
@@ -266,13 +288,16 @@ export default () => ({
|
||||
|
||||
init() {
|
||||
// console.log('accounts init');
|
||||
Promise.all([getVariable('viewRange', '1M'), getVariable('convertToNative', false), getVariable('language', 'en_US'),
|
||||
getConfiguration('cer.enabled', false)
|
||||
Promise.all([
|
||||
getVariable('viewRange', '1M'), // 0
|
||||
getVariable('convert_to_native', false), // 1
|
||||
getVariable('language', 'en_US'), // 2
|
||||
getConfiguration('cer.enabled', false) // 3
|
||||
]).then((values) => {
|
||||
//console.log('accounts after promises');
|
||||
this.convertToNative = values[1] && values[3];
|
||||
this.convertToNativeAvailable = values[3];
|
||||
afterPromises = true;
|
||||
//console.log('convertToNative in accounts.js: ', values);
|
||||
|
||||
// main dashboard chart:
|
||||
this.loadChart();
|
||||
@@ -289,7 +314,7 @@ export default () => ({
|
||||
this.loadChart();
|
||||
this.loadAccounts();
|
||||
});
|
||||
window.store.observe('convertToNative', () => {
|
||||
window.store.observe('convert_to_native', () => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -35,11 +35,21 @@ export default () => ({
|
||||
loading: false,
|
||||
boxData: null,
|
||||
boxOptions: null,
|
||||
eventListeners: {
|
||||
['@convert-to-native.window'](event){
|
||||
this.convertToNative = event.detail;
|
||||
this.accountList = [];
|
||||
console.log('I heard that! (dashboard/boxes)');
|
||||
this.boxData = null;
|
||||
this.loadBoxes();
|
||||
}
|
||||
},
|
||||
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
// TODO cache key is hard coded, problem?
|
||||
const boxesCacheKey = getCacheKey('ds_boxes_data', {start: start, end: end});
|
||||
const boxesCacheKey = getCacheKey('ds_boxes_data', {convertToNative: this.convertToNative, start: start, end: end});
|
||||
cleanupCache();
|
||||
|
||||
//const cacheValid = window.store.get('cacheValid');
|
||||
@@ -76,7 +86,7 @@ export default () => ({
|
||||
continue;
|
||||
}
|
||||
let key = current.key;
|
||||
console.log('NOT NATIVE');
|
||||
// console.log('NOT NATIVE');
|
||||
if (key.startsWith('balance-in-')) {
|
||||
this.balanceBox.amounts.push(formatMoney(current.monetary_value, current.currency_code));
|
||||
continue;
|
||||
@@ -153,7 +163,7 @@ export default () => ({
|
||||
init() {
|
||||
// console.log('boxes init');
|
||||
// TODO can be replaced by "getVariables"
|
||||
Promise.all([getVariable('viewRange'), getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('viewRange'), getVariable('convert_to_native', false)]).then((values) => {
|
||||
// console.log('boxes after promises');
|
||||
afterPromises = true;
|
||||
this.convertToNative = values[1];
|
||||
@@ -167,7 +177,7 @@ export default () => ({
|
||||
this.boxData = null;
|
||||
this.loadBoxes();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -48,9 +48,19 @@ export default () => ({
|
||||
}
|
||||
this.getFreshData();
|
||||
},
|
||||
|
||||
eventListeners: {
|
||||
['@convert-to-native.window'](event){
|
||||
console.log('I heard that! (dashboard/budgets)');
|
||||
this.convertToNative = event.detail;
|
||||
chartData = null;
|
||||
this.loadChart();
|
||||
}
|
||||
},
|
||||
|
||||
drawChart(options) {
|
||||
if (null !== chart) {
|
||||
chart.data.datasets = options.data.datasets;
|
||||
chart.data = options.data;
|
||||
chart.update();
|
||||
return;
|
||||
}
|
||||
@@ -59,8 +69,9 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey('ds_bdg_chart', {start: start, end: end});
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
const cacheKey = getCacheKey('ds_bdg_chart', {convertToNative: this.convertToNative, start: start, end: end});
|
||||
//const cacheValid = window.store.get('cacheValid');
|
||||
const cacheValid = false;
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
@@ -80,7 +91,7 @@ export default () => ({
|
||||
},
|
||||
generateOptions(data) {
|
||||
currencies = [];
|
||||
let options = getDefaultChartSettings('column');
|
||||
let options = getDefaultChartSettings('bar');
|
||||
options.options.locale = window.store.get('locale').replace('_', '-');
|
||||
options.options.plugins = {
|
||||
tooltip: {
|
||||
@@ -94,7 +105,7 @@ export default () => ({
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
return label + ' ' + formatMoney(context.parsed.y, currencies[context.parsed.x] ?? 'EUR');
|
||||
return label + ' ' + formatMoney(context.parsed.x, currencies[context.parsed.x] ?? 'EUR');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,28 +114,19 @@ export default () => ({
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: i18next.t('firefly.budgeted'),
|
||||
data: [],
|
||||
borderWidth: 1,
|
||||
backgroundColor: getColors('budgeted', 'background'),
|
||||
borderColor: getColors('budgeted', 'border'),
|
||||
},
|
||||
{
|
||||
//label: i18next.t('firefly.budgeted'),
|
||||
label: i18next.t('firefly.spent'),
|
||||
data: [],
|
||||
borderWidth: 1,
|
||||
stack: 1,
|
||||
backgroundColor: getColors('spent', 'background'),
|
||||
borderColor: getColors('spent', 'border'),
|
||||
},
|
||||
{
|
||||
label: i18next.t('firefly.left'),
|
||||
data: [],
|
||||
borderWidth: 1,
|
||||
stack: 1,
|
||||
backgroundColor: getColors('left', 'background'),
|
||||
borderColor: getColors('left', 'border'),
|
||||
},
|
||||
{
|
||||
label: i18next.t('firefly.overspent'),
|
||||
data: [],
|
||||
borderWidth: 1,
|
||||
stack: 1,
|
||||
backgroundColor: getColors('overspent', 'background'),
|
||||
borderColor: getColors('overspent', 'border'),
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -134,24 +136,16 @@ export default () => ({
|
||||
// // convert to EUR yes no?
|
||||
let label = current.label + ' (' + current.currency_code + ')';
|
||||
options.data.labels.push(label);
|
||||
if (this.convertToNative) {
|
||||
currencies.push(current.native_currency_code);
|
||||
// series 0: spent
|
||||
options.data.datasets[0].data.push(parseFloat(current.native_entries.spent) * -1);
|
||||
// series 1: left
|
||||
options.data.datasets[1].data.push(parseFloat(current.native_entries.left));
|
||||
// series 2: overspent
|
||||
options.data.datasets[2].data.push(parseFloat(current.native_entries.overspent));
|
||||
}
|
||||
if (!this.convertToNative) {
|
||||
currencies.push(current.currency_code);
|
||||
// series 0: spent
|
||||
options.data.datasets[0].data.push(parseFloat(current.entries.spent) * -1);
|
||||
// series 1: left
|
||||
options.data.datasets[1].data.push(parseFloat(current.entries.left));
|
||||
// series 2: overspent
|
||||
options.data.datasets[2].data.push(parseFloat(current.entries.overspent));
|
||||
}
|
||||
// label = current.label + ' (' + current.currency_code + ') b';
|
||||
// options.data.labels.push(label);
|
||||
|
||||
currencies.push(current.currency_code);
|
||||
// series 0: budgeted
|
||||
options.data.datasets[0].data.push(parseFloat(current.entries.budgeted));
|
||||
// series 1: spent
|
||||
options.data.datasets[1].data.push(parseFloat(current.entries.spent) * -1);
|
||||
// series 2: overspent
|
||||
// options.data.datasets[2].data.push(parseFloat(current.entries.overspent));
|
||||
}
|
||||
}
|
||||
// the currency format callback for the Y axis is AlWAYS based on whatever the first currency is.
|
||||
@@ -160,9 +154,10 @@ export default () => ({
|
||||
options.options.scales = {
|
||||
y: {
|
||||
ticks: {
|
||||
callback: function (context) {
|
||||
return formatMoney(context, currencies[0] ?? 'EUR');
|
||||
}
|
||||
// callback: function (context) {
|
||||
// return 'abc';
|
||||
// return formatMoney(context, currencies[0] ?? 'EUR');
|
||||
// }
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -172,7 +167,7 @@ export default () => ({
|
||||
|
||||
|
||||
init() {
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
afterPromises = true;
|
||||
if (false === this.loading) {
|
||||
@@ -189,7 +184,7 @@ export default () => ({
|
||||
this.loadChart();
|
||||
}
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -33,6 +33,17 @@ let afterPromises = false;
|
||||
export default () => ({
|
||||
loading: false,
|
||||
convertToNative: false,
|
||||
|
||||
eventListeners: {
|
||||
['@convert-to-native.window'](event){
|
||||
console.log('I heard that! (dashboard/categories)');
|
||||
this.convertToNative = event.detail;
|
||||
chartData = null;
|
||||
this.loadChart();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
generateOptions(data) {
|
||||
currencies = [];
|
||||
let options = getDefaultChartSettings('column');
|
||||
@@ -43,11 +54,6 @@ export default () => ({
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
let code = current.currency_code;
|
||||
// only use native code when doing auto conversion.
|
||||
if (this.convertToNative) {
|
||||
code = current.native_currency_code;
|
||||
}
|
||||
|
||||
if (!series.hasOwnProperty(code)) {
|
||||
series[code] = {
|
||||
name: code,
|
||||
@@ -65,9 +71,6 @@ export default () => ({
|
||||
let yAxis = 'y';
|
||||
let current = data[i];
|
||||
let code = current.currency_code;
|
||||
if (this.convertToNative) {
|
||||
code = current.native_currency_code;
|
||||
}
|
||||
|
||||
// loop series, add 0 if not present or add actual amount.
|
||||
for (const ii in series) {
|
||||
@@ -77,10 +80,6 @@ export default () => ({
|
||||
// this series' currency matches this column's currency.
|
||||
amount = parseFloat(current.amount);
|
||||
yAxis = 'y' + current.currency_code;
|
||||
if (this.convertToNative) {
|
||||
amount = parseFloat(current.native_amount);
|
||||
yAxis = 'y' + current.native_currency_code;
|
||||
}
|
||||
}
|
||||
if (series[ii].data.hasOwnProperty(current.label)) {
|
||||
// there is a value for this particular currency. The amount from this column will be added.
|
||||
@@ -147,7 +146,7 @@ export default () => ({
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey('ds_ct_chart', {start: start, end: end});
|
||||
const cacheKey = getCacheKey('ds_ct_chart', {convertToNative: this.convertToNative, start: start, end: end});
|
||||
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
@@ -183,7 +182,7 @@ export default () => ({
|
||||
},
|
||||
init() {
|
||||
// console.log('categories init');
|
||||
Promise.all([getVariable('convertToNative', false),]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false),]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
afterPromises = true;
|
||||
this.loadChart();
|
||||
@@ -195,7 +194,7 @@ export default () => ({
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -46,7 +46,8 @@ import {
|
||||
} from "chart.js";
|
||||
import 'chartjs-adapter-date-fns';
|
||||
import {showInternalsButton} from "../../support/page-settings/show-internals-button.js";
|
||||
import {showWizardButton} from "../../support/page-settings/show-wizard-button.js";
|
||||
import {setVariable} from "../../store/set-variable.js";
|
||||
import {getVariable} from "../../store/get-variable.js";
|
||||
|
||||
// register things
|
||||
Chart.register({
|
||||
@@ -66,7 +67,26 @@ Chart.register({
|
||||
Legend
|
||||
});
|
||||
|
||||
let index = function () {
|
||||
return {
|
||||
convertToNative: false,
|
||||
saveNativeSettings(event) {
|
||||
let target = event.currentTarget || event.target;
|
||||
setVariable('convert_to_native',target.checked).then(() => {
|
||||
console.log('Set convert to native to: ', target.checked);
|
||||
this.$dispatch('convert-to-native', target.checked);
|
||||
});
|
||||
},
|
||||
init() {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const comps = {
|
||||
index,
|
||||
dates,
|
||||
boxes,
|
||||
accounts,
|
||||
|
@@ -36,7 +36,7 @@ export default () => ({
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
// needs user data.
|
||||
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {start: start, end: end});
|
||||
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, {convertToNative: this.convertToNative, start: start, end: end});
|
||||
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
@@ -129,7 +129,7 @@ export default () => ({
|
||||
init() {
|
||||
// console.log('piggies init');
|
||||
apiData = [];
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
|
||||
afterPromises = true;
|
||||
this.convertToNative = values[0];
|
||||
@@ -144,7 +144,7 @@ export default () => ({
|
||||
apiData = [];
|
||||
this.loadPiggyBanks();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -29,17 +29,16 @@ import i18next from "i18next";
|
||||
Chart.register({SankeyController, Flow});
|
||||
|
||||
const SANKEY_CACHE_KEY = 'ds_sankey_data';
|
||||
let currencies = [];
|
||||
let afterPromises = false;
|
||||
let chart = null;
|
||||
let transactions = [];
|
||||
let convertToNative = false;
|
||||
let translations = {
|
||||
let currencies = [];
|
||||
let afterPromises = false;
|
||||
let chart = null;
|
||||
let transactions = [];
|
||||
let convertToNative = false;
|
||||
let translations = {
|
||||
category: null,
|
||||
unknown_category: null,
|
||||
in: null,
|
||||
out: null,
|
||||
// TODO
|
||||
unknown_source: null,
|
||||
unknown_dest: null,
|
||||
unknown_account: null,
|
||||
@@ -80,74 +79,97 @@ const getColor = function (key) {
|
||||
|
||||
// little helper
|
||||
function getObjectName(type, name, direction, code) {
|
||||
if(convertToNative) {
|
||||
return getObjectNameWithoutCurrency(type, name, direction);
|
||||
}
|
||||
return getObjectNameWithCurrency(type, name, direction, code);
|
||||
|
||||
// category 4x
|
||||
if ('category' === type && null !== name && 'in' === direction) {
|
||||
return translations.category + ' "' + name + '" (' + translations.in + (convertToNative ? ', ' + code + ')' : ')');
|
||||
}
|
||||
if ('category' === type && null === name && 'in' === direction) {
|
||||
return translations.unknown_category + ' (' + translations.in + (convertToNative ? ', ' + code + ')' : ')');
|
||||
}
|
||||
if ('category' === type && null !== name && 'out' === direction) {
|
||||
return translations.category + ' "' + name + '" (' + translations.out + (convertToNative ? ', ' + code + ')' : ')');
|
||||
}
|
||||
if ('category' === type && null === name && 'out' === direction) {
|
||||
return translations.unknown_category + ' (' + translations.out + (convertToNative ? ', ' + code + ')' : ')');
|
||||
}
|
||||
// account 4x
|
||||
if ('account' === type && null === name && 'in' === direction) {
|
||||
return translations.unknown_source + (convertToNative ? ' (' + code + ')' : '');
|
||||
}
|
||||
if ('account' === type && null !== name && 'in' === direction) {
|
||||
return translations.revenue_account + '"' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
|
||||
}
|
||||
if ('account' === type && null === name && 'out' === direction) {
|
||||
return translations.unknown_dest + (convertToNative ? ' (' + code + ')' : '');
|
||||
}
|
||||
if ('account' === type && null !== name && 'out' === direction) {
|
||||
return translations.expense_account + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
|
||||
}
|
||||
|
||||
// budget 2x
|
||||
if ('budget' === type && null !== name) {
|
||||
return translations.budget + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
|
||||
}
|
||||
if ('budget' === type && null === name) {
|
||||
return translations.unknown_budget + (convertToNative ? ' (' + code + ')' : '');
|
||||
}
|
||||
console.error('Cannot handle: type:"' + type + '", dir: "' + direction + '"');
|
||||
}
|
||||
|
||||
function getLabelName(type, name, code) {
|
||||
// category
|
||||
if ('category' === type && null !== name) {
|
||||
return translations.category + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
|
||||
function getObjectNameWithoutCurrency(type, name, direction) {
|
||||
if('category' === type) {
|
||||
let catName = null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
|
||||
let directionText = 'in' === direction ? translations.in : translations.out;
|
||||
return catName + ' (' + directionText + ')';
|
||||
}
|
||||
if ('category' === type && null === name) {
|
||||
return translations.unknown_category + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('account' === type) {
|
||||
let accountName = null === name ? translations.unknown_account : name;
|
||||
let directionText = 'in' === direction ? translations.in : translations.out;
|
||||
let fullAccountName = 'in' === direction ? translations.revenue_account + ' "' + accountName + '"' : translations.expense_account + ' "' + accountName + '"';
|
||||
return fullAccountName + ' (' + directionText + ')';
|
||||
}
|
||||
// account
|
||||
if ('account' === type && null === name) {
|
||||
return translations.unknown_account + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('budget' === type) {
|
||||
return null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"';
|
||||
}
|
||||
if ('account' === type && null !== name) {
|
||||
return name + (convertToNative ? ' (' + code + ')' : '');
|
||||
console.error('[a] Cannot handle: type:"' + type + '", dir: "' + direction + '"');
|
||||
}
|
||||
function getObjectNameWithCurrency(type, name, direction, code) {
|
||||
if('category' === type) {
|
||||
let catName = null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
|
||||
let directionText = 'in' === direction ? translations.in : translations.out;
|
||||
return catName + ' (' + directionText + ', ' + code + ')';
|
||||
}
|
||||
|
||||
// budget 2x
|
||||
if ('budget' === type && null !== name) {
|
||||
return translations.budget + ' "' + name + '"' + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('account' === type) {
|
||||
let accountName = null === name ? translations.unknown_account : name;
|
||||
let directionText = 'in' === direction ? translations.in : translations.out;
|
||||
let fullAccountName = 'in' === direction ? translations.revenue_account + ' "' + accountName + '"' : translations.expense_account + ' "' + accountName + '"';
|
||||
return fullAccountName + ' (' + directionText + ', ' + code + ')';
|
||||
}
|
||||
if ('budget' === type && null === name) {
|
||||
return translations.unknown_budget + (convertToNative ? ' (' + code + ')' : '');
|
||||
if('budget' === type) {
|
||||
return (null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"') + ' (' + code + ')';
|
||||
}
|
||||
console.error('Cannot handle: type:"' + type + '"');
|
||||
console.error('[b] Cannot handle: type:"' + type + '", dir: "' + direction + '"');
|
||||
}
|
||||
|
||||
|
||||
function getLabel(type, name, code) {
|
||||
if(convertToNative) {
|
||||
return getLabelWithoutCurrency(type, name);
|
||||
}
|
||||
return getLabelWithCurrency(type, name, code);
|
||||
|
||||
}
|
||||
|
||||
function getLabelWithoutCurrency(type, name) {
|
||||
if('category' === type) {
|
||||
return null === name ? translations.unknown_category : translations.category + ' "' + name + '"';
|
||||
}
|
||||
if('account' === type) {
|
||||
return null === name ? translations.unknown_account : name;
|
||||
}
|
||||
if('budget' === type) {
|
||||
return null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"';
|
||||
}
|
||||
console.error('[a] Cannot handle: type:"' + type + '"');
|
||||
}
|
||||
function getLabelWithCurrency(type, name, code) {
|
||||
if('category' === type) {
|
||||
return (null === name ? translations.unknown_category : translations.category + ' "' + name + '"') + ' ('+ code + ')';
|
||||
}
|
||||
if('account' === type) {
|
||||
return (null === name ? translations.unknown_account : name) + ' (' + code + ')';
|
||||
}
|
||||
if('budget' === type) {
|
||||
return (null === name ? translations.unknown_budget : translations.budget + ' "' + name + '"') + ' (' + code + ')';;
|
||||
}
|
||||
console.error('[b] Cannot handle: type:"' + type + '"');
|
||||
}
|
||||
|
||||
export default () => ({
|
||||
loading: false,
|
||||
convertToNative: false,
|
||||
processedData: null,
|
||||
eventListeners: {
|
||||
['@convert-to-native.window'](event){
|
||||
console.log('I heard that! (dashboard/sankey)');
|
||||
this.convertToNative = event.detail;
|
||||
convertToNative = event.detail;
|
||||
this.processedData = null;
|
||||
this.loadChart();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
generateOptions() {
|
||||
let options = getDefaultChartSettings('sankey');
|
||||
|
||||
@@ -155,120 +177,22 @@ export default () => ({
|
||||
currencies = [];
|
||||
|
||||
// variables collected for the sankey chart:
|
||||
let amounts = {};
|
||||
let labels = {};
|
||||
for (let i in transactions) {
|
||||
if (transactions.hasOwnProperty(i)) {
|
||||
let group = transactions[i];
|
||||
for (let ii in group.attributes.transactions) {
|
||||
if (group.attributes.transactions.hasOwnProperty(ii)) {
|
||||
// properties of the transaction, used in the generation of the chart:
|
||||
let transaction = group.attributes.transactions[ii];
|
||||
let currencyCode = this.convertToNative ? transaction.native_currency_code : transaction.currency_code;
|
||||
let amount = this.convertToNative ? parseFloat(transaction.native_amount) : parseFloat(transaction.amount);
|
||||
let flowKey;
|
||||
|
||||
/*
|
||||
Two entries in the sankey diagram for deposits:
|
||||
1. From the revenue account (source) to a category (in).
|
||||
2. From the category (in) to the big inbox.
|
||||
*/
|
||||
if ('deposit' === transaction.type) {
|
||||
// nr 1
|
||||
let category = getObjectName('category', transaction.category_name, 'in', currencyCode);
|
||||
let revenueAccount = getObjectName('account', transaction.source_name, 'in', currencyCode);
|
||||
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
|
||||
labels[revenueAccount] = getLabelName('account', transaction.source_name, currencyCode);
|
||||
flowKey = revenueAccount + '-' + category + '-' + currencyCode;
|
||||
if (!amounts.hasOwnProperty(flowKey)) {
|
||||
amounts[flowKey] = {
|
||||
from: revenueAccount,
|
||||
to: category,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
amounts[flowKey].amount += amount;
|
||||
|
||||
// nr 2
|
||||
flowKey = category + '-' + translations.all_money + '-' + currencyCode;
|
||||
if (!amounts.hasOwnProperty(flowKey)) {
|
||||
amounts[flowKey] = {
|
||||
from: category,
|
||||
to: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
amounts[flowKey].amount += amount;
|
||||
}
|
||||
/*
|
||||
Three entries in the sankey diagram for withdrawals:
|
||||
1. From the big box to a budget.
|
||||
2. From a budget to a category.
|
||||
3. From a category to an expense account.
|
||||
*/
|
||||
if ('withdrawal' === transaction.type) {
|
||||
// 1.
|
||||
let budget = getObjectName('budget', transaction.budget_name, 'out', currencyCode);
|
||||
labels[budget] = getLabelName('budget', transaction.budget_name, currencyCode);
|
||||
flowKey = translations.all_money + '-' + budget + '-' + currencyCode;
|
||||
|
||||
if (!amounts.hasOwnProperty(flowKey)) {
|
||||
amounts[flowKey] = {
|
||||
from: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
|
||||
to: budget,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
amounts[flowKey].amount += amount;
|
||||
|
||||
|
||||
// 2.
|
||||
let category = getObjectName('category', transaction.category_name, 'out', currencyCode);
|
||||
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
|
||||
flowKey = budget + '-' + category + '-' + currencyCode;
|
||||
|
||||
if (!amounts.hasOwnProperty(flowKey)) {
|
||||
amounts[flowKey] = {
|
||||
from: budget,
|
||||
to: category,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
amounts[flowKey].amount += amount;
|
||||
|
||||
// 3.
|
||||
let expenseAccount = getObjectName('account', transaction.destination_name, 'out', currencyCode);
|
||||
labels[expenseAccount] = getLabelName('account', transaction.destination_name, currencyCode);
|
||||
flowKey = category + '-' + expenseAccount + '-' + currencyCode;
|
||||
|
||||
if (!amounts.hasOwnProperty(flowKey)) {
|
||||
amounts[flowKey] = {
|
||||
from: category,
|
||||
to: expenseAccount,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
amounts[flowKey].amount += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.parseTransactionGroups(transactions);
|
||||
|
||||
let dataSet =
|
||||
// sankey chart has one data set.
|
||||
{
|
||||
label: 'Firefly III dashboard sankey chart',
|
||||
data: [],
|
||||
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].from : ''),
|
||||
colorTo: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].to : ''),
|
||||
colorMode: 'gradient', // or 'from' or 'to'
|
||||
labels: labels,
|
||||
size: 'min', // or 'min' if flow overlap is preferred
|
||||
};
|
||||
for (let i in amounts) {
|
||||
if (amounts.hasOwnProperty(i)) {
|
||||
let amount = amounts[i];
|
||||
// sankey chart has one data set.
|
||||
{
|
||||
label: 'Firefly III dashboard sankey chart',
|
||||
data: [],
|
||||
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].from : ''),
|
||||
colorTo: (c) => getColor(c.dataset.data[c.dataIndex] ? c.dataset.data[c.dataIndex].to : ''),
|
||||
colorMode: 'gradient', // or 'from' or 'to'
|
||||
labels: this.processedData.labels,
|
||||
size: 'min', // or 'min' if flow overlap is preferred
|
||||
};
|
||||
for (let i in this.processedData.amounts) {
|
||||
if (this.processedData.amounts.hasOwnProperty(i)) {
|
||||
let amount = this.processedData.amounts[i];
|
||||
dataSet.data.push({from: amount.from, to: amount.to, flow: amount.amount});
|
||||
}
|
||||
}
|
||||
@@ -276,6 +200,133 @@ export default () => ({
|
||||
|
||||
return options;
|
||||
},
|
||||
parseTransactionGroups(groups) {
|
||||
this.processedData = {
|
||||
amounts: {},
|
||||
labels: {}
|
||||
};
|
||||
for (let i in groups) {
|
||||
if (groups.hasOwnProperty(i)) {
|
||||
let group = groups[i];
|
||||
this.parseTransactionGroup(group);
|
||||
}
|
||||
}
|
||||
},
|
||||
parseTransactionGroup(group) {
|
||||
for (let ii in group.attributes.transactions) {
|
||||
if (group.attributes.transactions.hasOwnProperty(ii)) {
|
||||
// properties of the transaction, used in the generation of the chart:
|
||||
let transaction = group.attributes.transactions[ii];
|
||||
this.parseTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
parseTransaction(transaction) {
|
||||
let currencyCode = transaction.currency_code;
|
||||
let amount = parseFloat(transaction.amount);
|
||||
let flowKey;
|
||||
if (this.convertToNative) {
|
||||
currencyCode = transaction.native_currency_code;
|
||||
amount = parseFloat(transaction.native_amount);
|
||||
}
|
||||
if ('deposit' === transaction.type) {
|
||||
this.parseDeposit(transaction, currencyCode, amount);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('withdrawal' === transaction.type) {
|
||||
this.parseWithdrawal(transaction, currencyCode, amount);
|
||||
}
|
||||
},
|
||||
parseWithdrawal(transaction, currencyCode, amount) {
|
||||
/*
|
||||
Three entries in the sankey diagram for withdrawals:
|
||||
1. From the big box to a budget.
|
||||
2. From a budget to a category.
|
||||
3. From a category to an expense account.
|
||||
*/
|
||||
|
||||
// first one:
|
||||
let budget = getObjectName('budget', transaction.budget_name, 'out', currencyCode);
|
||||
this.processedData.labels[budget] = getLabel('budget', transaction.budget_name, currencyCode);
|
||||
let flowKey = translations.all_money + '-' + budget + '-' + currencyCode;
|
||||
|
||||
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
|
||||
this.processedData.amounts[flowKey] = {
|
||||
from: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
|
||||
to: budget,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
this.processedData.amounts[flowKey].amount += amount;
|
||||
|
||||
|
||||
// second one:
|
||||
let category = getObjectName('category', transaction.category_name, 'out', currencyCode);
|
||||
this.processedData.labels[category] = getLabel('category', transaction.category_name, currencyCode);
|
||||
flowKey = budget + '-' + category + '-' + currencyCode;
|
||||
|
||||
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
|
||||
this.processedData.amounts[flowKey] = {
|
||||
from: budget,
|
||||
to: category,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
this.processedData.amounts[flowKey].amount += amount;
|
||||
|
||||
// third one:
|
||||
let expenseAccount = getObjectName('account', transaction.destination_name, 'out', currencyCode);
|
||||
this.processedData.labels[expenseAccount] = getLabel('account', transaction.destination_name, currencyCode);
|
||||
flowKey = category + '-' + expenseAccount + '-' + currencyCode;
|
||||
|
||||
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
|
||||
this.processedData.amounts[flowKey] = {
|
||||
from: category,
|
||||
to: expenseAccount,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
this.processedData.amounts[flowKey].amount += amount;
|
||||
},
|
||||
parseDeposit(transaction, currencyCode, amount) {
|
||||
/*
|
||||
Two entries in the sankey diagram for deposits:
|
||||
1. From the revenue account (source) to a category (in).
|
||||
2. From the category (in) to the big inbox.
|
||||
*/
|
||||
|
||||
// this is the first one:
|
||||
let category = getObjectName('category', transaction.category_name, 'in', currencyCode);
|
||||
let revenueAccount = getObjectName('account', transaction.source_name, 'in', currencyCode);
|
||||
let flowKey = revenueAccount + '-' + category + '-' + currencyCode;
|
||||
this.processedData.labels[category] = getLabel('category', transaction.category_name, currencyCode);
|
||||
this.processedData.labels[revenueAccount] = getLabel('account', transaction.source_name, currencyCode);
|
||||
|
||||
// create if necessary:
|
||||
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
|
||||
this.processedData.amounts[flowKey] = {
|
||||
from: revenueAccount,
|
||||
to: category,
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
this.processedData.amounts[flowKey].amount += amount;
|
||||
|
||||
// this is the second one:
|
||||
flowKey = category + '-' + translations.all_money + '-' + currencyCode;
|
||||
if (!this.processedData.amounts.hasOwnProperty(flowKey)) {
|
||||
this.processedData.amounts[flowKey] = {
|
||||
from: category,
|
||||
to: translations.all_money + (this.convertToNative ? ' (' + currencyCode + ')' : ''),
|
||||
amount: 0
|
||||
};
|
||||
}
|
||||
this.processedData.amounts[flowKey].amount += amount;
|
||||
},
|
||||
|
||||
|
||||
drawChart(options) {
|
||||
if (null !== chart) {
|
||||
chart.data.datasets = options.data.datasets;
|
||||
@@ -286,12 +337,12 @@ export default () => ({
|
||||
|
||||
},
|
||||
getFreshData() {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
|
||||
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
let cachedData = window.store.get(cacheKey);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
transactions = cachedData;
|
||||
@@ -310,9 +361,9 @@ export default () => ({
|
||||
this.downloadTransactions(params);
|
||||
},
|
||||
downloadTransactions(params) {
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {start: start, end: end});
|
||||
const start = new Date(window.store.get('start'));
|
||||
const end = new Date(window.store.get('end'));
|
||||
const cacheKey = getCacheKey(SANKEY_CACHE_KEY, {convertToNative: this.convertToNative, start: start, end: end});
|
||||
|
||||
//console.log('Downloading page ' + params.page + '...');
|
||||
const getter = new Get();
|
||||
@@ -348,26 +399,27 @@ export default () => ({
|
||||
init() {
|
||||
// console.log('sankey init');
|
||||
transactions = [];
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
convertToNative = values[0];
|
||||
// some translations:
|
||||
translations.all_money = i18next.t('firefly.all_money');
|
||||
translations.category = i18next.t('firefly.category');
|
||||
translations.in = i18next.t('firefly.money_flowing_in');
|
||||
translations.out = i18next.t('firefly.money_flowing_out');
|
||||
translations.unknown_category = i18next.t('firefly.unknown_category_plain');
|
||||
translations.unknown_source = i18next.t('firefly.unknown_source_plain');
|
||||
translations.unknown_dest = i18next.t('firefly.unknown_dest_plain');
|
||||
translations.unknown_account = i18next.t('firefly.unknown_any_plain');
|
||||
translations.unknown_budget = i18next.t('firefly.unknown_budget_plain');
|
||||
translations.expense_account = i18next.t('firefly.expense_account');
|
||||
translations.revenue_account = i18next.t('firefly.revenue_account');
|
||||
translations.budget = i18next.t('firefly.budget');
|
||||
convertToNative = values[0];
|
||||
|
||||
// console.log('sankey after promises');
|
||||
afterPromises = true;
|
||||
this.loadChart();
|
||||
// some translations:
|
||||
translations.all_money = i18next.t('firefly.all_money');
|
||||
translations.category = i18next.t('firefly.category');
|
||||
translations.in = i18next.t('firefly.money_flowing_in');
|
||||
translations.out = i18next.t('firefly.money_flowing_out');
|
||||
translations.unknown_category = i18next.t('firefly.unknown_category_plain');
|
||||
translations.unknown_source = i18next.t('firefly.unknown_source_plain');
|
||||
translations.unknown_dest = i18next.t('firefly.unknown_dest_plain');
|
||||
translations.unknown_account = i18next.t('firefly.unknown_any_plain');
|
||||
translations.unknown_budget = i18next.t('firefly.unknown_budget_plain');
|
||||
translations.expense_account = i18next.t('firefly.expense_account');
|
||||
translations.revenue_account = i18next.t('firefly.revenue_account');
|
||||
translations.budget = i18next.t('firefly.budget');
|
||||
|
||||
// console.log('sankey after promises');
|
||||
afterPromises = true;
|
||||
this.loadChart();
|
||||
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
@@ -378,7 +430,7 @@ export default () => ({
|
||||
this.transactions = [];
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -31,6 +31,99 @@ let afterPromises = false;
|
||||
let apiData = [];
|
||||
let subscriptionData = {};
|
||||
|
||||
function addObjectGroupInfo(data) {
|
||||
let objectGroupId = parseInt(data.object_group_id);
|
||||
if (!subscriptionData.hasOwnProperty(objectGroupId)) {
|
||||
subscriptionData[objectGroupId] = {
|
||||
id: objectGroupId,
|
||||
title: null === data.object_group_title ? i18next.t('firefly.default_group_title_name_plain') : data.object_group_title,
|
||||
order: parseInt(data.object_group_order),
|
||||
payment_info: {},
|
||||
bills: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function parsePayDates(list) {
|
||||
let newList = [];
|
||||
for(let i in list) {
|
||||
if (list.hasOwnProperty(i)) {
|
||||
let current = list[i];
|
||||
// convert to date object:
|
||||
newList.push(new Date(current));
|
||||
}
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
|
||||
function parseBillInfo(data) {
|
||||
let result = {
|
||||
id: data.id,
|
||||
name: data.attributes.name,
|
||||
amount_min: data.attributes.amount_min,
|
||||
amount_max: data.attributes.amount_max,
|
||||
amount: (parseFloat(data.attributes.amount_max) + parseFloat(data.attributes.amount_min)) / 2,
|
||||
currency_code: data.attributes.currency_code,
|
||||
// paid transactions:
|
||||
transactions: [],
|
||||
// unpaid moments
|
||||
pay_dates: parsePayDates(data.attributes.pay_dates),
|
||||
paid: data.attributes.paid_dates.length > 0,
|
||||
};
|
||||
// set variables
|
||||
result.expected_amount = formatMoney(result.amount, result.currency_code);
|
||||
result.expected_times = i18next.t('firefly.subscr_expected_x_times', {
|
||||
times: data.attributes.pay_dates.length,
|
||||
amount: result.expected_amount
|
||||
});
|
||||
// console.log(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function parsePaidTransactions(paid_dates, bill) {
|
||||
if( !paid_dates || paid_dates.length < 1) {
|
||||
return [];
|
||||
}
|
||||
let result = [];
|
||||
// add transactions (simpler version)
|
||||
for (let i in paid_dates) {
|
||||
if (paid_dates.hasOwnProperty(i)) {
|
||||
const currentPayment = paid_dates[i];
|
||||
// console.log(currentPayment);
|
||||
// math: -100+(paid/expected)*100
|
||||
let percentage = Math.round(-100 + ((parseFloat(currentPayment.amount) ) / parseFloat(bill.amount)) * 100);
|
||||
let currentTransaction = {
|
||||
amount: formatMoney(currentPayment.amount, currentPayment.currency_code),
|
||||
percentage: percentage,
|
||||
date: format(new Date(currentPayment.date), 'PP'),
|
||||
foreign_amount: null,
|
||||
};
|
||||
if (null !== currentPayment.foreign_currency_code) {
|
||||
currentTransaction.foreign_amount = currentPayment.foreign_amount;
|
||||
currentTransaction.foreign_currency_code = currentPayment.foreign_currency_code;
|
||||
}
|
||||
|
||||
result.push(currentTransaction);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function isInRange(bill) {
|
||||
let start = new Date(window.store.get('start'));
|
||||
let end = new Date(window.store.get('end'));
|
||||
for(let i in bill.pay_dates) {
|
||||
if (bill.pay_dates.hasOwnProperty(i)) {
|
||||
let currentDate = bill.pay_dates[i];
|
||||
//console.log(currentDate);
|
||||
if (currentDate >= start && currentDate <= end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function downloadSubscriptions(params) {
|
||||
const getter = new Get();
|
||||
return getter.list(params)
|
||||
@@ -41,83 +134,25 @@ function downloadSubscriptions(params) {
|
||||
for (let i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
//console.log(current);
|
||||
if (current.attributes.active && current.attributes.pay_dates.length > 0) {
|
||||
let objectGroupId = null === current.attributes.object_group_id ? 0 : current.attributes.object_group_id;
|
||||
let objectGroupTitle = null === current.attributes.object_group_title ? i18next.t('firefly.default_group_title_name_plain') : current.attributes.object_group_title;
|
||||
let objectGroupOrder = null === current.attributes.object_group_order ? 0 : current.attributes.object_group_order;
|
||||
if (!subscriptionData.hasOwnProperty(objectGroupId)) {
|
||||
subscriptionData[objectGroupId] = {
|
||||
id: objectGroupId,
|
||||
title: objectGroupTitle,
|
||||
order: objectGroupOrder,
|
||||
payment_info: {},
|
||||
bills: [],
|
||||
};
|
||||
// create or update object group
|
||||
let objectGroupId = parseInt(current.attributes.object_group_id);
|
||||
addObjectGroupInfo(current.attributes);
|
||||
|
||||
// create and update the bill.
|
||||
let bill = parseBillInfo(current);
|
||||
|
||||
// if not yet paid, and pay_dates is not in current rage, ignore it.
|
||||
if (false === bill.paid && !isInRange(bill)) {
|
||||
console.warn('Bill "'+bill.name+'" is not paid and not in range, ignoring: ');
|
||||
continue;
|
||||
}
|
||||
// TODO this conversion needs to be inside some kind of a parsing class.
|
||||
let bill = {
|
||||
id: current.id,
|
||||
name: current.attributes.name,
|
||||
// amount
|
||||
amount_min: current.attributes.amount_min,
|
||||
amount_max: current.attributes.amount_max,
|
||||
amount: (parseFloat(current.attributes.amount_max) + parseFloat(current.attributes.amount_min)) / 2,
|
||||
currency_code: current.attributes.currency_code,
|
||||
|
||||
// native amount
|
||||
// native_amount_min: current.attributes.native_amount_min,
|
||||
// native_amount_max: current.attributes.native_amount_max,
|
||||
// native_amount: (parseFloat(current.attributes.native_amount_max) + parseFloat(current.attributes.native_amount_min)) / 2,
|
||||
// native_currency_code: current.attributes.native_currency_code,
|
||||
|
||||
// paid transactions:
|
||||
transactions: [],
|
||||
|
||||
// unpaid moments
|
||||
pay_dates: current.attributes.pay_dates,
|
||||
paid: current.attributes.paid_dates.length > 0,
|
||||
};
|
||||
// set variables
|
||||
bill.expected_amount = formatMoney(bill.amount, bill.currency_code);
|
||||
bill.expected_times = i18next.t('firefly.subscr_expected_x_times', {
|
||||
times: current.attributes.pay_dates.length,
|
||||
amount: bill.expected_amount
|
||||
});
|
||||
|
||||
// add transactions (simpler version)
|
||||
for (let iii in current.attributes.paid_dates) {
|
||||
if (current.attributes.paid_dates.hasOwnProperty(iii)) {
|
||||
const currentPayment = current.attributes.paid_dates[iii];
|
||||
let percentage = 100;
|
||||
// math: -100+(paid/expected)*100
|
||||
if (params.convertToNative) {
|
||||
percentage = Math.round(-100 + ((parseFloat(currentPayment.native_amount) * -1) / parseFloat(bill.native_amount)) * 100);
|
||||
}
|
||||
if (!params.convertToNative) {
|
||||
percentage = Math.round(-100 + ((parseFloat(currentPayment.amount) * -1) / parseFloat(bill.amount)) * 100);
|
||||
}
|
||||
// TODO fix me
|
||||
currentPayment.currency_code = 'EUR';
|
||||
console.log('Currency code: "'+currentPayment+'"');
|
||||
console.log(currentPayment);
|
||||
let currentTransaction = {
|
||||
amount: formatMoney(currentPayment.amount, currentPayment.currency_code),
|
||||
percentage: percentage,
|
||||
date: format(new Date(currentPayment.date), 'PP'),
|
||||
foreign_amount: null,
|
||||
};
|
||||
if (null !== currentPayment.foreign_currency_code) {
|
||||
currentTransaction.foreign_amount = currentPayment.foreign_amount;
|
||||
currentTransaction.foreign_currency_code = currentPayment.foreign_currency_code;
|
||||
}
|
||||
|
||||
bill.transactions.push(currentTransaction);
|
||||
}
|
||||
}
|
||||
bill.transactions = parsePaidTransactions(current.attributes.paid_dates, bill);
|
||||
|
||||
subscriptionData[objectGroupId].bills.push(bill);
|
||||
if (0 === current.attributes.paid_dates.length) {
|
||||
if (false === bill.paid) {
|
||||
// bill is unpaid, count the "pay_dates" and multiply with the "amount".
|
||||
// since bill is unpaid, this can only be in currency amount and native currency amount.
|
||||
const totalAmount = current.attributes.pay_dates.length * bill.amount;
|
||||
@@ -128,13 +163,10 @@ function downloadSubscriptions(params) {
|
||||
currency_code: bill.currency_code,
|
||||
paid: 0,
|
||||
unpaid: 0,
|
||||
native_currency_code: bill.native_currency_code,
|
||||
native_paid: 0,
|
||||
//native_unpaid: 0,
|
||||
};
|
||||
}
|
||||
|
||||
subscriptionData[objectGroupId].payment_info[bill.currency_code].unpaid += totalAmount;
|
||||
//subscriptionData[objectGroupId].payment_info[bill.currency_code].native_unpaid += totalNativeAmount;
|
||||
}
|
||||
|
||||
if (current.attributes.paid_dates.length > 0) {
|
||||
@@ -142,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)) {
|
||||
@@ -151,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,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'));
|
||||
@@ -220,6 +256,7 @@ export default () => ({
|
||||
//console.log(group);
|
||||
}
|
||||
}
|
||||
console.log('Subscriptions: ', this.subscriptions);
|
||||
|
||||
// then assign to this.subscriptions.
|
||||
this.loading = false;
|
||||
@@ -269,7 +306,7 @@ export default () => ({
|
||||
},
|
||||
|
||||
init() {
|
||||
Promise.all([getVariable('convertToNative', false)]).then((values) => {
|
||||
Promise.all([getVariable('convert_to_native', false)]).then((values) => {
|
||||
this.convertToNative = values[0];
|
||||
afterPromises = true;
|
||||
|
||||
@@ -287,7 +324,7 @@ export default () => ({
|
||||
this.startSubscriptions();
|
||||
}
|
||||
});
|
||||
window.store.observe('convertToNative', (newValue) => {
|
||||
window.store.observe('convert_to_native', (newValue) => {
|
||||
if (!afterPromises) {
|
||||
return;
|
||||
}
|
||||
|
@@ -71,7 +71,7 @@ let index = function () {
|
||||
init() {
|
||||
// TODO need date range.
|
||||
// TODO handle page number
|
||||
this.getTransactions(this.page);``
|
||||
this.getTransactions(this.page);
|
||||
|
||||
// Your Javascript code to create the grid
|
||||
// dataTable = createGrid(document.querySelector('#grid'), gridOptions);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* adminlte-filteres.scss
|
||||
* adminlte-filtered.scss
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
@@ -23,7 +23,7 @@
|
||||
// copied from:
|
||||
|
||||
/*!
|
||||
* AdminLTE v4.0.0-alpha2
|
||||
* AdminLTE v4.0.0-rc3
|
||||
* Author: Colorlib
|
||||
* Website: AdminLTE.io <https://adminlte.io>
|
||||
* License: Open source - MIT <https://opensource.org/licenses/MIT>
|
||||
@@ -84,7 +84,6 @@
|
||||
// Bootstrap Utilities
|
||||
@import "bootstrap/scss/utilities/api";
|
||||
|
||||
|
||||
// AdminLTE Configuration
|
||||
// ---------------------------------------------------
|
||||
@import "admin-lte/src/scss/variables";
|
||||
@@ -97,9 +96,12 @@
|
||||
@import "admin-lte/src/scss/parts/components";
|
||||
@import "admin-lte/src/scss/parts/extra-components";
|
||||
|
||||
// @import "admin-lte/src/scss/parts/pages";
|
||||
@import "admin-lte/src/scss/pages/login_and_register";
|
||||
//
|
||||
// Part: Pages
|
||||
//
|
||||
|
||||
@import "admin-lte/src/scss/pages/login_and_register";
|
||||
@import "admin-lte/src/scss/parts/miscellaneous";
|
||||
|
||||
|
||||
// AdminLTE Accessibility Styles - WCAG 2.1 AA Compliance
|
||||
@import "admin-lte/src/scss/accessibility";
|
||||
|
@@ -26,6 +26,30 @@ $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;}
|
||||
|
||||
/*
|
||||
Remove bottom margin from unstyled lists
|
||||
*/
|
||||
@@ -56,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;
|
||||
}
|
||||
@@ -73,12 +88,12 @@ td:not(:hover) .hidden-edit-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
// Bootstrap
|
||||
// @import "bootstrap/scss/bootstrap";
|
||||
|
||||
|
||||
// @import "~bootstrap-sass/assets/stylesheets/bootstrap";
|
||||
|
||||
// hover buttons
|
||||
|
||||
|
||||
// Font awesome
|
||||
//@import "~font-awesome/css/font-awesome";
|
||||
$fa-font-path: "@fortawesome/fontawesome-free/webfonts";
|
||||
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/solid.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/brands.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/regular.scss";
|
||||
|
@@ -27,19 +27,29 @@ export function getConfiguration(name, defaultValue = null) {
|
||||
// to make things available quicker than if the store has to grab it through the API.
|
||||
// then again, it's not that slow.
|
||||
if (validCache && window.hasOwnProperty(name)) {
|
||||
// console.log('Get from window');
|
||||
console.log('Return configuration "' + name + '" from window: ' + window[name]);
|
||||
return Promise.resolve(window[name]);
|
||||
}
|
||||
// load from store2, if it's present.
|
||||
const fromStore = window.store.get(name);
|
||||
if (validCache && typeof fromStore !== 'undefined') {
|
||||
console.log('Return configuration "' + name + '" from store: ' + fromStore);
|
||||
return Promise.resolve(fromStore);
|
||||
}
|
||||
let getter = (new Get);
|
||||
return getter.getByName(name).then((response) => {
|
||||
// console.log('Get "' + name + '" from API');
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
}).catch(() => {
|
||||
console.log('Return configuration "' + name + '" from API: ' + parseConfigurationResponse(name, response));
|
||||
return Promise.resolve(parseConfigurationResponse(name, response));
|
||||
}).catch((error) => {
|
||||
console.log('Returning "'+name+'" from DEFAULT: ' + defaultValue);
|
||||
console.warn(error);
|
||||
return defaultValue;
|
||||
});
|
||||
}
|
||||
export function parseConfigurationResponse(name, response) {
|
||||
let value = response.data.data.value;
|
||||
window.store.set(name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@@ -26,13 +26,17 @@ export function getFreshVariable(name, defaultValue = null) {
|
||||
return getter.getByName(name).then((response) => {
|
||||
// console.log('Get from API');
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
}).catch(() => {
|
||||
// preference does not exist (yet).
|
||||
// POST it and then return it anyway.
|
||||
let poster = (new Post);
|
||||
poster.post(name, defaultValue).then((response) => {
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
});
|
||||
}).catch((response) => {
|
||||
if(response.status === 404) {
|
||||
// preference does not exist (yet).
|
||||
// POST it and then return it anyway.
|
||||
let poster = (new Post);
|
||||
poster.post(name, defaultValue).then((response) => {
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
});
|
||||
return;
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -27,16 +27,19 @@ export function getVariable(name, defaultValue = null) {
|
||||
// to make things available quicker than if the store has to grab it through the API.
|
||||
// then again, it's not that slow.
|
||||
if (validCache && window.hasOwnProperty(name)) {
|
||||
console.log('Returning "'+name+'" from window: ' + window[name]);
|
||||
return Promise.resolve(window[name]);
|
||||
}
|
||||
// load from store2, if it's present.
|
||||
const fromStore = window.store.get(name);
|
||||
if (validCache && typeof fromStore !== 'undefined') {
|
||||
console.log('Returning "'+name+'" from store: ' + fromStore);
|
||||
return Promise.resolve(fromStore);
|
||||
}
|
||||
let getter = (new Get);
|
||||
|
||||
return getter.getByName(name).then((response) => {
|
||||
console.log('Returning "'+name+'" from server: ' + parseResponse(name, response));
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
}).catch((error) => {
|
||||
if('' === defaultValue) {
|
||||
@@ -47,6 +50,7 @@ export function getVariable(name, defaultValue = null) {
|
||||
// POST it and then return it anyway.
|
||||
let poster = (new Post);
|
||||
return poster.post(name, defaultValue).then((response) => {
|
||||
console.log('Returning "'+name+'" from POST: ' + parseResponse(name, response));
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
});
|
||||
});
|
||||
|
@@ -28,19 +28,25 @@ export function setVariable(name, value = null) {
|
||||
// then again, it's not that slow.
|
||||
|
||||
// set in window.x
|
||||
// window[name] = value;
|
||||
window[name] = value;
|
||||
|
||||
// set in store:
|
||||
window.store.set(name, value);
|
||||
|
||||
// post to user preferences (because why not):
|
||||
let putter = new Put();
|
||||
putter.put(name, value).then((response) => {
|
||||
}).catch(() => {
|
||||
return putter.put(name, value).then((response) => {
|
||||
console.log('set "'+name+'" to value: ', value);
|
||||
return Promise.resolve(response);
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
// preference does not exist (yet).
|
||||
// POST it
|
||||
let poster = (new Post);
|
||||
poster.post(name, value).then((response) => {
|
||||
poster.post(name, value).then((response) => {
|
||||
console.log('POST "'+name+'" to value: ', value);
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
@@ -62,6 +62,36 @@ function getDefaultChartSettings(type) {
|
||||
},
|
||||
};
|
||||
}
|
||||
if('bar' === type) {
|
||||
return {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
indexAxis: 'y',
|
||||
// Elements options apply to all the options unless overridden in a dataset
|
||||
// In this case, we are setting the border of each horizontal bar to be 2px wide
|
||||
elements: {
|
||||
bar: {
|
||||
borderWidth: 2,
|
||||
}
|
||||
},
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Chart.js Horizontal Bar Chart'
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
if ('line' === type) {
|
||||
return {
|
||||
options: {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user