mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-24 12:11:22 +00:00
Compare commits
28 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d4004d1ed | ||
|
|
ae60cd5b28 | ||
|
|
ab31a72199 | ||
|
|
2c1b9534f3 | ||
|
|
7028cb1546 | ||
|
|
dc1ecf6a42 | ||
|
|
3a27f9d02c | ||
|
|
4b27ab38f8 | ||
|
|
40de147611 | ||
|
|
bb4f90d730 | ||
|
|
d89d46aaec | ||
|
|
304d720c4c | ||
|
|
7eff160190 | ||
|
|
8b2e18ed9d | ||
|
|
7001051833 | ||
|
|
b4b9752c05 | ||
|
|
acadc89eaa | ||
|
|
6ff84b8e90 | ||
|
|
7f3e3fc3bf | ||
|
|
02233fd7a4 | ||
|
|
50d3db0643 | ||
|
|
3751831779 | ||
|
|
14a24e47fb | ||
|
|
b7e78cb0e6 | ||
|
|
a8f65f42fc | ||
|
|
d3385a116d | ||
|
|
33d11b4780 | ||
|
|
07c49d1d04 |
96
.ci/php-cs-fixer/composer.lock
generated
96
.ci/php-cs-fixer/composer.lock
generated
@@ -1259,16 +1259,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.1.5",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee"
|
||||
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee",
|
||||
"reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
|
||||
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1332,7 +1332,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.1.5"
|
||||
"source": "https://github.com/symfony/console/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1348,7 +1348,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-20T08:28:38+00:00"
|
||||
"time": "2024-10-09T08:46:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -1419,16 +1419,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7"
|
||||
"reference": "87254c78dd50721cfd015b62277a8281c5589702"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
|
||||
"reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702",
|
||||
"reference": "87254c78dd50721cfd015b62277a8281c5589702",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1479,7 +1479,7 @@
|
||||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1495,7 +1495,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
@@ -1575,16 +1575,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v7.1.5",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a"
|
||||
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a",
|
||||
"reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/c835867b3c62bb05c7fe3d637c871c7ae52024d4",
|
||||
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1621,7 +1621,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.1.5"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1637,20 +1637,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-17T09:16:35+00:00"
|
||||
"time": "2024-10-25T15:11:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v7.1.4",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "d95bbf319f7d052082fb7af147e0f835a695e823"
|
||||
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823",
|
||||
"reference": "d95bbf319f7d052082fb7af147e0f835a695e823",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8",
|
||||
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1685,7 +1685,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v7.1.4"
|
||||
"source": "https://github.com/symfony/finder/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1701,20 +1701,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-08-13T14:28:19+00:00"
|
||||
"time": "2024-10-01T08:31:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/options-resolver.git",
|
||||
"reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55"
|
||||
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55",
|
||||
"reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85",
|
||||
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1752,7 +1752,7 @@
|
||||
"options"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1768,7 +1768,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
@@ -2246,16 +2246,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.1.5",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "5c03ee6369281177f07f7c68252a280beccba847"
|
||||
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847",
|
||||
"reference": "5c03ee6369281177f07f7c68252a280beccba847",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
|
||||
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2287,7 +2287,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.1.5"
|
||||
"source": "https://github.com/symfony/process/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2303,7 +2303,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-19T21:48:23+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -2390,16 +2390,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/stopwatch",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/stopwatch.git",
|
||||
"reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d"
|
||||
"reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
|
||||
"reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
|
||||
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05",
|
||||
"reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2432,7 +2432,7 @@
|
||||
"description": "Provides a way to profile code",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/stopwatch/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/stopwatch/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2448,20 +2448,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.1.5",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "d66f9c343fa894ec2037cc928381df90a7ad4306"
|
||||
"reference": "61b72d66bf96c360a727ae6232df5ac83c71f626"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306",
|
||||
"reference": "d66f9c343fa894ec2037cc928381df90a7ad4306",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/61b72d66bf96c360a727ae6232df5ac83c71f626",
|
||||
"reference": "61b72d66bf96c360a727ae6232df5ac83c71f626",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2519,7 +2519,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.1.5"
|
||||
"source": "https://github.com/symfony/string/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2535,7 +2535,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-20T08:28:38+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
2
.github/workflows/close-duplicates.yml
vendored
2
.github/workflows/close-duplicates.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
close_duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/command@v1.2.1
|
||||
- uses: github/command@v1.2.2
|
||||
id: command
|
||||
with:
|
||||
allowed_contexts: "issue"
|
||||
|
||||
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@@ -35,4 +35,5 @@ jobs:
|
||||
Thank you for your contributions.
|
||||
days-before-stale: 14
|
||||
days-before-close: 7
|
||||
exempt-issue-labels: 'enhancement,feature,bug,announcement,epic,triage'
|
||||
exempt-all-milestones: true
|
||||
exempt-issue-labels: 'triage'
|
||||
|
||||
@@ -68,9 +68,9 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function accounts(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$queryParameters = $request->getParameters();
|
||||
$result = $this->repository->searchAccount($queryParameters['query'], $queryParameters['account_types'], $queryParameters['size']);
|
||||
$return = [];
|
||||
$params = $request->getParameters();
|
||||
$result = $this->repository->searchAccount($params['query'], $params['account_types'], $params['page'], $params['size']);
|
||||
$return = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
@@ -89,6 +89,7 @@ class AccountController extends Controller
|
||||
'title' => $account->name,
|
||||
'meta' => [
|
||||
'type' => $account->accountType->type,
|
||||
// TODO is multi currency property.
|
||||
'currency_id' => null === $currency ? null : (string) $currency->id,
|
||||
'currency_code' => $currency?->code,
|
||||
'currency_symbol' => $currency?->symbol,
|
||||
|
||||
@@ -23,15 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Request\Autocomplete;
|
||||
|
||||
use FireflyIII\JsonApi\Rules\IsValidFilter;
|
||||
use FireflyIII\JsonApi\Rules\IsValidPage;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use LaravelJsonApi\Core\Query\QueryParameters;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
/**
|
||||
* Class AutocompleteRequest
|
||||
@@ -51,35 +48,46 @@ class AutocompleteRequest extends FormRequest
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
$queryParameters = QueryParameters::cast($this->all());
|
||||
|
||||
return [
|
||||
'date' => $this->dateOrToday($queryParameters, 'date'),
|
||||
'query' => $this->arrayOfStrings($queryParameters, 'query'),
|
||||
'size' => $this->integerFromQueryParams($queryParameters, 'size', 50),
|
||||
'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
|
||||
$array = [
|
||||
'date' => $this->convertDateTime('date'),
|
||||
'query' => $this->clearString((string) $this->get('query')),
|
||||
'size' => $this->integerFromValue('size'),
|
||||
'page' => $this->integerFromValue('page'),
|
||||
'account_types' => $this->arrayFromValue($this->get('account_types')),
|
||||
'transaction_types' => $this->arrayFromValue($this->get('transaction_types')),
|
||||
];
|
||||
$array['size'] = $array['size'] < 1 || $array['size'] > 100 ? 15 : $array['size'];
|
||||
$array['page'] = max($array['page'], 1);
|
||||
if (null === $array['account_types']) {
|
||||
$array['account_types'] = [];
|
||||
}
|
||||
if (null === $array['transaction_types']) {
|
||||
$array['transaction_types'] = [];
|
||||
}
|
||||
|
||||
// remove 'initial balance' from allowed types. its internal
|
||||
$array['account_types'] = array_diff($array['account_types'], [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION, AccountType::CREDITCARD]);
|
||||
$array['account_types'] = $this->getAccountTypeParameter($array['account_types']);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$valid = array_keys($this->types);
|
||||
|
||||
return [
|
||||
'fields' => JsonApiRule::notSupported(),
|
||||
'filter' => ['nullable', 'array', new IsValidFilter(['query', 'date', 'account_types'])],
|
||||
'include' => JsonApiRule::notSupported(),
|
||||
'page' => ['nullable', 'array', new IsValidPage(['size'])],
|
||||
'sort' => JsonApiRule::notSupported(),
|
||||
'date' => 'nullable|date|after:1900-01-01|before:2100-01-01',
|
||||
'query' => 'nullable|string',
|
||||
'size' => 'nullable|integer|min:1|max:100',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'account_types' => sprintf('nullable|in:%s', implode(',', $valid)),
|
||||
'transaction_types' => 'nullable|in:todo',
|
||||
];
|
||||
}
|
||||
|
||||
private function getAccountTypeParameter(mixed $types): array
|
||||
private function getAccountTypeParameter(array $types): array
|
||||
{
|
||||
if (is_string($types) && str_contains($types, ',')) {
|
||||
$types = explode(',', $types);
|
||||
}
|
||||
if (!is_iterable($types)) {
|
||||
$types = [$types];
|
||||
}
|
||||
$return = [];
|
||||
foreach ($types as $type) {
|
||||
$return = array_merge($return, $this->mapAccountTypes($type));
|
||||
|
||||
@@ -24,8 +24,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V2\Request\Chart;
|
||||
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\JsonApi\Rules\IsValidFilter;
|
||||
use FireflyIII\Rules\IsFilterValueIn;
|
||||
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
@@ -33,8 +31,6 @@ use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Validator;
|
||||
use LaravelJsonApi\Core\Query\QueryParameters;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
/**
|
||||
* Class ChartRequest
|
||||
@@ -50,52 +46,29 @@ class ChartRequest extends FormRequest
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
$queryParameters = QueryParameters::cast($this->all());
|
||||
|
||||
// $queryParameters = QueryParameters::cast($this->all());
|
||||
return [
|
||||
'start' => $this->dateOrToday($queryParameters, 'start')->startOfDay(),
|
||||
'end' => $this->dateOrToday($queryParameters, 'end')->endOfDay(),
|
||||
'preselected' => $this->stringFromQueryParams($queryParameters, 'preselected', 'empty'),
|
||||
'period' => $this->stringFromFilterParams($queryParameters, 'period', '1M'),
|
||||
'accounts' => $this->arrayOfStrings($queryParameters, 'accounts'),
|
||||
// preselected heeft maar een paar toegestane waardes, dat moet ook goed gaan.
|
||||
// 'query' => $this->arrayOfStrings($queryParameters, 'query'),
|
||||
// 'size' => $this->integerFromQueryParams($queryParameters,'size', 50),
|
||||
// 'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
|
||||
'start' => $this->convertDateTime('start')?->startOfDay(),
|
||||
'end' => $this->convertDateTime('end')?->endOfDay(),
|
||||
'preselected' => $this->convertString('preselected', 'empty'),
|
||||
'period' => $this->convertString('period', '1M'),
|
||||
'accounts' => $this->arrayFromValue($this->get('accounts')),
|
||||
];
|
||||
// collect accounts based on this list?
|
||||
}
|
||||
|
||||
// return [
|
||||
// 'accounts' => $this->getAccountList(),
|
||||
// 'preselected' => $this->convertString('preselected'),
|
||||
// ];
|
||||
// }
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'fields' => JsonApiRule::notSupported(),
|
||||
'filter' => ['nullable', 'array',
|
||||
new IsValidFilter(['start', 'end', 'preselected', 'accounts', 'period']),
|
||||
new IsFilterValueIn('preselected', config('firefly.preselected_accounts')),
|
||||
],
|
||||
'include' => JsonApiRule::notSupported(),
|
||||
'page' => JsonApiRule::notSupported(),
|
||||
'sort' => JsonApiRule::notSupported(),
|
||||
// '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: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',
|
||||
'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',
|
||||
];
|
||||
|
||||
// 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',
|
||||
// 'preselected' => sprintf('in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
// 'accounts.*' => 'exists:accounts,id',
|
||||
// ];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
|
||||
@@ -51,7 +51,11 @@ class FixUnevenAmount extends Command
|
||||
$this->convertOldStyleTransfers();
|
||||
$this->fixUnevenAmounts();
|
||||
$this->matchCurrencies();
|
||||
AccountBalanceCalculator::forceRecalculateAll();
|
||||
if (config('firefly.feature_flags.running_balance_column')) {
|
||||
$this->friendlyInfo('Will recalculate transaction running balance columns. This may take a LONG time. Please be patient.');
|
||||
AccountBalanceCalculator::recalculateAll(true);
|
||||
$this->friendlyInfo('Done recalculating transaction running balance columns.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,15 @@ class Cron extends Command
|
||||
protected $signature = 'firefly-iii:cron
|
||||
{--F|force : Force the cron job(s) to execute.}
|
||||
{--date= : Set the date in YYYY-MM-DD to make Firefly III think that\'s the current date.}
|
||||
{--download-cer : Download exchange rates. Other tasks will be skipped unless also requested.}
|
||||
{--create-recurring : Create recurring transactions. Other tasks will be skipped unless also requested.}
|
||||
{--create-auto-budgets : Create auto budgets. Other tasks will be skipped unless also requested.}
|
||||
{--send-bill-warnings : Send bill warnings. Other tasks will be skipped unless also requested.}
|
||||
';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$doAll = !$this->option('download-cer') && !$this->option('create-recurring') && !$this->option('create-auto-budgets') && !$this->option('send-bill-warnings');
|
||||
$date = null;
|
||||
|
||||
try {
|
||||
@@ -60,7 +65,7 @@ class Cron extends Command
|
||||
$force = (bool)$this->option('force'); // @phpstan-ignore-line
|
||||
|
||||
// Fire exchange rates cron job.
|
||||
if (true === config('cer.download_enabled')) {
|
||||
if (true === config('cer.download_enabled') && ($doAll || $this->option('download-cer'))) {
|
||||
try {
|
||||
$this->exchangeRatesCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
@@ -71,30 +76,36 @@ class Cron extends Command
|
||||
}
|
||||
|
||||
// Fire recurring transaction cron job.
|
||||
try {
|
||||
$this->recurringCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
if ($doAll || $this->option('create-recurring')) {
|
||||
try {
|
||||
$this->recurringCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fire auto-budget cron job:
|
||||
try {
|
||||
$this->autoBudgetCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
if ($doAll || $this->option('create-auto-budgets')) {
|
||||
try {
|
||||
$this->autoBudgetCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fire bill warning cron job
|
||||
try {
|
||||
$this->billWarningCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
if ($doAll || $this->option('send-bill-warnings')) {
|
||||
try {
|
||||
$this->billWarningCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->friendlyInfo('More feedback on the cron jobs can be found in the log files.');
|
||||
|
||||
@@ -33,6 +33,7 @@ use Illuminate\Console\Command;
|
||||
class CorrectAccountBalance extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
public const string CONFIG_NAME = '610_correct_balances';
|
||||
protected $description = 'Recalculate all account balance amounts';
|
||||
protected $signature = 'firefly-iii:correct-account-balance {--F|force : Force the execution of this command.}';
|
||||
@@ -44,23 +45,30 @@ class CorrectAccountBalance extends Command
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->friendlyWarning('This command has been disabled.');
|
||||
$this->markAsExecuted();
|
||||
if (config('firefly.feature_flags.running_balance_column')) {
|
||||
$this->friendlyInfo('Will recalculate account balances. This may take a LONG time. Please be patient.');
|
||||
$this->markAsExecuted();
|
||||
$this->correctBalanceAmounts();
|
||||
$this->friendlyInfo('Done recalculating account balances.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->friendlyWarning('This command has been disabled.');
|
||||
|
||||
// $this->correctBalanceAmounts();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function correctBalanceAmounts(): void
|
||||
{
|
||||
AccountBalanceCalculator::recalculateAll();
|
||||
return;
|
||||
AccountBalanceCalculator::recalculateAll(true);
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool)$configVar?->data;
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
|
||||
@@ -54,6 +54,7 @@ class MigrateRuleActions extends Command
|
||||
}
|
||||
$this->replaceEqualSign();
|
||||
$this->replaceObsoleteActions();
|
||||
$this->markAsExecuted();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -179,4 +180,9 @@ class MigrateRuleActions extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class DebugController extends Controller
|
||||
|
||||
// also do some recalculations.
|
||||
Artisan::call('firefly-iii:trigger-credit-recalculation');
|
||||
AccountBalanceCalculator::forceRecalculateAll();
|
||||
AccountBalanceCalculator::recalculateAll(true);
|
||||
|
||||
try {
|
||||
Artisan::call('twig:clean');
|
||||
|
||||
@@ -107,7 +107,6 @@ class JavascriptController extends Controller
|
||||
$lang = $pref->data;
|
||||
$dateRange = $this->getDateRangeConfig();
|
||||
$uid = substr(hash('sha256', sprintf('%s-%s-%s', (string)config('app.key'), auth()->user()->id, auth()->user()->email)), 0, 12);
|
||||
|
||||
$data = [
|
||||
'currencyCode' => $currency->code,
|
||||
'currencySymbol' => $currency->symbol,
|
||||
|
||||
@@ -65,6 +65,16 @@ class FrontpageController extends Controller
|
||||
$info[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
// sort by current percentage (lowest at the top)
|
||||
uasort(
|
||||
$info,
|
||||
static function (array $a, array $b) {
|
||||
return $a['percentage'] <=> $b['percentage'];
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
$html = '';
|
||||
if (0 !== count($info)) {
|
||||
try {
|
||||
|
||||
@@ -77,17 +77,18 @@ class ShowController extends Controller
|
||||
*/
|
||||
public function show(Recurrence $recurrence)
|
||||
{
|
||||
$repos = app(AttachmentRepositoryInterface::class);
|
||||
$repos = app(AttachmentRepositoryInterface::class);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
|
||||
$array = $transformer->transform($recurrence);
|
||||
$array = $transformer->transform($recurrence);
|
||||
|
||||
$groups = $this->recurring->getTransactions($recurrence);
|
||||
$today = today(config('app.timezone'));
|
||||
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
|
||||
$groups = $this->recurring->getTransactions($recurrence);
|
||||
$today = today(config('app.timezone'));
|
||||
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
|
||||
$array['journal_count'] = $this->recurring->getJournalCount($recurrence);
|
||||
|
||||
// transform dates back to Carbon objects and expand information
|
||||
foreach ($array['repetitions'] as $index => $repetition) {
|
||||
@@ -103,9 +104,9 @@ class ShowController extends Controller
|
||||
}
|
||||
|
||||
// add attachments to the recurrence object.
|
||||
$attachments = $recurrence->attachments()->get();
|
||||
$array['attachments'] = [];
|
||||
$attachmentTransformer = app(AttachmentTransformer::class);
|
||||
$attachments = $recurrence->attachments()->get();
|
||||
$array['attachments'] = [];
|
||||
$attachmentTransformer = app(AttachmentTransformer::class);
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($attachments as $attachment) {
|
||||
@@ -114,7 +115,16 @@ class ShowController extends Controller
|
||||
$array['attachments'][] = $item;
|
||||
}
|
||||
|
||||
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
|
||||
if (null !== $array['nr_of_repetitions']) {
|
||||
$left = $array['nr_of_repetitions'] - $array['journal_count'];
|
||||
$left = max(0, $left);
|
||||
// limit each repetition to X occurrences:
|
||||
foreach ($array['repetitions'] as $index => $repetition) {
|
||||
$array['repetitions'][$index]['occurrences'] = array_slice($repetition['occurrences'], 0, $left);
|
||||
}
|
||||
}
|
||||
|
||||
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
|
||||
|
||||
return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups', 'today'));
|
||||
}
|
||||
|
||||
@@ -177,10 +177,11 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
// has repeated X times.
|
||||
$journalCount = $this->repository->getJournalCount($recurrence);
|
||||
if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) {
|
||||
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions));
|
||||
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $journalCount));
|
||||
|
||||
return false;
|
||||
}
|
||||
app('log')->debug(sprintf('Recurrence #%d has run %d times, max is %d times.', $recurrence->id, $journalCount, $recurrence->repetitions));
|
||||
|
||||
// is no longer running
|
||||
if ($this->repeatUntilHasPassed($recurrence)) {
|
||||
@@ -202,8 +203,8 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
sprintf(
|
||||
'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.',
|
||||
$recurrence->id,
|
||||
$recurrence->first_date->format('Y-m-d'),
|
||||
$this->date->format('Y-m-d')
|
||||
$recurrence->first_date->format('Y-m-d H:i:s'),
|
||||
$this->date->format('Y-m-d H:i:s')
|
||||
)
|
||||
);
|
||||
|
||||
@@ -244,9 +245,10 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
private function hasNotStartedYet(Recurrence $recurrence): bool
|
||||
{
|
||||
$startDate = $this->getStartDate($recurrence);
|
||||
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d H:i:s')));
|
||||
app('log')->debug(sprintf('Ask date is %s', $this->date->format('Y-m-d H:i:s')));
|
||||
|
||||
return $startDate->gt($this->date);
|
||||
return $startDate->gte($this->date);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,6 +37,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
@@ -167,12 +168,16 @@ class TransactionJournal extends Model
|
||||
|
||||
public function scopeAfter(EloquentBuilder $query, Carbon $date): EloquentBuilder
|
||||
{
|
||||
return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00'));
|
||||
Log::debug(sprintf('scopeAfter("%s")', $date->format('Y-m-d H:i:s')));
|
||||
|
||||
return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function scopeBefore(EloquentBuilder $query, Carbon $date): EloquentBuilder
|
||||
{
|
||||
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00'));
|
||||
Log::debug(sprintf('scopeBefore("%s")', $date->format('Y-m-d H:i:s')));
|
||||
|
||||
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function scopeTransactionTypes(EloquentBuilder $query, array $types): void
|
||||
|
||||
@@ -306,6 +306,8 @@ class BillRepository implements BillRepositoryInterface
|
||||
{
|
||||
// app('log')->debug('Now in getPaidDatesInRange()');
|
||||
|
||||
Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString()));
|
||||
|
||||
return $bill->transactionJournals()
|
||||
->before($end)->after($start)->get(
|
||||
[
|
||||
|
||||
@@ -67,13 +67,13 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
$set
|
||||
= TransactionJournalMeta::where(static function (Builder $q1) use ($recurrence): void {
|
||||
$q1->where('name', 'recurrence_id');
|
||||
$q1->where('data', json_encode((string)$recurrence->id));
|
||||
$q1->where('data', json_encode((string) $recurrence->id));
|
||||
})->get(['journal_meta.transaction_journal_id']);
|
||||
|
||||
// there are X journals made for this recurrence. Any of them meant for today?
|
||||
foreach ($set as $journalMeta) {
|
||||
$count = TransactionJournalMeta::where(static function (Builder $q2) use ($date): void {
|
||||
$string = (string)$date;
|
||||
$string = (string) $date;
|
||||
app('log')->debug(sprintf('Search for date: %s', json_encode($string)));
|
||||
$q2->where('name', 'recurrence_date');
|
||||
$q2->where('data', json_encode($string));
|
||||
@@ -141,7 +141,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/** @var RecurrenceTransactionMeta $meta */
|
||||
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
|
||||
if ('bill_id' === $meta->name) {
|
||||
$return = (int)$meta->value;
|
||||
$return = (int) $meta->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/** @var RecurrenceTransactionMeta $meta */
|
||||
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
|
||||
if ('budget_id' === $meta->name) {
|
||||
$return = (int)$meta->value;
|
||||
$return = (int) $meta->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/** @var RecurrenceTransactionMeta $meta */
|
||||
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
|
||||
if ('category_id' === $meta->name) {
|
||||
$return = (int)$meta->value;
|
||||
$return = (int) $meta->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/** @var RecurrenceTransactionMeta $meta */
|
||||
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
|
||||
if ('category_name' === $meta->name) {
|
||||
$return = (string)$meta->value;
|
||||
$return = (string) $meta->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
*/
|
||||
public function getJournalCount(Recurrence $recurrence, ?Carbon $start = null, ?Carbon $end = null): int
|
||||
{
|
||||
Log::debug(sprintf('Now in getJournalCount(#%d, "%s", "%s")', $recurrence->id, $start?->format('Y-m-d H:i:s'), $end?->format('Y-m-d H:i:s')));
|
||||
$query = TransactionJournal::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transaction_journals.user_id', $recurrence->user_id)
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
@@ -216,8 +217,10 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
if (null !== $end) {
|
||||
$query->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00'));
|
||||
}
|
||||
$count = $query->count('transaction_journals.id');
|
||||
Log::debug(sprintf('Count is %d', $count));
|
||||
|
||||
return $query->count('transaction_journals.id');
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +231,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
return TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
->where('journal_meta.name', '=', 'recurrence_id')
|
||||
->where('journal_meta.data', '=', json_encode((string)$recurrence->id))
|
||||
->where('journal_meta.data', '=', json_encode((string) $recurrence->id))
|
||||
->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray()
|
||||
;
|
||||
}
|
||||
@@ -241,7 +244,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/** @var null|Note $note */
|
||||
$note = $recurrence->notes()->first();
|
||||
|
||||
return (string)$note?->text;
|
||||
return (string) $note?->text;
|
||||
}
|
||||
|
||||
public function getPiggyBank(RecurrenceTransaction $transaction): ?int
|
||||
@@ -251,7 +254,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/** @var RecurrenceTransactionMeta $metaEntry */
|
||||
foreach ($meta as $metaEntry) {
|
||||
if ('piggy_bank_id' === $metaEntry->name) {
|
||||
return (int)$metaEntry->value;
|
||||
return (int) $metaEntry->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,12 +284,12 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
->where('name', 'recurrence_id')
|
||||
->where('data', json_encode((string)$recurrence->id))
|
||||
->where('data', json_encode((string) $recurrence->id))
|
||||
->get()->pluck('transaction_journal_id')->toArray()
|
||||
;
|
||||
$search = [];
|
||||
foreach ($journalMeta as $journalId) {
|
||||
$search[] = (int)$journalId;
|
||||
$search[] = (int) $journalId;
|
||||
}
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
@@ -314,13 +317,13 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
->where('name', 'recurrence_id')
|
||||
->where('data', json_encode((string)$recurrence->id))
|
||||
->where('data', json_encode((string) $recurrence->id))
|
||||
->get()->pluck('transaction_journal_id')->toArray()
|
||||
;
|
||||
$search = [];
|
||||
|
||||
foreach ($journalMeta as $journalId) {
|
||||
$search[] = (int)$journalId;
|
||||
$search[] = (int) $journalId;
|
||||
}
|
||||
if (0 === count($search)) {
|
||||
return new Collection();
|
||||
@@ -442,28 +445,28 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
if (is_array($language)) {
|
||||
$language = 'en_US';
|
||||
}
|
||||
$language = (string)$language;
|
||||
$language = (string) $language;
|
||||
if ('daily' === $repetition->repetition_type) {
|
||||
return (string)trans('firefly.recurring_daily', [], $language);
|
||||
return (string) trans('firefly.recurring_daily', [], $language);
|
||||
}
|
||||
if ('weekly' === $repetition->repetition_type) {
|
||||
$dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language);
|
||||
if ($repetition->repetition_skip > 0) {
|
||||
return (string)trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language);
|
||||
return (string) trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language);
|
||||
}
|
||||
|
||||
return (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
|
||||
return (string) trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
|
||||
}
|
||||
if ('monthly' === $repetition->repetition_type) {
|
||||
if ($repetition->repetition_skip > 0) {
|
||||
return (string)trans(
|
||||
return (string) trans(
|
||||
'firefly.recurring_monthly_skip',
|
||||
['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip + 1],
|
||||
$language
|
||||
);
|
||||
}
|
||||
|
||||
return (string)trans(
|
||||
return (string) trans(
|
||||
'firefly.recurring_monthly',
|
||||
['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1],
|
||||
$language
|
||||
@@ -474,7 +477,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
// first part is number of week, second is weekday.
|
||||
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language);
|
||||
|
||||
return (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
|
||||
return (string) trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
|
||||
}
|
||||
if ('yearly' === $repetition->repetition_type) {
|
||||
$today = today(config('app.timezone'))->endOfYear();
|
||||
@@ -482,11 +485,11 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
if (null === $repDate) {
|
||||
$repDate = clone $today;
|
||||
}
|
||||
$diffInYears = (int)$today->diffInYears($repDate, true);
|
||||
$diffInYears = (int) $today->diffInYears($repDate, true);
|
||||
$repDate->addYears($diffInYears); // technically not necessary.
|
||||
$string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
|
||||
$string = $repDate->isoFormat((string) trans('config.month_and_day_no_year_js'));
|
||||
|
||||
return (string)trans('firefly.recurring_yearly', ['date' => $string], $language);
|
||||
return (string) trans('firefly.recurring_yearly', ['date' => $string], $language);
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -520,16 +523,16 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
public function totalTransactions(Recurrence $recurrence, RecurrenceRepetition $repetition): int
|
||||
{
|
||||
// if repeat = null just return 0.
|
||||
if (null === $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) {
|
||||
if (null === $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) {
|
||||
return 0;
|
||||
}
|
||||
// expect X transactions then stop. Return that number
|
||||
if (null === $recurrence->repeat_until && 0 !== (int)$recurrence->repetitions) {
|
||||
return (int)$recurrence->repetitions;
|
||||
if (null === $recurrence->repeat_until && 0 !== (int) $recurrence->repetitions) {
|
||||
return (int) $recurrence->repetitions;
|
||||
}
|
||||
|
||||
// need to calculate, this depends on the repetition:
|
||||
if (null !== $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) {
|
||||
if (null !== $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) {
|
||||
$occurrences = $this->getOccurrencesInRange($repetition, $recurrence->first_date ?? today(), $recurrence->repeat_until);
|
||||
|
||||
return count($occurrences);
|
||||
|
||||
@@ -298,34 +298,38 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function searchAccount(array $query, array $types, int $limit): Collection
|
||||
public function searchAccount(string $query, array $types, int $page, int $limit): Collection
|
||||
{
|
||||
// search by group, not by user
|
||||
$dbQuery = $this->userGroup->accounts()
|
||||
->where('active', true)
|
||||
->orderBy('accounts.updated_at', 'ASC')
|
||||
->orderBy('accounts.order', 'ASC')
|
||||
->orderBy('accounts.account_type_id', 'ASC')
|
||||
->orderBy('accounts.name', 'ASC')
|
||||
->with(['accountType'])
|
||||
;
|
||||
if (count($query) > 0) {
|
||||
// split query on spaces just in case:
|
||||
|
||||
// split query on spaces just in case:
|
||||
if ('' !== trim($query)) {
|
||||
$dbQuery->where(function (EloquentBuilder $q) use ($query): void {
|
||||
foreach ($query as $line) {
|
||||
$parts = explode(' ', $line);
|
||||
foreach ($parts as $part) {
|
||||
$search = sprintf('%%%s%%', $part);
|
||||
$q->orWhereLike('name', $search);
|
||||
}
|
||||
$parts = explode(' ', $query);
|
||||
foreach ($parts as $part) {
|
||||
$search = sprintf('%%%s%%', $part);
|
||||
$q->orWhereLike('name', $search);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (0 !== count($types)) {
|
||||
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
|
||||
$dbQuery->whereIn('account_types.type', $types);
|
||||
}
|
||||
|
||||
return $dbQuery->take($limit)->get(['accounts.*']);
|
||||
$dbQuery->skip(($page - 1) * $limit)->take($limit);
|
||||
|
||||
return $dbQuery->get(['accounts.*']);
|
||||
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
||||
@@ -80,7 +80,7 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function resetAccountOrder(): void;
|
||||
|
||||
public function searchAccount(array $query, array $types, int $limit): Collection;
|
||||
public function searchAccount(string $query, array $types, int $page, int $limit): Collection;
|
||||
|
||||
public function setUser(User $user): void;
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ class CreditRecalculateService
|
||||
app('log')->debug(sprintf('Now processing account #%d ("%s"). All amounts with 2 decimals!', $account->id, $account->name));
|
||||
// get opening balance (if present)
|
||||
$this->repository->setUser($account->user);
|
||||
$direction = (string)$this->repository->getMetaValue($account, 'liability_direction');
|
||||
$direction = (string) $this->repository->getMetaValue($account, 'liability_direction');
|
||||
$openingBalance = $this->repository->getOpeningBalance($account);
|
||||
if (null !== $openingBalance) {
|
||||
app('log')->debug(sprintf('Found opening balance transaction journal #%d', $openingBalance->id));
|
||||
@@ -242,6 +242,15 @@ class CreditRecalculateService
|
||||
private function processTransaction(Account $account, string $direction, Transaction $transaction, string $leftOfDebt): string
|
||||
{
|
||||
$journal = $transaction->transactionJournal;
|
||||
|
||||
// here be null pointers.
|
||||
if (null === $journal) {
|
||||
app('log')->warning(sprintf('Transaction #%d has no journal.', $transaction->id));
|
||||
|
||||
return $leftOfDebt;
|
||||
}
|
||||
|
||||
|
||||
$foreignCurrency = $transaction->foreignCurrency;
|
||||
$accountCurrency = $this->repository->getAccountCurrency($account);
|
||||
$type = $journal->transactionType->type;
|
||||
|
||||
@@ -40,6 +40,7 @@ use FireflyIII\Models\RecurrenceTransaction;
|
||||
use FireflyIII\Models\RecurrenceTransactionMeta;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Validation\AccountValidator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Trait RecurringTransactionTrait
|
||||
@@ -212,10 +213,14 @@ trait RecurringTransactionTrait
|
||||
|
||||
private function setBudget(RecurrenceTransaction $transaction, int $budgetId): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$budgetFactory = app(BudgetFactory::class);
|
||||
$budgetFactory->setUser($transaction->recurrence->user);
|
||||
$budget = $budgetFactory->find($budgetId, null);
|
||||
if (null === $budget) {
|
||||
// remove budget from recurring transaction:
|
||||
$transaction->recurrenceTransactionMeta()->where('name', 'budget_id')->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,6 +240,9 @@ trait RecurringTransactionTrait
|
||||
$billFactory->setUser($transaction->recurrence->user);
|
||||
$bill = $billFactory->find($billId, null);
|
||||
if (null === $bill) {
|
||||
// remove bill from recurring transaction:
|
||||
$transaction->recurrenceTransactionMeta()->where('name', 'bill_id')->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,7 @@ class RecurringCronjob extends AbstractCronjob
|
||||
{
|
||||
app('log')->info(sprintf('Will now fire recurring cron job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
|
||||
/** @var CreateRecurringTransactions $job */
|
||||
$job = app(CreateRecurringTransactions::class);
|
||||
$job->setDate($this->date);
|
||||
$job = new CreateRecurringTransactions($this->date);
|
||||
$job->setForce($this->force);
|
||||
$job->handle();
|
||||
|
||||
|
||||
@@ -36,10 +36,12 @@ trait CollectsAccountsFromFilter
|
||||
$collection = new Collection();
|
||||
|
||||
// always collect from the query parameter, even when it's empty.
|
||||
foreach ($queryParameters['accounts'] as $accountId) {
|
||||
$account = $this->repository->find((int) $accountId);
|
||||
if (null !== $account) {
|
||||
$collection->push($account);
|
||||
if (null !== $queryParameters['accounts']) {
|
||||
foreach ($queryParameters['accounts'] as $accountId) {
|
||||
$account = $this->repository->find((int) $accountId);
|
||||
if (null !== $account) {
|
||||
$collection->push($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Support\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountBalance;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -47,20 +47,15 @@ class AccountBalanceCalculator
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate all balances.
|
||||
* Recalculate all account and transaction balances.
|
||||
*/
|
||||
public static function forceRecalculateAll(): void
|
||||
{
|
||||
Transaction::whereNull('deleted_at')->update(['balance_dirty' => true]);
|
||||
$object = new self();
|
||||
$object->optimizedCalculation(new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate all balances.
|
||||
*/
|
||||
public static function recalculateAll(): void
|
||||
public static function recalculateAll(bool $forced): void
|
||||
{
|
||||
if ($forced) {
|
||||
Transaction::whereNull('deleted_at')->update(['balance_dirty' => true]);
|
||||
// also delete account balances.
|
||||
AccountBalance::whereNotNull('created_at')->delete();
|
||||
}
|
||||
$object = new self();
|
||||
$object->optimizedCalculation(new Collection());
|
||||
}
|
||||
@@ -130,12 +125,6 @@ class AccountBalanceCalculator
|
||||
private function optimizedCalculation(Collection $accounts, ?Carbon $notBefore = null): void
|
||||
{
|
||||
Log::debug('start of optimizedCalculation');
|
||||
if (false === config('firefly.feature_flags.running_balance_column')) {
|
||||
Log::debug('optimizedCalculation is disabled, return.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($accounts->count() > 0) {
|
||||
Log::debug(sprintf('Limited to %d account(s)', $accounts->count()));
|
||||
}
|
||||
@@ -162,14 +151,17 @@ class AccountBalanceCalculator
|
||||
|
||||
$set = $query->get(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount']);
|
||||
|
||||
// the balance value is an array.
|
||||
// first entry is the balance, second is the date.
|
||||
|
||||
/** @var Transaction $entry */
|
||||
foreach ($set as $entry) {
|
||||
// start with empty array:
|
||||
$balances[$entry->account_id] ??= [];
|
||||
$balances[$entry->account_id][$entry->transaction_currency_id] ??= $this->getLatestBalance($entry->account_id, $entry->transaction_currency_id, $notBefore);
|
||||
$balances[$entry->account_id][$entry->transaction_currency_id] ??= [$this->getLatestBalance($entry->account_id, $entry->transaction_currency_id, $notBefore), null];
|
||||
|
||||
// before and after are easy:
|
||||
$before = $balances[$entry->account_id][$entry->transaction_currency_id];
|
||||
$before = $balances[$entry->account_id][$entry->transaction_currency_id][0];
|
||||
$after = bcadd($before, $entry->amount);
|
||||
if (true === $entry->balance_dirty || $accounts->count() > 0) {
|
||||
// update the transaction:
|
||||
@@ -181,12 +173,13 @@ class AccountBalanceCalculator
|
||||
}
|
||||
|
||||
// then update the array:
|
||||
$balances[$entry->account_id][$entry->transaction_currency_id] = $after;
|
||||
$balances[$entry->account_id][$entry->transaction_currency_id] = [$after, $entry->date];
|
||||
}
|
||||
Log::debug(sprintf('end of optimizedCalculation, corrected %d balance(s)', $count));
|
||||
// then update all transactions.
|
||||
|
||||
// ?? something with accounts?
|
||||
// save all collected balances in their respective account objects.
|
||||
$this->storeAccountBalances($balances);
|
||||
}
|
||||
|
||||
private function getAccountBalanceByJournal(string $title, int $account, int $journal, int $currency): AccountBalance
|
||||
@@ -208,145 +201,182 @@ class AccountBalanceCalculator
|
||||
return $entry;
|
||||
}
|
||||
|
||||
private function recalculateLatest(?Account $account): void
|
||||
// private function recalculateLatest(?Account $account): void
|
||||
// {
|
||||
// $query = Transaction::groupBy(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
|
||||
//
|
||||
// if (null !== $account) {
|
||||
// $query->where('transactions.account_id', $account->id);
|
||||
// }
|
||||
// $result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
|
||||
//
|
||||
// // reset account balances:
|
||||
// $this->resetAccountBalancesByAccount('balance', $account);
|
||||
//
|
||||
// /** @var \stdClass $row */
|
||||
// foreach ($result as $row) {
|
||||
// $account = (int) $row->account_id;
|
||||
// $transactionCurrency = (int) $row->transaction_currency_id;
|
||||
// $foreignCurrency = (int) $row->foreign_currency_id;
|
||||
// $sumAmount = (string) $row->sum_amount;
|
||||
// $sumForeignAmount = (string) $row->sum_foreign_amount;
|
||||
// $sumAmount = '' === $sumAmount ? '0' : $sumAmount;
|
||||
// $sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
|
||||
//
|
||||
// // at this point SQLite may return scientific notation because why not. Terrible.
|
||||
// $sumAmount = app('steam')->floatalize($sumAmount);
|
||||
// $sumForeignAmount = app('steam')->floatalize($sumForeignAmount);
|
||||
//
|
||||
// // first create for normal currency:
|
||||
// $entry = $this->getAccountBalanceByAccount($account, $transactionCurrency);
|
||||
//
|
||||
// try {
|
||||
// $entry->balance = bcadd((string) $entry->balance, $sumAmount);
|
||||
// } catch (\ValueError $e) {
|
||||
// $message = sprintf('[a] Could not add "%s" to "%s": %s', $entry->balance, $sumAmount, $e->getMessage());
|
||||
// Log::error($message);
|
||||
//
|
||||
// throw new FireflyException($message, 0, $e);
|
||||
// }
|
||||
// $entry->save();
|
||||
//
|
||||
// // then do foreign amount, if present:
|
||||
// if ($foreignCurrency > 0) {
|
||||
// $entry = $this->getAccountBalanceByAccount($account, $foreignCurrency);
|
||||
//
|
||||
// try {
|
||||
// $entry->balance = bcadd((string) $entry->balance, $sumForeignAmount);
|
||||
// } catch (\ValueError $e) {
|
||||
// $message = sprintf('[b] Could not add "%s" to "%s": %s', $entry->balance, $sumForeignAmount, $e->getMessage());
|
||||
// Log::error($message);
|
||||
//
|
||||
// throw new FireflyException($message, 0, $e);
|
||||
// }
|
||||
// $entry->save();
|
||||
// }
|
||||
// }
|
||||
// Log::debug(sprintf('Recalculated %d account balance(s)', $result->count()));
|
||||
// }
|
||||
|
||||
// private function resetAccountBalancesByAccount(string $title, ?Account $account): void
|
||||
// {
|
||||
// if (null === $account) {
|
||||
// $count = AccountBalance::whereNotNull('updated_at')->where('title', $title)->update(['balance' => '0']);
|
||||
// Log::debug(sprintf('Set %d account balance(s) to zero.', $count));
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
// $count = AccountBalance::where('account_id', $account->id)->where('title', $title)->update(['balance' => '0']);
|
||||
// Log::debug(sprintf('Set %d account balance(s) of account #%d to zero.', $count, $account->id));
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Als je alles opnieuw doet, verzamel je alle transactions en het bedrag en zet je dat neer als "balance after
|
||||
// * journal". Dat betekent, netjes op volgorde van datum en doorrekenen.
|
||||
// *
|
||||
// * Zodra je een transaction journal verplaatst (datum) moet je dat journal en alle latere journals opnieuw doen.
|
||||
// * Maar dan moet je van de account wel een beginnetje hebben, namelijk de balance tot en met dat moment.
|
||||
// *
|
||||
// * 1. Dus dan search je eerst naar die SUM, som alle transactions eerder dan (niet inclusief) de journal.
|
||||
// * 2. En vanaf daar pak je alle journals op of na de journal (dus ook de journal zelf) en begin je door te tellen.
|
||||
// * 3. Elke voorbij gaande journal entry "balance_after_journal" geef je een update of voeg je toe.
|
||||
// */
|
||||
// private function recalculateJournals(?Account $account, ?TransactionJournal $transactionJournal): void
|
||||
// {
|
||||
// $query = Transaction::groupBy(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
|
||||
// $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
|
||||
// $query->orderBy('transaction_journals.date', 'asc');
|
||||
// $amounts = [];
|
||||
// if (null !== $account) {
|
||||
// $query->where('transactions.account_id', $account->id);
|
||||
// }
|
||||
// if (null !== $account && null !== $transactionJournal) {
|
||||
// $query->where('transaction_journals.date', '>=', $transactionJournal->date);
|
||||
// $amounts = $this->getStartAmounts($account, $transactionJournal);
|
||||
// }
|
||||
// $result = $query->get(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
|
||||
//
|
||||
// /** @var \stdClass $row */
|
||||
// foreach ($result as $row) {
|
||||
// $account = (int) $row->account_id;
|
||||
// $transactionCurrency = (int) $row->transaction_currency_id;
|
||||
// $foreignCurrency = (int) $row->foreign_currency_id;
|
||||
// $sumAmount = (string) $row->sum_amount;
|
||||
// $sumForeignAmount = (string) $row->sum_foreign_amount;
|
||||
// $journalId = (int) $row->id;
|
||||
//
|
||||
// // check for empty strings
|
||||
// $sumAmount = '' === $sumAmount ? '0' : $sumAmount;
|
||||
// $sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
|
||||
//
|
||||
// // new amounts:
|
||||
// $amounts[$account][$transactionCurrency] = bcadd($amounts[$account][$transactionCurrency] ?? '0', $sumAmount);
|
||||
// $amounts[$account][$foreignCurrency] = bcadd($amounts[$account][$foreignCurrency] ?? '0', $sumForeignAmount);
|
||||
//
|
||||
// // first create for normal currency:
|
||||
// $entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $transactionCurrency);
|
||||
// $entry->balance = $amounts[$account][$transactionCurrency];
|
||||
// $entry->save();
|
||||
//
|
||||
// // then do foreign amount, if present:
|
||||
// if ($foreignCurrency > 0) {
|
||||
// $entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $foreignCurrency);
|
||||
// $entry->balance = $amounts[$account][$foreignCurrency];
|
||||
// $entry->save();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // select transactions.account_id, transaction_journals.id, transactions.transaction_currency_id, transactions.foreign_currency_id, sum(transactions.amount), sum(transactions.foreign_amount)
|
||||
// //
|
||||
// // from transactions
|
||||
// //
|
||||
// // left join transaction_journals ON transaction_journals.id = transactions.transaction_journal_id
|
||||
// //
|
||||
// // group by account_id, transaction_journals.id, transaction_currency_id, foreign_currency_id
|
||||
// // order by transaction_journals.date desc
|
||||
// }
|
||||
|
||||
// private function getStartAmounts(Account $account, TransactionJournal $journal): array
|
||||
// {
|
||||
// exit('here we are 1');
|
||||
//
|
||||
// return [];
|
||||
// }
|
||||
|
||||
private function storeAccountBalances(array $balances): void
|
||||
{
|
||||
$query = Transaction::groupBy(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
|
||||
/**
|
||||
* @var int $accountId
|
||||
* @var array $currencies
|
||||
*/
|
||||
foreach ($balances as $accountId => $currencies) {
|
||||
/** @var Account $account */
|
||||
$account = Account::find($accountId);
|
||||
if (null === $account) {
|
||||
Log::error(sprintf('Could not find account #%d, will not save account balance.', $accountId));
|
||||
|
||||
if (null !== $account) {
|
||||
$query->where('transactions.account_id', $account->id);
|
||||
}
|
||||
$result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
|
||||
|
||||
// reset account balances:
|
||||
$this->resetAccountBalancesByAccount('balance', $account);
|
||||
|
||||
/** @var \stdClass $row */
|
||||
foreach ($result as $row) {
|
||||
$account = (int) $row->account_id;
|
||||
$transactionCurrency = (int) $row->transaction_currency_id;
|
||||
$foreignCurrency = (int) $row->foreign_currency_id;
|
||||
$sumAmount = (string) $row->sum_amount;
|
||||
$sumForeignAmount = (string) $row->sum_foreign_amount;
|
||||
$sumAmount = '' === $sumAmount ? '0' : $sumAmount;
|
||||
$sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
|
||||
|
||||
// at this point SQLite may return scientific notation because why not. Terrible.
|
||||
$sumAmount = app('steam')->floatalize($sumAmount);
|
||||
$sumForeignAmount = app('steam')->floatalize($sumForeignAmount);
|
||||
|
||||
// first create for normal currency:
|
||||
$entry = $this->getAccountBalanceByAccount($account, $transactionCurrency);
|
||||
|
||||
try {
|
||||
$entry->balance = bcadd((string) $entry->balance, $sumAmount);
|
||||
} catch (\ValueError $e) {
|
||||
$message = sprintf('[a] Could not add "%s" to "%s": %s', $entry->balance, $sumAmount, $e->getMessage());
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message, 0, $e);
|
||||
continue;
|
||||
}
|
||||
$entry->save();
|
||||
|
||||
// then do foreign amount, if present:
|
||||
if ($foreignCurrency > 0) {
|
||||
$entry = $this->getAccountBalanceByAccount($account, $foreignCurrency);
|
||||
/**
|
||||
* @var int $currencyId
|
||||
* @var array $balance
|
||||
*/
|
||||
foreach ($currencies as $currencyId => $balance) {
|
||||
/** @var TransactionCurrency $currency */
|
||||
$currency = TransactionCurrency::find($currencyId);
|
||||
if (null === $currency) {
|
||||
Log::error(sprintf('Could not find currency #%d, will not save account balance.', $currencyId));
|
||||
|
||||
try {
|
||||
$entry->balance = bcadd((string) $entry->balance, $sumForeignAmount);
|
||||
} catch (\ValueError $e) {
|
||||
$message = sprintf('[b] Could not add "%s" to "%s": %s', $entry->balance, $sumForeignAmount, $e->getMessage());
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message, 0, $e);
|
||||
continue;
|
||||
}
|
||||
$entry->save();
|
||||
|
||||
/** @var AccountBalance $object */
|
||||
$object = $account->accountBalances()->firstOrCreate(['title' => 'running_balance', 'balance' => '0', 'transaction_currency_id' => $currencyId, 'date' => $balance[1]]);
|
||||
$object->balance = $balance[0];
|
||||
$object->date = $balance[1];
|
||||
$object->save();
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d account balance(s)', $result->count()));
|
||||
}
|
||||
|
||||
private function resetAccountBalancesByAccount(string $title, ?Account $account): void
|
||||
{
|
||||
if (null === $account) {
|
||||
$count = AccountBalance::whereNotNull('updated_at')->where('title', $title)->update(['balance' => '0']);
|
||||
Log::debug(sprintf('Set %d account balance(s) to zero.', $count));
|
||||
|
||||
return;
|
||||
}
|
||||
$count = AccountBalance::where('account_id', $account->id)->where('title', $title)->update(['balance' => '0']);
|
||||
Log::debug(sprintf('Set %d account balance(s) of account #%d to zero.', $count, $account->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Als je alles opnieuw doet, verzamel je alle transactions en het bedrag en zet je dat neer als "balance after
|
||||
* journal". Dat betekent, netjes op volgorde van datum en doorrekenen.
|
||||
*
|
||||
* Zodra je een transaction journal verplaatst (datum) moet je dat journal en alle latere journals opnieuw doen.
|
||||
* Maar dan moet je van de account wel een beginnetje hebben, namelijk de balance tot en met dat moment.
|
||||
*
|
||||
* 1. Dus dan search je eerst naar die SUM, som alle transactions eerder dan (niet inclusief) de journal.
|
||||
* 2. En vanaf daar pak je alle journals op of na de journal (dus ook de journal zelf) en begin je door te tellen.
|
||||
* 3. Elke voorbij gaande journal entry "balance_after_journal" geef je een update of voeg je toe.
|
||||
*/
|
||||
private function recalculateJournals(?Account $account, ?TransactionJournal $transactionJournal): void
|
||||
{
|
||||
$query = Transaction::groupBy(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
|
||||
$query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
|
||||
$query->orderBy('transaction_journals.date', 'asc');
|
||||
$amounts = [];
|
||||
if (null !== $account) {
|
||||
$query->where('transactions.account_id', $account->id);
|
||||
}
|
||||
if (null !== $account && null !== $transactionJournal) {
|
||||
$query->where('transaction_journals.date', '>=', $transactionJournal->date);
|
||||
$amounts = $this->getStartAmounts($account, $transactionJournal);
|
||||
}
|
||||
$result = $query->get(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
|
||||
|
||||
/** @var \stdClass $row */
|
||||
foreach ($result as $row) {
|
||||
$account = (int) $row->account_id;
|
||||
$transactionCurrency = (int) $row->transaction_currency_id;
|
||||
$foreignCurrency = (int) $row->foreign_currency_id;
|
||||
$sumAmount = (string) $row->sum_amount;
|
||||
$sumForeignAmount = (string) $row->sum_foreign_amount;
|
||||
$journalId = (int) $row->id;
|
||||
|
||||
// check for empty strings
|
||||
$sumAmount = '' === $sumAmount ? '0' : $sumAmount;
|
||||
$sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
|
||||
|
||||
// new amounts:
|
||||
$amounts[$account][$transactionCurrency] = bcadd($amounts[$account][$transactionCurrency] ?? '0', $sumAmount);
|
||||
$amounts[$account][$foreignCurrency] = bcadd($amounts[$account][$foreignCurrency] ?? '0', $sumForeignAmount);
|
||||
|
||||
// first create for normal currency:
|
||||
$entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $transactionCurrency);
|
||||
$entry->balance = $amounts[$account][$transactionCurrency];
|
||||
$entry->save();
|
||||
|
||||
// then do foreign amount, if present:
|
||||
if ($foreignCurrency > 0) {
|
||||
$entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $foreignCurrency);
|
||||
$entry->balance = $amounts[$account][$foreignCurrency];
|
||||
$entry->save();
|
||||
}
|
||||
}
|
||||
|
||||
// select transactions.account_id, transaction_journals.id, transactions.transaction_currency_id, transactions.foreign_currency_id, sum(transactions.amount), sum(transactions.foreign_amount)
|
||||
//
|
||||
// from transactions
|
||||
//
|
||||
// left join transaction_journals ON transaction_journals.id = transactions.transaction_journal_id
|
||||
//
|
||||
// group by account_id, transaction_journals.id, transaction_currency_id, foreign_currency_id
|
||||
// order by transaction_journals.date desc
|
||||
}
|
||||
|
||||
private function getStartAmounts(Account $account, TransactionJournal $journal): array
|
||||
{
|
||||
exit('here we are 1');
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,11 +105,11 @@ trait ConvertsDataTypes
|
||||
/**
|
||||
* Return string value.
|
||||
*/
|
||||
public function convertString(string $field): string
|
||||
public function convertString(string $field, string $default = ''): string
|
||||
{
|
||||
$entry = $this->get($field);
|
||||
if (!is_scalar($entry)) {
|
||||
return '';
|
||||
return $default;
|
||||
}
|
||||
|
||||
return (string)$this->clearString((string)$entry);
|
||||
|
||||
@@ -81,7 +81,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
|
||||
if ($source->account_id === $piggyBank->account_id) {
|
||||
app('log')->debug('Piggy bank account is linked to source, so remove amount from piggy bank.');
|
||||
$this->removeAmount($piggyBank, $journalObj, $destination->amount);
|
||||
$this->removeAmount($piggyBank, $journal, $journalObj, $destination->amount);
|
||||
|
||||
event(
|
||||
new TriggeredAuditLog(
|
||||
@@ -102,7 +102,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
}
|
||||
if ($destination->account_id === $piggyBank->account_id) {
|
||||
app('log')->debug('Piggy bank account is linked to source, so add amount to piggy bank.');
|
||||
$this->addAmount($piggyBank, $journalObj, $destination->amount);
|
||||
$this->addAmount($piggyBank, $journal, $journalObj, $destination->amount);
|
||||
|
||||
event(
|
||||
new TriggeredAuditLog(
|
||||
@@ -111,10 +111,11 @@ class UpdatePiggybank implements ActionInterface
|
||||
'add_to_piggy',
|
||||
null,
|
||||
[
|
||||
'currency_symbol' => $journalObj->transactionCurrency->symbol,
|
||||
'decimal_places' => $journalObj->transactionCurrency->decimal_places,
|
||||
'amount' => $destination->amount,
|
||||
'piggy' => $piggyBank->name,
|
||||
'currency_symbol' => $journalObj->transactionCurrency->symbol,
|
||||
'decimal_places' => $journalObj->transactionCurrency->decimal_places,
|
||||
'amount' => $destination->amount,
|
||||
'piggy' => $piggyBank->name,
|
||||
'piggy_id' => $piggyBank->id,
|
||||
]
|
||||
)
|
||||
);
|
||||
@@ -138,7 +139,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
return $user->piggyBanks()->where('piggy_banks.name', $name)->first();
|
||||
}
|
||||
|
||||
private function removeAmount(PiggyBank $piggyBank, TransactionJournal $journal, string $amount): void
|
||||
private function removeAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, string $amount): void
|
||||
{
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$repository->setUser($journal->user);
|
||||
@@ -154,6 +155,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
// if amount is zero, stop.
|
||||
if (0 === bccomp('0', $amount)) {
|
||||
app('log')->warning('Amount left is zero, stop.');
|
||||
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_zero_piggy', ['name' => $piggyBank->name])));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -161,6 +163,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
// make sure we can remove amount:
|
||||
if (false === $repository->canRemoveAmount($piggyBank, $amount)) {
|
||||
app('log')->warning(sprintf('Cannot remove %s from piggy bank.', $amount));
|
||||
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_from_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -169,7 +172,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
$repository->removeAmount($piggyBank, $amount, $journal);
|
||||
}
|
||||
|
||||
private function addAmount(PiggyBank $piggyBank, TransactionJournal $journal, string $amount): void
|
||||
private function addAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, string $amount): void
|
||||
{
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$repository->setUser($journal->user);
|
||||
@@ -190,6 +193,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
// if amount is zero, stop.
|
||||
if (0 === bccomp('0', $amount)) {
|
||||
app('log')->warning('Amount left is zero, stop.');
|
||||
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_zero_piggy', ['name' => $piggyBank->name])));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -197,6 +201,7 @@ class UpdatePiggybank implements ActionInterface
|
||||
// make sure we can add amount:
|
||||
if (false === $repository->canAddAmount($piggyBank, $amount)) {
|
||||
app('log')->warning(sprintf('Cannot add %s to piggy bank.', $amount));
|
||||
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_to_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,11 +194,19 @@ class BillTransformer extends AbstractTransformer
|
||||
$searchStart = clone $start;
|
||||
$start->subDay();
|
||||
|
||||
app('log')->debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $this->parameters->get('end')->format('Y-m-d')));
|
||||
/** @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, $this->parameters->get('end'));
|
||||
$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.
|
||||
|
||||
@@ -847,7 +847,8 @@ class FireflyValidator extends Validator
|
||||
->where('trigger', $trigger)
|
||||
->where('response', $response)
|
||||
->where('delivery', $delivery)
|
||||
->where('url', $url)->count();
|
||||
->where('url', $url)->count()
|
||||
;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
596
composer.lock
generated
596
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -110,7 +110,7 @@ return [
|
||||
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2024-10-14',
|
||||
'version' => 'develop/2024-11-04',
|
||||
'api_version' => '2.1.0',
|
||||
'db_version' => 24,
|
||||
|
||||
|
||||
1545
package-lock.json
generated
1545
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -131,7 +131,7 @@ function initRevenueACField(fieldName) {
|
||||
}
|
||||
});
|
||||
sourceNames.initialize();
|
||||
$('input[name="' + fieldName + '"]').typeahead({hint: true, highlight: true,}, {source: sourceNames, displayKey: 'name', autoSelect: false});
|
||||
$('input[name="' + fieldName + '"]').typeahead({hint: true, highlight: true,}, {source: sourceNames, displayKey: 'name', autoselect: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,5 +161,5 @@ function initCategoryAC() {
|
||||
}
|
||||
});
|
||||
categories.initialize();
|
||||
$('input[name="category"]').typeahead({hint: true, highlight: true,}, {source: categories, displayKey: 'name', autoSelect: false});
|
||||
$('input[name="category"]').typeahead({hint: true, highlight: true,}, {source: categories, displayKey: 'name', autoselect: true});
|
||||
}
|
||||
|
||||
6
public/v1/js/lib/typeahead/bloodhound.js
Normal file → Executable file
6
public/v1/js/lib/typeahead/bloodhound.js
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
/*!
|
||||
* typeahead.js 1.3.1
|
||||
* typeahead.js 1.3.3
|
||||
* https://github.com/corejavascript/typeahead.js
|
||||
* Copyright 2013-2020 Twitter, Inc. and other contributors; Licensed MIT
|
||||
* Copyright 2013-2024 Twitter, Inc. and other contributors; Licensed MIT
|
||||
*/
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
noop: function() {}
|
||||
};
|
||||
}();
|
||||
var VERSION = "1.3.1";
|
||||
var VERSION = "1.3.3";
|
||||
var tokenizers = function() {
|
||||
"use strict";
|
||||
return {
|
||||
|
||||
6
public/v1/js/lib/typeahead/bloodhound.min.js
vendored
Normal file → Executable file
6
public/v1/js/lib/typeahead/bloodhound.min.js
vendored
Normal file → Executable file
File diff suppressed because one or more lines are too long
7
public/v1/js/lib/typeahead/typeahead.bundle.js
Normal file → Executable file
7
public/v1/js/lib/typeahead/typeahead.bundle.js
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
/*!
|
||||
* typeahead.js 1.3.1
|
||||
* typeahead.js 1.3.3
|
||||
* https://github.com/corejavascript/typeahead.js
|
||||
* Copyright 2013-2020 Twitter, Inc. and other contributors; Licensed MIT
|
||||
* Copyright 2013-2024 Twitter, Inc. and other contributors; Licensed MIT
|
||||
*/
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
noop: function() {}
|
||||
};
|
||||
}();
|
||||
var VERSION = "1.3.1";
|
||||
var VERSION = "1.3.3";
|
||||
var tokenizers = function() {
|
||||
"use strict";
|
||||
return {
|
||||
@@ -1446,6 +1446,7 @@
|
||||
});
|
||||
this.$input.attr({
|
||||
"aria-owns": id + "_listbox",
|
||||
"aria-controls": id + "_listbox",
|
||||
role: "combobox",
|
||||
"aria-autocomplete": "list",
|
||||
"aria-expanded": false
|
||||
|
||||
6
public/v1/js/lib/typeahead/typeahead.bundle.min.js
vendored
Normal file → Executable file
6
public/v1/js/lib/typeahead/typeahead.bundle.min.js
vendored
Normal file → Executable file
File diff suppressed because one or more lines are too long
5
public/v1/js/lib/typeahead/typeahead.jquery.js
Normal file → Executable file
5
public/v1/js/lib/typeahead/typeahead.jquery.js
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
/*!
|
||||
* typeahead.js 1.3.1
|
||||
* typeahead.js 1.3.3
|
||||
* https://github.com/corejavascript/typeahead.js
|
||||
* Copyright 2013-2020 Twitter, Inc. and other contributors; Licensed MIT
|
||||
* Copyright 2013-2024 Twitter, Inc. and other contributors; Licensed MIT
|
||||
*/
|
||||
|
||||
|
||||
@@ -499,6 +499,7 @@
|
||||
});
|
||||
this.$input.attr({
|
||||
"aria-owns": id + "_listbox",
|
||||
"aria-controls": id + "_listbox",
|
||||
role: "combobox",
|
||||
"aria-autocomplete": "list",
|
||||
"aria-expanded": false
|
||||
|
||||
6
public/v1/js/lib/typeahead/typeahead.jquery.min.js
vendored
Normal file → Executable file
6
public/v1/js/lib/typeahead/typeahead.jquery.min.js
vendored
Normal file → Executable file
File diff suppressed because one or more lines are too long
@@ -29,7 +29,7 @@
|
||||
"bootstrap5-tags": "^1.7",
|
||||
"chart.js": "^4.4.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-chart-sankey": "^0.12.1",
|
||||
"chartjs-chart-sankey": "^0.13.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"i18next": "^23.15.2",
|
||||
"i18next-chained-backend": "^4.6.2",
|
||||
|
||||
@@ -755,7 +755,7 @@ return [
|
||||
'instructions_rule_from_journal' => 'Create a rule based on one of your transactions. Complement or submit the form below.',
|
||||
'rule_is_strict' => 'strict rule',
|
||||
'rule_is_not_strict' => 'non-strict rule',
|
||||
'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.',
|
||||
'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed if this particular rule is executed.',
|
||||
'rule_help_strict' => 'In strict rules ALL triggers must fire for the action(s) to be executed. In non-strict rules, ANY trigger is enough for the action(s) to be executed.',
|
||||
'rule_help_active' => 'Inactive rules will never fire.',
|
||||
'stored_new_rule' => 'Stored new rule with title ":title"',
|
||||
@@ -2627,6 +2627,7 @@ return [
|
||||
'no_bills_create_default' => 'Create a bill',
|
||||
|
||||
// recurring transactions
|
||||
'recurrence_max_count' => 'This recurring transactions will be created at most :max time(s), and has been created :count time(s) already.',
|
||||
'create_right_now' => 'Create right now',
|
||||
'no_new_transaction_in_recurrence' => 'No new transaction was created. Perhaps it was already fired for this date?',
|
||||
'recurrences' => 'Recurring transactions',
|
||||
|
||||
@@ -71,4 +71,8 @@ return [
|
||||
'cannot_find_category' => 'Firefly III can\'t find category ":name"',
|
||||
'cannot_set_budget' => 'Firefly III can\'t set budget ":name" to a transaction of type ":type"',
|
||||
'journal_invalid_amount' => 'Firefly III can\'t set amount ":amount" because it is not a valid number.',
|
||||
'cannot_remove_zero_piggy' => 'Cannot remove zero amount from piggy bank ":name"',
|
||||
'cannot_remove_from_piggy' => 'Cannot remove ":amount" from piggy bank ":name"',
|
||||
'cannot_add_zero_piggy' => 'Cannot add zero amount to piggy bank ":name"',
|
||||
'cannot_add_to_piggy' => 'Cannot add ":amount" to piggy bank ":name"',
|
||||
];
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// date ranges
|
||||
var ranges = {};
|
||||
{% for title, range in dateRangeConfig.ranges %}
|
||||
ranges["{{ title }}"] = [moment("{{ range[0].format('Y-m-d') }}"), moment("{{ range[1].format('Y-m-d') }}")];
|
||||
ranges["{{ title|escape('js') }}"] = [moment("{{ range[0].format('Y-m-d') }}"), moment("{{ range[1].format('Y-m-d') }}")];
|
||||
{% endfor %}
|
||||
|
||||
// date range meta configuration
|
||||
var dateRangeMeta = {
|
||||
title: "{{ dateRangeTitle }}",
|
||||
title: "{{ dateRangeTitle|escape('js') }}",
|
||||
url: "{{ route('daterange') }}",
|
||||
labels: {
|
||||
apply: "{{ 'apply'|_ }}",
|
||||
|
||||
@@ -93,10 +93,10 @@
|
||||
<code>{{ logEntry.after }}</code>
|
||||
{% endif %}
|
||||
{% if 'add_to_piggy' == logEntry.action %}
|
||||
{{ trans('firefly.ale_action_log_add', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.name})|raw }}
|
||||
{{ trans('firefly.ale_action_log_add', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.piggy})|raw }}
|
||||
{% endif %}
|
||||
{% if 'remove_from_piggy' == logEntry.action %}
|
||||
{{ trans('firefly.ale_action_log_remove', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.name})|raw }}
|
||||
{{ trans('firefly.ale_action_log_remove', {amount: formatAmountBySymbol(logEntry.after.amount, logEntry.after.currency_symbol, logEntry.after.decimal_places, true), name: logEntry.after.piggy})|raw }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<h4>{{ 'transaction_journal_meta'|_ }}</h4>
|
||||
{% if array.nr_of_repetitions > 0 %}
|
||||
<p>
|
||||
{% if array.journal_count >= array.nr_of_repetitions %}
|
||||
<span class="text-danger">{{ trans('firefly.recurrence_max_count', {count: array.journal_count, max: array.nr_of_repetitions}) }}</span>
|
||||
{% endif %}
|
||||
{% if array.journal_count < array.nr_of_repetitions %}
|
||||
{{ trans('firefly.recurrence_max_count', {count: array.journal_count, max: array.nr_of_repetitions}) }}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>{{ 'description'|_ }}: <em>{{ array.description }}</em></p>
|
||||
|
||||
{% if array.active == false %}
|
||||
|
||||
@@ -47,9 +47,9 @@ Route::group(
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('accounts', ['uses' => 'AccountController@accounts', 'as' => 'accounts']);
|
||||
Route::get('categories', ['uses' => 'CategoryController@categories', 'as' => 'categories']);
|
||||
Route::get('tags', ['uses' => 'TagController@tags', 'as' => 'tags']);
|
||||
Route::get('transaction-descriptions', ['uses' => 'TransactionController@transactionDescriptions', 'as' => 'transaction-descriptions']);
|
||||
// Route::get('categories', ['uses' => 'CategoryController@categories', 'as' => 'categories']);
|
||||
// Route::get('tags', ['uses' => 'TagController@tags', 'as' => 'tags']);
|
||||
// Route::get('transaction-descriptions', ['uses' => 'TransactionController@transactionDescriptions', 'as' => 'transaction-descriptions']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -58,12 +58,12 @@ Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Chart',
|
||||
'prefix' => 'v2/chart',
|
||||
'as' => 'api.v1.chart.',
|
||||
'as' => 'api.v2.chart.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('account/dashboard', ['uses' => 'AccountController@dashboard', 'as' => 'account.dashboard']);
|
||||
Route::get('budget/dashboard', ['uses' => 'BudgetController@dashboard', 'as' => 'budget.dashboard']);
|
||||
Route::get('category/dashboard', ['uses' => 'CategoryController@dashboard', 'as' => 'category.dashboard']);
|
||||
// Route::get('account/dashboard', ['uses' => 'AccountController@dashboard', 'as' => 'account.dashboard']);
|
||||
// Route::get('budget/dashboard', ['uses' => 'BudgetController@dashboard', 'as' => 'budget.dashboard']);
|
||||
// Route::get('category/dashboard', ['uses' => 'CategoryController@dashboard', 'as' => 'category.dashboard']);
|
||||
Route::get('balance/balance', ['uses' => 'BalanceController@balance', 'as' => 'balance.balance']);
|
||||
}
|
||||
);
|
||||
@@ -77,7 +77,7 @@ Route::group(
|
||||
'as' => 'api.v2.summary.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('basic', ['uses' => 'BasicController@basic', 'as' => 'basic']);
|
||||
// Route::get('basic', ['uses' => 'BasicController@basic', 'as' => 'basic']);
|
||||
}
|
||||
);
|
||||
// V2 API route for all kinds of Transaction lists.
|
||||
@@ -91,11 +91,11 @@ Route::group(
|
||||
],
|
||||
static function (): void {
|
||||
// basic list
|
||||
Route::get('transactions', ['uses' => 'TransactionController@list', 'as' => 'transactions.list']);
|
||||
// Route::get('transactions', ['uses' => 'TransactionController@list', 'as' => 'transactions.list']);
|
||||
|
||||
// list by parent or related object.
|
||||
// note how the check is done on the user group, not the user itself.
|
||||
Route::get('accounts/{userGroupAccount}/transactions', ['uses' => 'AccountController@list', 'as' => 'accounts.transactions']);
|
||||
// Route::get('accounts/{userGroupAccount}/transactions', ['uses' => 'AccountController@list', 'as' => 'accounts.transactions']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -109,7 +109,7 @@ Route::group(
|
||||
'as' => 'api.v2.net-worth.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'NetWorthController@get', 'as' => 'index']);
|
||||
// Route::get('', ['uses' => 'NetWorthController@get', 'as' => 'index']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -135,10 +135,10 @@ Route::group(
|
||||
'as' => 'api.v2.subscriptions.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
Route::get('{userGroupBill}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
Route::get('sum/paid', ['uses' => 'SumController@paid', 'as' => 'sum.paid']);
|
||||
Route::get('sum/unpaid', ['uses' => 'SumController@unpaid', 'as' => 'sum.unpaid']);
|
||||
// Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// Route::get('{userGroupBill}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// Route::get('sum/paid', ['uses' => 'SumController@paid', 'as' => 'sum.paid']);
|
||||
// Route::get('sum/unpaid', ['uses' => 'SumController@unpaid', 'as' => 'sum.unpaid']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -150,7 +150,7 @@ Route::group(
|
||||
'as' => 'api.v2.piggy-banks.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -162,7 +162,7 @@ Route::group(
|
||||
'as' => 'api.v2.currencies.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -174,9 +174,9 @@ Route::group(
|
||||
'as' => 'api.v2.transactions.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::post('', ['uses' => 'StoreController@post', 'as' => 'store']);
|
||||
Route::get('{userGroupTransaction}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
Route::put('{userGroupTransaction}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
// Route::post('', ['uses' => 'StoreController@post', 'as' => 'store']);
|
||||
// Route::get('{userGroupTransaction}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// Route::put('{userGroupTransaction}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
}
|
||||
);
|
||||
// infinite (transactions) list:
|
||||
@@ -187,7 +187,7 @@ Route::group(
|
||||
'as' => 'api.v2.infinite.transactions.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'TransactionController@infiniteList', 'as' => 'list']);
|
||||
// Route::get('', ['uses' => 'TransactionController@infiniteList', 'as' => 'list']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -199,11 +199,11 @@ Route::group(
|
||||
'as' => 'api.v2.budgets',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'Budget\IndexController@index', 'as' => 'index']);
|
||||
Route::get('{budget}', ['uses' => 'Budget\ShowController@show', 'as' => 'show']);
|
||||
Route::get('{budget}/limits', ['uses' => 'BudgetLimit\IndexController@index', 'as' => 'budget-limits.index']);
|
||||
Route::get('sum/budgeted', ['uses' => 'Budget\SumController@budgeted', 'as' => 'sum.budgeted']);
|
||||
Route::get('sum/spent', ['uses' => 'Budget\SumController@spent', 'as' => 'sum.spent']);
|
||||
// Route::get('', ['uses' => 'Budget\IndexController@index', 'as' => 'index']);
|
||||
// Route::get('{budget}', ['uses' => 'Budget\ShowController@show', 'as' => 'show']);
|
||||
// Route::get('{budget}/limits', ['uses' => 'BudgetLimit\IndexController@index', 'as' => 'budget-limits.index']);
|
||||
// Route::get('sum/budgeted', ['uses' => 'Budget\SumController@budgeted', 'as' => 'sum.budgeted']);
|
||||
// Route::get('sum/spent', ['uses' => 'Budget\SumController@spent', 'as' => 'sum.spent']);
|
||||
// Route::get('{budget}/budgeted', ['uses' => 'Budget\ShowController@budgeted', 'as' => 'budget.budgeted']);
|
||||
// Route::get('{budget}/spent', ['uses' => 'Budget\ShowController@spent', 'as' => 'budget.spent']);
|
||||
}
|
||||
@@ -217,7 +217,7 @@ Route::group(
|
||||
'as' => 'api.v2.system.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('preferences/{preference}', ['uses' => 'PreferencesController@get', 'as' => 'preferences.get']);
|
||||
// Route::get('preferences/{preference}', ['uses' => 'PreferencesController@get', 'as' => 'preferences.get']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -229,32 +229,32 @@ Route::group(
|
||||
'as' => 'api.v2.user-groups.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']);
|
||||
Route::get('{userGroup}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
Route::put('{userGroup}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
Route::post('{userGroup}/use', ['uses' => 'UpdateController@useUserGroup', 'as' => 'use']);
|
||||
Route::put('{userGroup}/update-membership', ['uses' => 'UpdateController@updateMembership', 'as' => 'updateMembership']);
|
||||
Route::delete('{userGroup}', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']);
|
||||
// Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']);
|
||||
// Route::get('{userGroup}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// Route::put('{userGroup}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
// Route::post('{userGroup}/use', ['uses' => 'UpdateController@useUserGroup', 'as' => 'use']);
|
||||
// Route::put('{userGroup}/update-membership', ['uses' => 'UpdateController@updateMembership', 'as' => 'updateMembership']);
|
||||
// Route::delete('{userGroup}', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']);
|
||||
}
|
||||
);
|
||||
|
||||
// V2 JSON API ROUTES
|
||||
JsonApiRoute::server('v2')->prefix('v2')
|
||||
->resources(function (ResourceRegistrar $server): void {
|
||||
// ACCOUNTS
|
||||
$server->resource('accounts', AccountController::class)
|
||||
->relationships(function (Relationships $relations): void {
|
||||
$relations->hasOne('user')->readOnly();
|
||||
})
|
||||
;
|
||||
|
||||
// USERS
|
||||
$server->resource('users', JsonApiController::class)->readOnly()->relationships(function (Relationships $relations): void {
|
||||
$relations->hasMany('accounts')->readOnly();
|
||||
});
|
||||
})
|
||||
;
|
||||
// JsonApiRoute::server('v2')->prefix('v2')
|
||||
// ->resources(function (ResourceRegistrar $server): void {
|
||||
// // ACCOUNTS
|
||||
// $server->resource('accounts', AccountController::class)
|
||||
// ->relationships(function (Relationships $relations): void {
|
||||
// $relations->hasOne('user')->readOnly();
|
||||
// })
|
||||
// ;
|
||||
//
|
||||
// // USERS
|
||||
// $server->resource('users', JsonApiController::class)->readOnly()->relationships(function (Relationships $relations): void {
|
||||
// $relations->hasMany('accounts')->readOnly();
|
||||
// });
|
||||
// })
|
||||
// ;
|
||||
|
||||
/*
|
||||
* ____ ____ __ .______ ______ __ __ .___________. _______ _______.
|
||||
|
||||
Reference in New Issue
Block a user