Compare commits

..

54 Commits

Author SHA1 Message Date
github-actions[bot]
b506281bd6 Merge pull request #10716 from firefly-iii/release-1754505595
🤖 Automatically merge the PR into the develop branch.
2025-08-06 20:40:02 +02:00
JC5
dfe9b3e787 🤖 Auto commit for release 'develop' on 2025-08-06 2025-08-06 20:39:55 +02:00
James Cole
2428a2a7c5 Expand API test code. 2025-08-06 20:27:59 +02:00
James Cole
0e8f608e00 Optimize available budgets. 2025-08-06 20:23:58 +02:00
James Cole
70071767ab Collect account balances, optimized. 2025-08-06 20:15:02 +02:00
James Cole
0ad6beb66c Optimize recurrence enrichment. 2025-08-06 15:35:29 +02:00
James Cole
1197f65589 Start work on recurring transaction enrichment. 2025-08-06 10:46:23 +02:00
James Cole
7bbf2dcc6f Add enrichment. 2025-08-06 09:18:29 +02:00
James Cole
d4ab69ebe6 Expand piggy bank enrichment. 2025-08-06 09:15:54 +02:00
James Cole
c8c552602e Expand piggy bank events. 2025-08-05 19:49:13 +02:00
James Cole
1921a8050b Fix #10709 2025-08-05 18:56:31 +02:00
James Cole
f488feda93 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-05 17:57:57 +02:00
James Cole
d90c033b83 Fix #10708 2025-08-05 17:57:52 +02:00
github-actions[bot]
9f256253f2 Merge pull request #10707 from firefly-iii/release-1754394813
🤖 Automatically merge the PR into the develop branch.
2025-08-05 13:53:39 +02:00
JC5
489b7c12e5 🤖 Auto commit for release 'develop' on 2025-08-05 2025-08-05 13:53:33 +02:00
Sander Dorigo
1049a8314d Fix lang 2025-08-05 13:48:58 +02:00
Sander Dorigo
48301b6b9c Add missing classes 2025-08-05 13:40:46 +02:00
Sander Dorigo
5a92215921 Fix #10706 2025-08-05 12:50:05 +02:00
Sander Dorigo
ccfc75852a Fix #10702 2025-08-05 11:02:26 +02:00
Sander Dorigo
9804cffff3 Fix #10704 2025-08-05 10:41:54 +02:00
James Cole
901e113fef Fix #10700 2025-08-05 05:55:45 +02:00
James Cole
a4021ff056 Enrich categories. 2025-08-04 20:55:31 +02:00
James Cole
902d91ad29 Add moment JS 2025-08-04 20:19:09 +02:00
James Cole
fa2cf22e73 Add locale files 2025-08-04 20:17:11 +02:00
James Cole
970dad4c49 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-04 20:11:57 +02:00
James Cole
9d01c7bdb8 Add language 2025-08-04 20:11:51 +02:00
github-actions[bot]
dc7d4fb258 Merge pull request #10698 from firefly-iii/release-1754331101
🤖 Automatically merge the PR into the develop branch.
2025-08-04 20:11:50 +02:00
JC5
a807ca5002 🤖 Auto commit for release 'develop' on 2025-08-04 2025-08-04 20:11:41 +02:00
github-actions[bot]
d59d326841 Merge pull request #10693 from firefly-iii/release-1754278961
🤖 Automatically merge the PR into the develop branch.
2025-08-04 05:42:50 +02:00
JC5
b915548e82 🤖 Auto commit for release 'develop' on 2025-08-04 2025-08-04 05:42:41 +02:00
James Cole
8200a81840 Add enrichment. 2025-08-03 20:19:07 +02:00
James Cole
6a49918707 Add budget transformer and enrichment. 2025-08-03 20:17:50 +02:00
James Cole
e55fc483bd Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-03 17:42:13 +02:00
James Cole
4ff5f5883d Improve budget limit. 2025-08-03 17:42:07 +02:00
github-actions[bot]
eda2eae04a Merge pull request #10689 from firefly-iii/release-1754232349
🤖 Automatically merge the PR into the develop branch.
2025-08-03 16:45:56 +02:00
JC5
c07c30ea17 🤖 Auto commit for release 'develop' on 2025-08-03 2025-08-03 16:45:49 +02:00
James Cole
56f1eb03e0 Add missing field. 2025-08-03 10:24:49 +02:00
James Cole
d4e14dd262 Move account balance logic to enrichment. 2025-08-03 10:22:12 +02:00
James Cole
0c7f04fb17 Expand bill transformer. 2025-08-03 08:12:19 +02:00
James Cole
061c01da53 Remove unnecessary setters. 2025-08-03 08:02:13 +02:00
James Cole
716d72d8af Optimize available budgets. 2025-08-03 07:53:36 +02:00
James Cole
3233ca4a4c Fix badly named field. 2025-08-03 07:13:57 +02:00
James Cole
1041030b1e First start on optimized available balance enrichment. 2025-08-03 07:12:06 +02:00
James Cole
bb3b06cf08 Fix #10687 2025-08-02 19:17:37 +02:00
James Cole
f35e361915 Match fields for 6.3.0 2025-08-02 14:21:22 +02:00
James Cole
47d697c7dc Remove references to "balances". 2025-08-02 11:07:35 +02:00
James Cole
3745d79f1f Clean up account transformer. 2025-08-02 07:16:30 +02:00
github-actions[bot]
04cbff4b9a Merge pull request #10684 from firefly-iii/release-1754070963
🤖 Automatically merge the PR into the develop branch.
2025-08-01 19:56:12 +02:00
JC5
0c2ca4b97c 🤖 Auto commit for release 'develop' on 2025-08-01 2025-08-01 19:56:03 +02:00
James Cole
3918665cd1 Move all endpoints to v1. 2025-08-01 19:41:36 +02:00
James Cole
9eb8869649 Fix files. 2025-08-01 19:33:30 +02:00
github-actions[bot]
62221af591 Merge pull request #10683 from firefly-iii/release-1754069211
🤖 Automatically merge the PR into the develop branch.
2025-08-01 19:26:58 +02:00
JC5
4d013a44ce 🤖 Auto commit for release 'develop' on 2025-08-01 2025-08-01 19:26:51 +02:00
Sander Dorigo
c55cfd1acf Rename file 2025-08-01 15:30:45 +02:00
119 changed files with 3666 additions and 1398 deletions

View File

@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Generic\DateRequest;
use FireflyIII\Api\V1\Requests\Data\DateRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Budget;
@@ -291,7 +291,7 @@ class BudgetController extends Controller
}
if ($current->transaction_currency_id !== $this->primaryCurrency->id) {
// convert and then add it.
$converted = $converter->convert($current->transactionCurrency, $this->primaryCurrency, $limit->start_date, $limit->amount);
$converted = $converter->convert($current->transactionCurrency, $this->primaryCurrency, $current->start_date, $current->amount);
$amount = bcadd($amount, $converted);
Log::debug(sprintf('Budgeted in limit #%d: %s %s, converted to %s %s', $current->id, $current->transactionCurrency->code, $current->amount, $this->primaryCurrency->code, $converted));
Log::debug(sprintf('Set amount in limit to %s', $amount));

View File

@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Request\Generic\DateRequest;
use FireflyIII\Api\V1\Requests\Data\DateRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Enums\UserRoleEnum;

View File

@@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\PiggyBankTransformer;
@@ -117,6 +118,13 @@ class ListController extends Controller
$count = $collection->count();
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$piggyBanks = $enrichment->enrich($piggyBanks);
// make paginator:
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.accounts.piggy-banks', [$account->id]).$this->buildParams());
@@ -125,7 +133,7 @@ class ListController extends Controller
$transformer = app(PiggyBankTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy_banks');
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy-banks');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -96,8 +96,8 @@ class ShowController extends Controller
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
$enrichment->setDate($this->parameters->get('date'));
$enrichment->setUser($admin);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$accounts = $enrichment->enrich($accounts);
// make paginator:
@@ -131,8 +131,8 @@ class ShowController extends Controller
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
$enrichment->setDate($this->parameters->get('date'));
$enrichment->setUser($admin);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$account = $enrichment->enrichSingle($account);

View File

@@ -75,8 +75,8 @@ class StoreController extends Controller
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
$enrichment->setDate(null);
$enrichment->setUser($admin);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$account = $enrichment->enrichSingle($account);
/** @var AccountTransformer $transformer */

View File

@@ -80,8 +80,8 @@ class UpdateController extends Controller
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
$enrichment->setDate(null);
$enrichment->setUser($admin);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$account = $enrichment->enrichSingle($account);
/** @var AccountTransformer $transformer */

View File

@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\AvailableBudgetEnrichment;
use FireflyIII\Transformers\AvailableBudgetTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
@@ -75,7 +76,6 @@ class ShowController extends Controller
// types to get, page size:
$pageSize = $this->parameters->get('limit');
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
@@ -84,6 +84,13 @@ class ShowController extends Controller
$count = $collection->count();
$availableBudgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AvailableBudgetEnrichment();
$enrichment->setUser($admin);
$availableBudgets = $enrichment->enrich($availableBudgets);
// make paginator:
$paginator = new LengthAwarePaginator($availableBudgets, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.available-budgets.index').$this->buildParams());
@@ -106,13 +113,25 @@ class ShowController extends Controller
*/
public function show(AvailableBudget $availableBudget): JsonResponse
{
$manager = $this->getManager();
$manager = $this->getManager();
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
/** @var AvailableBudgetTransformer $transformer */
$transformer = app(AvailableBudgetTransformer::class);
$transformer = app(AvailableBudgetTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new Item($availableBudget, $transformer, 'available_budgets');
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AvailableBudgetEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$availableBudget = $enrichment->enrichSingle($availableBudget);
$resource = new Item($availableBudget, $transformer, 'available_budgets');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}

View File

@@ -83,8 +83,6 @@ class ShowController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$bills = $enrichment->enrich($bills);
@@ -114,8 +112,6 @@ class ShowController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$bill = $enrichment->enrichSingle($bill);

View File

@@ -79,8 +79,6 @@ class StoreController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$bill = $enrichment->enrichSingle($bill);

View File

@@ -74,8 +74,6 @@ class UpdateController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$bill = $enrichment->enrichSingle($bill);

View File

@@ -32,6 +32,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\BudgetLimitTransformer;
@@ -117,6 +118,14 @@ class ListController extends Controller
$paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.budgets.budget-limits', [$budget->id]).$this->buildParams());
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetLimitEnrichment();
$enrichment->setUser($admin);
$budgetLimits = $enrichment->enrich($budgetLimits);
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -29,7 +29,9 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment;
use FireflyIII\Transformers\BudgetTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
@@ -82,6 +84,15 @@ class ShowController extends Controller
$count = $collection->count();
$budgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$budgets = $enrichment->enrich($budgets);
// make paginator:
$paginator = new LengthAwarePaginator($budgets, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.budgets.index').$this->buildParams());
@@ -103,6 +114,15 @@ class ShowController extends Controller
{
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$budget = $enrichment->enrichSingle($budget);
/** @var BudgetTransformer $transformer */
$transformer = app(BudgetTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Budget\StoreRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment;
use FireflyIII\Transformers\BudgetTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -69,6 +71,13 @@ class StoreController extends Controller
$budget->refresh();
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetEnrichment();
$enrichment->setUser($admin);
$budget = $enrichment->enrichSingle($budget);
/** @var BudgetTransformer $transformer */
$transformer = app(BudgetTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Budget\UpdateRequest;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment;
use FireflyIII\Transformers\BudgetTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -67,6 +69,13 @@ class UpdateController extends Controller
$budget = $this->repository->update($budget, $data);
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetEnrichment();
$enrichment->setUser($admin);
$budget = $enrichment->enrichSingle($budget);
/** @var BudgetTransformer $transformer */
$transformer = app(BudgetTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -31,6 +31,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
use FireflyIII\Transformers\BudgetLimitTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
@@ -84,6 +85,14 @@ class ShowController extends Controller
$paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.budgets.limits.index', [$budget->id]).$this->buildParams());
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetLimitEnrichment();
$enrichment->setUser($admin);
$budgetLimits = $enrichment->enrich($budgetLimits);
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters);
@@ -113,6 +122,13 @@ class ShowController extends Controller
$paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.budget-limits.index').$this->buildParams());
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetLimitEnrichment();
$enrichment->setUser($admin);
$budgetLimits = $enrichment->enrich($budgetLimits);
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters);
@@ -137,6 +153,13 @@ class ShowController extends Controller
// continue!
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetLimitEnrichment();
$enrichment->setUser($admin);
$budgetLimit = $enrichment->enrichSingle($budgetLimit);
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\BudgetLimit\StoreRequest;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
use FireflyIII\Transformers\BudgetLimitTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
@@ -74,6 +75,13 @@ class StoreController extends Controller
$budgetLimit = $this->blRepository->store($data);
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetLimitEnrichment();
$enrichment->setUser($admin);
$budgetLimit = $enrichment->enrichSingle($budgetLimit);
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -30,6 +30,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
use FireflyIII\Transformers\BudgetLimitTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
@@ -80,6 +81,13 @@ class UpdateController extends Controller
$budgetLimit = $this->blRepository->update($budgetLimit, $data);
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetLimitEnrichment();
$enrichment->setUser($admin);
$budgetLimit = $enrichment->enrich($budgetLimit);
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\CategoryEnrichment;
use FireflyIII\Transformers\CategoryTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
@@ -78,6 +80,15 @@ class ShowController extends Controller
$count = $collection->count();
$categories = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new CategoryEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$categories = $enrichment->enrich($categories);
// make paginator:
$paginator = new LengthAwarePaginator($categories, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.categories.index').$this->buildParams());
@@ -105,6 +116,15 @@ class ShowController extends Controller
$transformer = app(CategoryTransformer::class);
$transformer->setParameters($this->parameters);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new CategoryEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$category = $enrichment->enrichSingle($category);
$resource = new Item($category, $transformer, 'categories');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Category\StoreRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\CategoryEnrichment;
use FireflyIII\Transformers\CategoryTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -72,6 +74,15 @@ class StoreController extends Controller
$transformer = app(CategoryTransformer::class);
$transformer->setParameters($this->parameters);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new CategoryEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$category = $enrichment->enrichSingle($category);
$resource = new Item($category, $transformer, 'categories');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Category\UpdateRequest;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\CategoryEnrichment;
use FireflyIII\Transformers\CategoryTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -71,6 +73,15 @@ class UpdateController extends Controller
$transformer = app(CategoryTransformer::class);
$transformer->setParameters($this->parameters);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new CategoryEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$category = $enrichment->enrichSingle($category);
$resource = new Item($category, $transformer, 'categories');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\Transformers\BillTransformer;
use FireflyIII\Transformers\PiggyBankTransformer;
@@ -85,8 +86,6 @@ class ListController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$bills = $enrichment->enrich($bills);
@@ -126,6 +125,13 @@ class ListController extends Controller
$count = $collection->count();
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$piggyBanks = $enrichment->enrich($piggyBanks);
// make paginator:
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.object-groups.piggy-banks', [$objectGroup->id]).$this->buildParams());
@@ -134,7 +140,7 @@ class ListController extends Controller
$transformer = app(PiggyBankTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy_banks');
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy-banks');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEventEnrichment;
use FireflyIII\Transformers\AccountTransformer;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\PiggyBankEventTransformer;
@@ -83,8 +84,8 @@ class ListController extends Controller
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
$enrichment->setDate($this->parameters->get('date'));
$enrichment->setUser($admin);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$accounts = $enrichment->enrich($accounts);
// make paginator:
@@ -148,6 +149,13 @@ class ListController extends Controller
$count = $collection->count();
$events = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEventEnrichment();
$enrichment->setUser($admin);
$events = $enrichment->enrich($events);
// make paginator:
$paginator = new LengthAwarePaginator($events, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.piggy-banks.events', [$piggyBank->id]).$this->buildParams());
@@ -156,7 +164,7 @@ class ListController extends Controller
$transformer = app(PiggyBankEventTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($events, $transformer, 'piggy_bank_events');
$resource = new FractalCollection($events, $transformer, sprintf('piggy-banks/%d/events', $piggyBank->id));
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
use FireflyIII\Transformers\PiggyBankTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
@@ -77,6 +79,13 @@ class ShowController extends Controller
$count = $collection->count();
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$piggyBanks = $enrichment->enrich($piggyBanks);
// make paginator:
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.piggy-banks.index').$this->buildParams());
@@ -85,7 +94,7 @@ class ShowController extends Controller
$transformer = app(PiggyBankTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy_banks');
$resource = new FractalCollection($piggyBanks, $transformer, 'piggy-banks');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
@@ -101,11 +110,19 @@ class ShowController extends Controller
{
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$piggyBank = $enrichment->enrichSingle($piggyBank);
/** @var PiggyBankTransformer $transformer */
$transformer = app(PiggyBankTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new Item($piggyBank, $transformer, 'piggy_banks');
$resource = new Item($piggyBank, $transformer, 'piggy-banks');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\PiggyBank\StoreRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
use FireflyIII\Transformers\PiggyBankTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -68,6 +70,13 @@ class StoreController extends Controller
$piggyBank = $this->repository->store($request->getAll());
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$piggyBank = $enrichment->enrichSingle($piggyBank);
/** @var PiggyBankTransformer $transformer */
$transformer = app(PiggyBankTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\PiggyBank\UpdateRequest;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
use FireflyIII\Transformers\PiggyBankTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -70,13 +72,20 @@ class UpdateController extends Controller
$this->repository->setCurrentAmount($piggyBank, $data['current_amount']);
}
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$piggyBank = $enrichment->enrichSingle($piggyBank);
$manager = $this->getManager();
/** @var PiggyBankTransformer $transformer */
$transformer = app(PiggyBankTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new Item($piggyBank, $transformer, 'piggy_banks');
$resource = new Item($piggyBank, $transformer, 'piggy-banks');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
@@ -76,17 +78,24 @@ class ShowController extends Controller
// get list of budgets. Count it and split it.
$collection = $this->repository->get();
$count = $collection->count();
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$recurrences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrences = $enrichment->enrich($recurrences);
// make paginator:
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
$paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.recurrences.index').$this->buildParams());
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($piggyBanks, $transformer, 'recurrences');
$resource = new FractalCollection($recurrences, $transformer, 'recurrences');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
@@ -102,6 +111,13 @@ class ShowController extends Controller
{
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrence = $enrichment->enrichSingle($recurrence);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalAPIRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEventEnrichment;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\PiggyBankEventTransformer;
use FireflyIII\Transformers\TransactionLinkTransformer;
@@ -113,6 +114,14 @@ class ListController extends Controller
}
$count = $collection->count();
$events = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEventEnrichment();
$enrichment->setUser($admin);
$events = $enrichment->enrich($events);
// make paginator:
$paginator = new LengthAwarePaginator($events, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.transactions.piggy-bank-events', [$transactionGroup->id]).$this->buildParams());

View File

@@ -43,6 +43,7 @@ use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AccountTransformer;
@@ -107,8 +108,8 @@ class ListController extends Controller
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
$enrichment->setDate($this->parameters->get('date'));
$enrichment->setUser($admin);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$accounts = $enrichment->enrich($accounts);
// make paginator:
@@ -188,8 +189,6 @@ class ListController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end'));
$bills = $enrichment->enrichSingle($bills);
@@ -229,6 +228,13 @@ class ListController extends Controller
$paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.currencies.budget-limits', [$currency->code]).$this->buildParams());
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new BudgetLimitEnrichment();
$enrichment->setUser($admin);
$budgetLimits = $enrichment->enrich($budgetLimits);
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -88,8 +88,8 @@ class AccountController extends Controller
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
$enrichment->setDate($this->parameters->get('date'));
$enrichment->setUser($admin);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$accounts = $enrichment->enrich($accounts);
/** @var AccountTransformer $transformer */

View File

@@ -1,62 +0,0 @@
<?php
/*
* DateRequest.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Generic;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
/**
* Request class for end points that require date parameters.
*
* Class DateRequest
*/
class DateRequest extends FormRequest
{
use ChecksLogin;
use ConvertsDataTypes;
/**
* Get all data from the request.
*/
public function getAll(): array
{
return [
'start' => $this->getCarbonDate('start')->startOfDay(),
'end' => $this->getCarbonDate('end')->endOfDay(),
];
}
/**
* The rules that the incoming request must be matched against.
*/
public function rules(): array
{
return [
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
];
}
}

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\PiggyBank;
use Illuminate\Contracts\Validation\Validator;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\IsValidZeroOrMoreAmount;
@@ -96,7 +95,10 @@ class StoreRequest extends FormRequest
function (Validator $validator): void {
// validate start before end only if both are there.
$data = $validator->getData();
$currency = $this->getCurrencyFromData($data);
$currency = $this->getCurrencyFromData($validator, $data);
if (null === $currency) {
return;
}
$targetAmount = (string) ($data['target_amount'] ?? '0');
$currentAmount = '0';
if (array_key_exists('accounts', $data) && is_array($data['accounts'])) {
@@ -130,7 +132,7 @@ class StoreRequest extends FormRequest
}
}
private function getCurrencyFromData(array $data): TransactionCurrency
private function getCurrencyFromData(Validator $validator, array $data): ?TransactionCurrency
{
if (array_key_exists('transaction_currency_code', $data) && '' !== (string) $data['transaction_currency_code']) {
$currency = TransactionCurrency::whereCode($data['transaction_currency_code'])->first();
@@ -144,7 +146,8 @@ class StoreRequest extends FormRequest
return $currency;
}
}
$validator->errors()->add('transaction_currency_id', trans('validation.require_currency_id_code'));
throw new FireflyException('Unexpected empty currency.');
return null;
}
}

View File

@@ -29,7 +29,7 @@ use FireflyIII\Models\UserGroup;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class UserGroupChangedDefaultCurrency extends Event
class UserGroupChangedPrimaryCurrency extends Event
{
use SerializesModels;

View File

@@ -32,7 +32,6 @@ use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Webhook;
use FireflyIII\Models\WebhookMessage;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Transformers\AccountTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer;
@@ -176,8 +175,8 @@ class StandardMessageGenerator implements MessageGeneratorInterface
/** @var TransactionGroup $model */
$accounts = $this->collectAccounts($model);
$enrichment = new AccountEnrichment();
$enrichment->setDate(null);
$enrichment->setUser($model->user);
$enrichment->setPrimaryCurrency(Amount::getPrimaryCurrencyByUserGroup($model->userGroup));
$accounts = $enrichment->enrich($accounts);
foreach ($accounts as $account) {
$transformer = new AccountTransformer();

View File

@@ -91,8 +91,6 @@ class IndexController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($tempStart);
$enrichment->setEnd($end);
$collection = $enrichment->enrich($collection);

View File

@@ -149,10 +149,10 @@ class ShowController extends Controller
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setConvertToPrimary($this->convertToPrimary);
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setStart($start);
$enrichment->setEnd($end);
/** @var Bill $bill */
$bill = $enrichment->enrichSingle($bill);
/** @var BillTransformer $transformer */

View File

@@ -95,6 +95,7 @@ class EditController extends Controller
$preFilled = [
'active' => $hasOldInput ? (bool) $request->old('active') : $budget->active,
'auto_budget_currency_id' => $hasOldInput ? (int) $request->old('auto_budget_currency_id') : $this->primaryCurrency->id,
'notes' => $this->repository->getNoteText($budget),
];
if ($autoBudget instanceof AutoBudget) {
$amount = $hasOldInput ? $request->old('auto_budget_amount') : $autoBudget->amount;

View File

@@ -210,7 +210,6 @@ class CategoryReportController extends Controller
$spent = $this->opsRepository->listExpenses($start, $end, $accounts, new Collection([$category]));
$earned = $this->opsRepository->listIncome($start, $end, $accounts, new Collection([$category]));
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
// loop expenses.
foreach ($spent as $currency) {
// add things to chart Data for each currency:

View File

@@ -157,6 +157,11 @@ class DebugController extends Controller
return view('debug', compact('table', 'now', 'logContent'));
}
public function apiTest()
{
return view('test.api-test');
}
private function generateTable(): string
{
// system information:

View File

@@ -32,8 +32,10 @@ use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
use FireflyIII\Transformers\AccountTransformer;
use FireflyIII\Transformers\PiggyBankTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -114,6 +116,13 @@ class IndexController extends Controller
$transformer->setParameters(new ParameterBag());
$piggyBanks = [];
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$collection = $enrichment->enrich($collection);
/** @var PiggyBank $piggy */
foreach ($collection as $piggy) {
$array = $transformer->transform($piggy);
@@ -148,7 +157,7 @@ class IndexController extends Controller
// enrich each account.
$enrichment = new AccountEnrichment();
$enrichment->setUser(auth()->user());
$enrichment->setPrimaryCurrency($this->primaryCurrency);
$enrichment->setDate($end);
$return = [];
/** @var PiggyBank $piggy */

View File

@@ -29,7 +29,9 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\PiggyBankEnrichment;
use FireflyIII\Transformers\PiggyBankTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\ParameterBag;
@@ -75,6 +77,13 @@ class ShowController extends Controller
$parameters = new ParameterBag();
$parameters->set('end', $end);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new PiggyBankEnrichment();
$enrichment->setUser($admin);
$piggyBank = $enrichment->enrichSingle($piggyBank);
/** @var PiggyBankTransformer $transformer */
$transformer = app(PiggyBankTransformer::class);
$transformer->setParameters($parameters);

View File

@@ -44,7 +44,6 @@ use Illuminate\View\View;
use function Safe\json_decode;
use function Safe\file_get_contents;
use function Safe\strtotime;
/**
* Class PreferencesController.
@@ -277,10 +276,10 @@ class PreferencesController extends Controller
// custom fiscal year
$customFiscalYear = 1 === (int) $request->get('customFiscalYear');
$string = strtotime((string) $request->get('fiscalYearStart'));
if (false !== $string) {
$fiscalYearStart = Carbon::createFromTimestamp($string)->format('m-d');
Preferences::set('customFiscalYear', $customFiscalYear);
Preferences::set('customFiscalYear', $customFiscalYear);
$fiscalYearString = (string) $request->get('fiscalYearStart');
if ('' !== $fiscalYearString) {
$fiscalYearStart = Carbon::parse($fiscalYearString, config('app.timezone'))->format('m-d');
Preferences::set('fiscalYearStart', $fiscalYearStart);
}

View File

@@ -47,12 +47,13 @@ class BudgetFormUpdateRequest extends FormRequest
public function getBudgetData(): array
{
return [
'name' => $this->convertString('name'),
'active' => $this->boolean('active'),
'auto_budget_type' => $this->convertInteger('auto_budget_type'),
'currency_id' => $this->convertInteger('auto_budget_currency_id'),
'auto_budget_amount' => $this->convertString('auto_budget_amount'),
'auto_budget_period' => $this->convertString('auto_budget_period'),
'name' => $this->convertString('name'),
'active' => $this->boolean('active'),
'auto_budget_type' => $this->convertInteger('auto_budget_type'),
'currency_id' => $this->convertInteger('auto_budget_currency_id'),
'auto_budget_amount' => $this->convertString('auto_budget_amount'),
'auto_budget_period' => $this->convertString('auto_budget_period'),
'notes' => $this->stringWithNewlines('notes'),
];
}

View File

@@ -32,6 +32,7 @@ use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Override;
/**
* Class NoBudgetRepository
@@ -98,4 +99,23 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface, UserGroupInterf
return $summarizer->groupByCurrencyId($journals);
}
#[Override]
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
if ($accounts instanceof Collection && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if ($currency instanceof TransactionCurrency) {
$collector->setCurrency($currency);
}
$collector->withoutBudget();
$collector->withBudgetInformation();
return $collector->getExtractedJournals();
}
}

View File

@@ -49,4 +49,6 @@ interface NoBudgetRepositoryInterface
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array;
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array;
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array;
}

View File

@@ -39,6 +39,7 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
/**
* Class OperationsRepository
@@ -57,17 +58,17 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$total = '0';
$count = 0;
foreach ($budget->budgetlimits as $limit) {
$diff = (int)$limit->start_date->diffInDays($limit->end_date, true);
$diff = (int) $limit->start_date->diffInDays($limit->end_date, true);
$diff = 0 === $diff ? 1 : $diff;
$amount = $limit->amount;
$perDay = bcdiv((string)$amount, (string)$diff);
$perDay = bcdiv((string) $amount, (string) $diff);
$total = bcadd($total, $perDay);
++$count;
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
}
$avg = $total;
if ($count > 0) {
$avg = bcdiv($total, (string)$count);
$avg = bcdiv($total, (string) $count);
}
app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
@@ -95,9 +96,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
/** @var array $journal */
foreach ($journals as $journal) {
// prep data array for currency:
$budgetId = (int)$journal['budget_id'];
$budgetId = (int) $journal['budget_id'];
$budgetName = $journal['budget_name'];
$currencyId = (int)$journal['currency_id'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId);
$data[$key] ??= [
@@ -112,7 +113,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
'entries' => [],
];
$date = $journal['date']->format($carbonFormat);
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string)$journal['amount']);
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string) $journal['amount']);
}
return $data;
@@ -156,7 +157,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
foreach ($journals as $journal) {
$amount = app('steam')->negative($journal['amount']);
$journalCurrencyId = (int)$journal['currency_id'];
$journalCurrencyId = (int) $journal['currency_id'];
if (false === $convertToPrimary) {
$currencyId = $journalCurrencyId;
$currencyName = $journal['currency_name'];
@@ -169,8 +170,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$amount = $converter->convert($currencies[$journalCurrencyId], $primaryCurrency, $journal['date'], $amount);
}
$budgetId = (int)$journal['budget_id'];
$budgetName = (string)$journal['budget_name'];
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
// catch "no budget" entries.
if (0 === $budgetId) {
@@ -196,7 +197,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// add journal to array:
// only a subset of the fields.
$journalId = (int)$journal['transaction_journal_id'];
$journalId = (int) $journal['transaction_journal_id'];
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [
'amount' => $amount,
'destination_account_id' => $journal['destination_account_id'],
@@ -282,4 +283,77 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
return $summarizer->groupByCurrencyId($journals, 'negative', false);
}
public function sumCollectedExpenses(array $expenses, Carbon $start, Carbon $end, TransactionCurrency $transactionCurrency, bool $convertToPrimary = false): array
{
Log::debug(sprintf('Start of %s.', __METHOD__));
$summarizer = new TransactionSummarizer($this->user);
$summarizer->setConvertToPrimary($convertToPrimary);
// filter $journals by range AND currency if it is present.
$expenses = array_filter($expenses, static function (array $expense) use ($start, $end, $transactionCurrency): bool {
return $expense['date']->between($start, $end) && $expense['currency_id'] === $transactionCurrency->id;
});
return $summarizer->groupByCurrencyId($expenses, 'negative', false);
}
public function sumCollectedExpensesByBudget(array $expenses, Budget $budget, bool $convertToPrimary = false): array
{
Log::debug(sprintf('Start of %s.', __METHOD__));
$summarizer = new TransactionSummarizer($this->user);
$summarizer->setConvertToPrimary($convertToPrimary);
// filter $journals by range AND currency if it is present.
$expenses = array_filter($expenses, static function (array $expense) use ($budget): bool {
return $expense['budget_id'] === $budget->id;
});
return $summarizer->groupByCurrencyId($expenses, 'negative', false);
}
#[Override]
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null): array
{
Log::debug(sprintf('Start of %s(date, date, array, array, "%s").', __METHOD__, $currency?->code));
// this collector excludes all transfers TO liabilities (which are also withdrawals)
// because those expenses only become expenses once they move from the liability to the friend.
// 2024-12-24 disable the exclusion for now.
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
/** @var Account $account */
foreach ($subset as $account) {
if ('credit' === $repository->getMetaValue($account, 'liability_direction')) {
$selection->push($account);
}
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
// ->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
;
if ($accounts instanceof Collection) {
$collector->setAccounts($accounts);
}
if (!$budgets instanceof Collection) {
$budgets = $this->getBudgets();
}
if ($currency instanceof TransactionCurrency) {
Log::debug(sprintf('Limit to normal currency %s', $currency->code));
$collector->setNormalCurrency($currency);
}
if ($budgets->count() > 0) {
$collector->setBudgets($budgets);
}
return $collector->getExtractedJournals();
}
}

View File

@@ -24,8 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Budget;
use Deprecated;
use Carbon\Carbon;
use Deprecated;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionCurrency;
@@ -73,4 +73,10 @@ interface OperationsRepositoryInterface
?TransactionCurrency $currency = null,
bool $convertToPrimary = false
): array;
public function sumCollectedExpenses(array $expenses, Carbon $start, Carbon $end, TransactionCurrency $transactionCurrency, bool $convertToPrimary = false): array;
public function sumCollectedExpensesByBudget(array $expenses, Budget $budget, bool $convertToPrimary = false): array;
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null): array;
}

View File

@@ -27,6 +27,7 @@ namespace FireflyIII\Repositories\Category;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Category;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
@@ -444,4 +445,74 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
return $array;
}
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
if ($accounts instanceof Collection && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (!$categories instanceof Collection || 0 === $categories->count()) {
$categories = $this->getCategories();
}
$collector->setCategories($categories);
$collector->withCategoryInformation();
return $collector->getExtractedJournals();
}
public function collectIncome(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)
->setTypes([TransactionTypeEnum::DEPOSIT->value])
;
if ($accounts instanceof Collection && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (!$categories instanceof Collection || 0 === $categories->count()) {
$categories = $this->getCategories();
}
$collector->setCategories($categories);
return $collector->getExtractedJournals();
}
public function collectTransfers(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)
->setTypes([TransactionTypeEnum::TRANSFER->value])
;
if ($accounts instanceof Collection && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (!$categories instanceof Collection || 0 === $categories->count()) {
$categories = $this->getCategories();
}
$collector->setCategories($categories);
return $collector->getExtractedJournals();
}
public function sumCollectedTransactionsByCategory(array $expenses, Category $category, string $method, bool $convertToPrimary = false): array
{
Log::debug(sprintf('Start of %s.', __METHOD__));
$summarizer = new TransactionSummarizer($this->user);
$summarizer->setConvertToPrimary($convertToPrimary);
// filter $journals by range AND currency if it is present.
$expenses = array_filter($expenses, static function (array $expense) use ($category): bool {
return $expense['category_id'] === $category->id;
});
return $summarizer->groupByCurrencyId($expenses, $method, false);
}
}

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Repositories\Category;
use Carbon\Carbon;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Category;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
@@ -78,6 +79,14 @@ interface OperationsRepositoryInterface
*/
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array;
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array;
public function collectIncome(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array;
public function collectTransfers(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array;
public function sumCollectedTransactionsByCategory(array $expenses, Category $category, string $method, bool $convertToPrimary = false): array;
/**
* Sum of income journals in period for a set of categories, grouped per currency. Amounts are always positive.
*/

View File

@@ -131,7 +131,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
/**
* Get current amount saved in piggy bank.
*/
public function getCurrentPrimaryAmount(PiggyBank $piggyBank, ?Account $account = null): string
public function getCurrentPrimaryCurrencyAmount(PiggyBank $piggyBank, ?Account $account = null): string
{
$sum = '0';
foreach ($piggyBank->accounts as $current) {

View File

@@ -80,7 +80,7 @@ interface PiggyBankRepositoryInterface
*/
public function getCurrentAmount(PiggyBank $piggyBank, ?Account $account = null): string;
public function getCurrentPrimaryAmount(PiggyBank $piggyBank, ?Account $account = null): string;
public function getCurrentPrimaryCurrencyAmount(PiggyBank $piggyBank, ?Account $account = null): string;
/**
* Get all events.

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -34,7 +35,9 @@ use FireflyIII\Models\Location;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
@@ -48,7 +51,7 @@ use Override;
*/
class AccountEnrichment implements EnrichmentInterface
{
private array $accountIds;
private array $ids;
private array $accountTypeIds;
private array $accountTypes;
private Collection $collection;
@@ -61,22 +64,26 @@ class AccountEnrichment implements EnrichmentInterface
private User $user;
private UserGroup $userGroup;
private array $lastActivities;
private ?Carbon $date = null;
private bool $convertToPrimary = false;
private array $balances = [];
/**
* TODO The account enricher must do conversion from and to the primary currency.
*/
public function __construct()
{
$this->accountIds = [];
$this->openingBalances = [];
$this->currencies = [];
$this->accountTypeIds = [];
$this->accountTypes = [];
$this->meta = [];
$this->notes = [];
$this->lastActivities = [];
$this->locations = [];
// $this->repository = app(AccountRepositoryInterface::class);
// $this->currencyRepository = app(CurrencyRepositoryInterface::class);
// $this->start = null;
// $this->end = null;
$this->ids = [];
$this->openingBalances = [];
$this->currencies = [];
$this->accountTypeIds = [];
$this->accountTypes = [];
$this->meta = [];
$this->notes = [];
$this->lastActivities = [];
$this->locations = [];
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
}
#[Override]
@@ -99,26 +106,27 @@ class AccountEnrichment implements EnrichmentInterface
// prep local fields
$this->collection = $collection;
$this->collectAccountIds();
$this->collectIds();
$this->getAccountTypes();
$this->collectMetaData();
$this->collectNotes();
$this->collectLastActivities();
$this->collectLocations();
$this->collectOpeningBalances();
$this->collectBalances();
$this->appendCollectedData();
return $this->collection;
}
private function collectAccountIds(): void
private function collectIds(): void
{
/** @var Account $account */
foreach ($this->collection as $account) {
$this->accountIds[] = (int) $account->id;
$this->accountTypeIds[] = (int) $account->account_type_id;
$this->ids[] = (int)$account->id;
$this->accountTypeIds[] = (int)$account->account_type_id;
}
$this->accountIds = array_unique($this->accountIds);
$this->ids = array_unique($this->ids);
$this->accountTypeIds = array_unique($this->accountTypeIds);
}
@@ -128,27 +136,29 @@ class AccountEnrichment implements EnrichmentInterface
/** @var AccountType $type */
foreach ($types as $type) {
$this->accountTypes[(int) $type->id] = $type->type;
$this->accountTypes[(int)$type->id] = $type->type;
}
}
private function collectMetaData(): void
{
$set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
->whereIn('account_id', $this->accountIds)
->whereIn('account_id', $this->ids)
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray()
;
/** @var array $entry */
foreach ($set as $entry) {
$this->meta[(int) $entry['account_id']][$entry['name']] = (string) $entry['data'];
$this->meta[(int)$entry['account_id']][$entry['name']] = (string)$entry['data'];
if ('currency_id' === $entry['name']) {
$this->currencies[(int) $entry['data']] = true;
$this->currencies[(int)$entry['data']] = true;
}
}
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
foreach ($currencies as $currency) {
$this->currencies[(int) $currency->id] = $currency;
if (count($this->currencies) > 0) {
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
foreach ($currencies as $currency) {
$this->currencies[(int)$currency->id] = $currency;
}
}
$this->currencies[0] = $this->primaryCurrency;
foreach ($this->currencies as $id => $currency) {
@@ -160,28 +170,28 @@ class AccountEnrichment implements EnrichmentInterface
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->accountIds)
$notes = Note::query()->whereIn('noteable_id', $this->ids)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectLocations(): void
{
$locations = Location::query()->whereIn('locatable_id', $this->accountIds)
$locations = Location::query()->whereIn('locatable_id', $this->ids)
->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray()
;
foreach ($locations as $location) {
$this->locations[(int) $location['locatable_id']]
$this->locations[(int)$location['locatable_id']]
= [
'latitude' => (float) $location['latitude'],
'longitude' => (float) $location['longitude'],
'zoom_level' => (int) $location['zoom_level'],
'latitude' => (float)$location['latitude'],
'longitude' => (float)$location['longitude'],
'zoom_level' => (int)$location['zoom_level'],
];
}
Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
@@ -201,12 +211,12 @@ class AccountEnrichment implements EnrichmentInterface
;
$journals = $collector->getExtractedJournals();
foreach ($journals as $journal) {
$this->openingBalances[(int) $journal['source_account_id']]
$this->openingBalances[(int)$journal['source_account_id']]
= [
'amount' => Steam::negative($journal['amount']),
'date' => $journal['date'],
];
$this->openingBalances[(int) $journal['destination_account_id']]
$this->openingBalances[(int)$journal['destination_account_id']]
= [
'amount' => Steam::positive($journal['amount']),
'date' => $journal['date'],
@@ -227,64 +237,127 @@ class AccountEnrichment implements EnrichmentInterface
private function appendCollectedData(): void
{
$accountTypes = $this->accountTypes;
$meta = $this->meta;
$currencies = $this->currencies;
$notes = $this->notes;
$openingBalances = $this->openingBalances;
$locations = $this->locations;
$lastActivities = $this->lastActivities;
$this->collection = $this->collection->map(function (Account $item) use ($accountTypes, $meta, $currencies, $notes, $openingBalances, $locations, $lastActivities) {
$item->full_account_type = $accountTypes[(int) $item->account_type_id] ?? null;
$accountMeta = [
'currency' => null,
'location' => [
$this->collection = $this->collection->map(function (Account $item) {
$id = (int)$item->id;
$item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null;
$meta = [
'currency' => null,
'location' => [
'latitude' => null,
'longitude' => null,
'zoom_level' => null,
],
'opening_balance_date' => null,
'opening_balance_amount' => null,
'account_number' => null,
'notes' => $notes[$id] ?? null,
'last_activity' => $this->lastActivities[$id] ?? null,
];
if (array_key_exists((int) $item->id, $meta)) {
foreach ($meta[(int) $item->id] as $name => $value) {
$accountMeta[$name] = $value;
// if location, add location:
if (array_key_exists($id, $this->locations)) {
$meta['location'] = $this->locations[$id];
}
if (array_key_exists($id, $this->meta)) {
foreach ($this->meta[$id] as $name => $value) {
$meta[$name] = $value;
}
}
// also add currency, if present.
if (array_key_exists('currency_id', $accountMeta)) {
$currencyId = (int) $accountMeta['currency_id'];
$accountMeta['currency'] = $currencies[$currencyId];
if (array_key_exists('currency_id', $meta)) {
$currencyId = (int)$meta['currency_id'];
$meta['currency'] = $this->currencies[$currencyId];
}
// if notes, add notes.
if (array_key_exists($item->id, $notes)) {
$accountMeta['notes'] = $notes[$item->id];
}
// if opening balance, add opening balance
if (array_key_exists($item->id, $openingBalances)) {
$accountMeta['opening_balance_date'] = $openingBalances[$item->id]['date'];
$accountMeta['opening_balance_amount'] = $openingBalances[$item->id]['amount'];
if (array_key_exists($id, $this->openingBalances)) {
$meta['opening_balance_date'] = $this->openingBalances[$id]['date'];
$meta['opening_balance_amount'] = $this->openingBalances[$id]['amount'];
}
// if location, add location:
if (array_key_exists($item->id, $locations)) {
$accountMeta['location'] = $locations[$item->id];
// add balances
// get currencies:
$currency = $this->primaryCurrency; // assume primary currency
if (null !== $meta['currency']) {
$currency = $meta['currency'];
}
if (array_key_exists($item->id, $lastActivities)) {
$accountMeta['last_activity'] = $lastActivities[$item->id];
// get the current balance:
$date = $this->getDate();
// $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
$finalBalance = $this->balances[$id];
Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance);
// collect current balances:
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
$openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places);
$virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places);
$debtAmount = $meta['current_debt'] ?? null;
// set some pc_ default values to NULL:
$pcCurrentBalance = null;
$pcOpeningBalance = null;
$pcVirtualBalance = null;
$pcDebtAmount = null;
// convert to primary currency if needed:
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
Log::debug(sprintf('Convert to primary, from %s to %s', $currency->code, $this->primaryCurrency->code));
$converter = new ExchangeRateConverter();
$pcCurrentBalance = $converter->convert($currency, $this->primaryCurrency, $date, $currentBalance);
$pcOpeningBalance = $converter->convert($currency, $this->primaryCurrency, $date, $openingBalance);
$pcVirtualBalance = $converter->convert($currency, $this->primaryCurrency, $date, $virtualBalance);
$pcDebtAmount = null === $debtAmount ? null : $converter->convert($currency, $this->primaryCurrency, $date, $debtAmount);
}
$item->meta = $accountMeta;
if ($this->convertToPrimary && $currency->id === $this->primaryCurrency->id) {
$pcCurrentBalance = $currentBalance;
$pcOpeningBalance = $openingBalance;
$pcVirtualBalance = $virtualBalance;
$pcDebtAmount = $debtAmount;
}
// set opening balance(s) to NULL if the date is null
if (null === $meta['opening_balance_date']) {
$openingBalance = null;
$pcOpeningBalance = null;
}
$meta['balances'] = [
'current_balance' => $currentBalance,
'pc_current_balance' => $pcCurrentBalance,
'opening_balance' => $openingBalance,
'pc_opening_balance' => $pcOpeningBalance,
'virtual_balance' => $virtualBalance,
'pc_virtual_balance' => $pcVirtualBalance,
'debt_amount' => $debtAmount,
'pc_debt_amount' => $pcDebtAmount,
];
// end add balances
$item->meta = $meta;
return $item;
});
}
public function setPrimaryCurrency(TransactionCurrency $primary): void
{
$this->primaryCurrency = $primary;
}
private function collectLastActivities(): void
{
$this->lastActivities = Steam::getLastActivities($this->accountIds);
$this->lastActivities = Steam::getLastActivities($this->ids);
}
private function collectBalances(): void
{
$this->balances = Steam::finalAccountsBalanceOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
}
public function setDate(?Carbon $date): void
{
$this->date = $date;
}
public function getDate(): Carbon
{
if (null === $this->date) {
return today();
}
return $this->date;
}
}

View File

@@ -0,0 +1,179 @@
<?php
/*
* AvailableBudgetEnrichment.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
class AvailableBudgetEnrichment implements EnrichmentInterface
{
private User $user;
private UserGroup $userGroup;
private TransactionCurrency $primaryCurrency;
private bool $convertToPrimary = false;
private array $ids = [];
private array $currencyIds = [];
private array $currencies = [];
private Collection $collection;
private array $spentInBudgets = [];
private array $spentOutsideBudgets = [];
private array $pcSpentInBudgets = [];
private array $pcSpentOutsideBudgets = [];
private readonly NoBudgetRepositoryInterface $noBudgetRepository;
private readonly OperationsRepositoryInterface $opsRepository;
private readonly BudgetRepositoryInterface $repository;
private ?Carbon $start = null;
private ?Carbon $end = null;
public function __construct()
{
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
$this->noBudgetRepository = app(NoBudgetRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->repository = app(BudgetRepositoryInterface::class);
}
#[Override]
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
$this->collectCurrencies();
$this->collectSpentInfo();
$this->appendCollectedData();
return $this->collection;
}
#[Override]
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
#[Override]
public function setUser(User $user): void
{
$this->user = $user;
$this->setUserGroup($user->userGroup);
}
#[Override]
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
$this->noBudgetRepository->setUserGroup($userGroup);
$this->opsRepository->setUserGroup($userGroup);
$this->repository->setUserGroup($userGroup);
}
private function collectIds(): void
{
/** @var AvailableBudget $availableBudget */
foreach ($this->collection as $availableBudget) {
$this->ids[] = (int)$availableBudget->id;
$this->currencyIds[(int)$availableBudget->id] = (int)$availableBudget->transaction_currency_id;
}
$this->ids = array_unique($this->ids);
}
private function collectSpentInfo(): void
{
$start = $this->collection->min('start_date');
$end = $this->collection->max('end_date');
$allActive = $this->repository->getActiveBudgets();
$spentInBudgets = $this->opsRepository->collectExpenses($start, $end, null, $allActive, null);
$spentOutsideBudgets = $this->noBudgetRepository->collectExpenses($start, $end, null, null, null);
foreach ($this->collection as $availableBudget) {
$id = (int)$availableBudget->id;
$currencyId = $this->currencyIds[$id];
$currency = $this->currencies[$currencyId];
$filteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, false);
$filteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, false);
$this->spentInBudgets[$id] = array_values($filteredSpentInBudgets);
$this->spentOutsideBudgets[$id] = array_values($filteredSpentOutsideBudgets);
if (true === $this->convertToPrimary) {
$pcFilteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, true);
$pcFilteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $currency, true);
$this->pcSpentInBudgets[$id] = array_values($pcFilteredSpentInBudgets);
$this->pcSpentOutsideBudgets[$id] = array_values($pcFilteredSpentOutsideBudgets);
}
// filter arrays on date.
// send them to sumCollection thing.
// save.
}
// first collect, then filter and append.
}
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (AvailableBudget $item) {
$id = (int)$item->id;
$currencyId = $this->currencyIds[$id];
$currency = $this->currencies[$currencyId];
$meta = [
'currency' => $currency,
'spent_in_budgets' => $this->spentInsideBudgets[$id] ?? [],
'pc_spent_in_budgets' => $this->pcSpentInBudgets[$id] ?? [],
'spent_outside_budgets' => $this->spentOutsideBudgets[$id] ?? [],
'pc_spent_outside_budgets' => $this->pcSpentOutsideBudgets[$id] ?? [],
];
$item->meta = $meta;
return $item;
});
}
private function collectCurrencies(): void
{
$ids = array_unique(array_values($this->currencyIds));
$set = TransactionCurrency::whereIn('id', $ids)->get();
foreach ($set as $currency) {
$this->currencies[(int)$currency->id] = $currency;
}
}
}

View File

@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class BudgetEnrichment implements EnrichmentInterface
{
private Collection $collection;
private bool $convertToPrimary = true;
private TransactionCurrency $primaryCurrency;
private User $user;
private UserGroup $userGroup;
private array $ids = [];
private array $notes = [];
private array $autoBudgets = [];
private array $currencies = [];
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $spent = [];
private array $pcSpent = [];
public function __construct()
{
$this->convertToPrimary = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
}
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
$this->collectNotes();
$this->collectAutoBudgets();
$this->collectExpenses();
$this->appendCollectedData();
return $this->collection;
}
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
public function setUser(User $user): void
{
$this->user = $user;
$this->setUserGroup($user->userGroup);
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
private function collectIds(): void
{
/** @var Budget $budget */
foreach ($this->collection as $budget) {
$this->ids[] = (int)$budget->id;
}
$this->ids = array_unique($this->ids);
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->ids)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Budget::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (Budget $item) {
$id = (int)$item->id;
$meta = [
'notes' => $this->notes[$id] ?? null,
'currency' => $this->currencies[$id] ?? null,
'auto_budget' => $this->autoBudgets[$id] ?? null,
'spent' => $this->spent[$id] ?? null,
'pc_spent' => $this->pcSpent[$id] ?? null,
];
$item->meta = $meta;
return $item;
});
}
private function collectAutoBudgets(): void
{
$set = AutoBudget::whereIn('budget_id', $this->ids)->with(['transactionCurrency'])->get();
/** @var AutoBudget $autoBudget */
foreach ($set as $autoBudget) {
$budgetId = (int)$autoBudget->budget_id;
$this->currencies[$budgetId] = $autoBudget->transactionCurrency;
$this->autoBudgets[$budgetId] = [
'type' => (int)$autoBudget->auto_budget_type,
'period' => $autoBudget->period,
'amount' => $autoBudget->amount,
'pc_amount' => $autoBudget->native_amount,
];
}
}
private function collectExpenses(): void
{
if (null !== $this->start && null !== $this->end) {
/** @var OperationsRepositoryInterface $opsRepository */
$opsRepository = app(OperationsRepositoryInterface::class);
$opsRepository->setUser($this->user);
$opsRepository->setUserGroup($this->userGroup);
// $spent = $this->beautify();
// $set = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$budget]))
$expenses = $opsRepository->collectExpenses($this->start, $this->end, null, $this->collection, null);
foreach ($this->collection as $item) {
$id = (int)$item->id;
$this->spent[$id] = array_values($opsRepository->sumCollectedExpensesByBudget($expenses, $item, false));
$this->pcSpent[$id] = array_values($opsRepository->sumCollectedExpensesByBudget($expenses, $item, true));
}
}
}
public function setEnd(?Carbon $end): void
{
$this->end = $end;
}
public function setStart(?Carbon $start): void
{
$this->start = $start;
}
}

View File

@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Budget\OperationsRepository;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class BudgetLimitEnrichment implements EnrichmentInterface
{
private User $user;
private UserGroup $userGroup;
private Collection $collection;
private array $ids = [];
private array $notes = [];
private Carbon $start;
private Carbon $end;
private Collection $budgets;
private array $expenses = [];
private array $pcExpenses = [];
private bool $convertToPrimary = true;
private TransactionCurrency $primaryCurrency;
public function __construct()
{
$this->convertToPrimary = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
}
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
$this->collectNotes();
$this->collectBudgets();
$this->appendCollectedData();
return $this->collection;
}
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
$collection = $this->enrich($collection);
return $collection->first();
}
public function setUser(User $user): void
{
$this->user = $user;
$this->userGroup = $user->userGroup;
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
private function collectIds(): void
{
$this->start = $this->collection->min('start_date');
$this->end = $this->collection->max('end_date');
/** @var BudgetLimit $limit */
foreach ($this->collection as $limit) {
$this->ids[] = (int)$limit->id;
}
$this->ids = array_unique($this->ids);
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->ids)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', BudgetLimit::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (BudgetLimit $item) {
$id = (int)$item->id;
$meta = [
'notes' => $this->notes[$id] ?? null,
'spent' => $this->expenses[$id] ?? [],
'pc_spent' => $this->pcExpenses[$id] ?? [],
];
$item->meta = $meta;
return $item;
});
}
private function collectBudgets(): void
{
$budgetIds = $this->collection->pluck('budget_id')->unique()->toArray();
$this->budgets = Budget::whereIn('id', $budgetIds)->get();
$repository = app(OperationsRepository::class);
$repository->setUser($this->user);
$expenses = $repository->collectExpenses($this->start, $this->end, null, $this->budgets, null);
/** @var BudgetLimit $budgetLimit */
foreach ($this->collection as $budgetLimit) {
$id = (int)$budgetLimit->id;
$filteredExpenses = $repository->sumCollectedExpenses($expenses, $budgetLimit->start_date, $budgetLimit->end_date, $budgetLimit->transactionCurrency, false);
$this->expenses[$id] = array_values($filteredExpenses);
if (true === $this->convertToPrimary && $budgetLimit->transactionCurrency->id !== $this->primaryCurrency->id) {
$pcFilteredExpenses = $repository->sumCollectedExpenses($expenses, $budgetLimit->start_date, $budgetLimit->end_date, $budgetLimit->transactionCurrency, true);
$this->pcExpenses[$id] = array_values($pcFilteredExpenses);
}
if (true === $this->convertToPrimary && $budgetLimit->transactionCurrency->id === $this->primaryCurrency->id) {
$this->pcExpenses[$id] = $this->expenses[$id] ?? [];
}
}
}
}

View File

@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Models\Category;
use FireflyIII\Models\Note;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Category\OperationsRepositoryInterface;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class CategoryEnrichment implements EnrichmentInterface
{
private Collection $collection;
private User $user;
private UserGroup $userGroup;
private array $ids = [];
private array $notes = [];
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $spent = [];
private array $pcSpent = [];
private array $earned = [];
private array $pcEarned = [];
private array $transfers = [];
private array $pcTransfers = [];
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
$this->collectNotes();
$this->collectTransactions();
$this->appendCollectedData();
return $collection;
}
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
public function setUser(User $user): void
{
$this->user = $user;
$this->setUserGroup($user->userGroup);
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
private function collectIds(): void
{
/** @var Category $category */
foreach ($this->collection as $category) {
$this->ids[] = (int)$category->id;
}
$this->ids = array_unique($this->ids);
}
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (Category $item) {
$id = (int)$item->id;
$meta = [
'notes' => $this->notes[$id] ?? null,
'spent' => $this->spent[$id] ?? null,
'pc_spent' => $this->pcSpent[$id] ?? null,
'earned' => $this->earned[$id] ?? null,
'pc_earned' => $this->pcEarned[$id] ?? null,
'transfers' => $this->transfers[$id] ?? null,
'pc_transfers' => $this->pcTransfers[$id] ?? null,
];
$item->meta = $meta;
return $item;
});
}
public function setEnd(?Carbon $end): void
{
$this->end = $end;
}
public function setStart(?Carbon $start): void
{
$this->start = $start;
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->ids)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Category::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectTransactions(): void
{
if (null !== $this->start && null !== $this->end) {
/** @var OperationsRepositoryInterface $opsRepository */
$opsRepository = app(OperationsRepositoryInterface::class);
$opsRepository->setUser($this->user);
$opsRepository->setUserGroup($this->userGroup);
$expenses = $opsRepository->collectExpenses($this->start, $this->end, null, $this->collection);
$income = $opsRepository->collectIncome($this->start, $this->end, null, $this->collection);
$transfers = $opsRepository->collectTransfers($this->start, $this->end, null, $this->collection);
foreach ($this->collection as $item) {
$id = (int)$item->id;
$this->spent[$id] = array_values($opsRepository->sumCollectedTransactionsByCategory($expenses, $item, 'negative', false));
$this->pcSpent[$id] = array_values($opsRepository->sumCollectedTransactionsByCategory($expenses, $item, 'negative', true));
$this->earned[$id] = array_values($opsRepository->sumCollectedTransactionsByCategory($income, $item, 'positive', false));
$this->pcEarned[$id] = array_values($opsRepository->sumCollectedTransactionsByCategory($income, $item, 'positive', true));
$this->transfers[$id] = array_values($opsRepository->sumCollectedTransactionsByCategory($transfers, $item, 'positive', false));
$this->pcTransfers[$id] = array_values($opsRepository->sumCollectedTransactionsByCategory($transfers, $item, 'positive', true));
}
}
}
}

View File

@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\Note;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class PiggyBankEnrichment implements EnrichmentInterface
{
private User $user;
private UserGroup $userGroup;
private Collection $collection;
private array $ids;
private array $currencyIds = [];
private array $currencies = [];
private array $accountIds = [];
private array $accountCurrencies = [];
private array $notes = [];
private array $mappedObjects = [];
private TransactionCurrency $primaryCurrency;
private array $amounts = [];
public function __construct()
{
$this->primaryCurrency = Amount::getPrimaryCurrency();
}
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
$this->collectObjectGroups();
$this->collectNotes();
$this->collectCurrentAmounts();
$this->appendCollectedData();
return $this->collection;
}
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
public function setUser(User $user): void
{
$this->user = $user;
$this->setUserGroup($user->userGroup);
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
private function collectIds(): void
{
/** @var PiggyBank $piggy */
foreach ($this->collection as $piggy) {
$id = (int)$piggy->id;
$this->ids[] = $id;
$this->currencyIds[$id] = (int)$piggy->transaction_currency_id;
}
$this->ids = array_unique($this->ids);
// collect currencies.
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->get();
foreach ($currencies as $currency) {
$this->currencies[(int)$currency->id] = $currency;
}
// collect accounts
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->ids)->get(['piggy_bank_id', 'account_id', 'current_amount', 'native_current_amount']);
foreach ($set as $item) {
$id = (int)$item->piggy_bank_id;
$accountId = (int)$item->account_id;
$this->amounts[$id] ??= [];
if (!array_key_exists($id, $this->accountIds)) {
$this->accountIds[$id] = (int)$item->account_id;
}
if (!array_key_exists($accountId, $this->amounts[$id])) {
$this->amounts[$id][$accountId] = [
'current_amount' => '0',
'pc_current_amount' => '0',
];
}
$this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], $item->current_amount);
$this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], $item->native_current_amount);
}
// get account currency preference for ALL.
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($set as $item) {
$accountId = (int)$item->account_id;
$currencyId = (int)$item->data;
if (!array_key_exists($currencyId, $this->currencies)) {
$this->currencies[$currencyId] = TransactionCurrency::find($currencyId);
}
$this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
}
// get account info.
$set = Account::whereIn('id', array_values($this->accountIds))->get();
/** @var Account $item */
foreach ($set as $item) {
$id = (int)$item->id;
$this->accounts[$id] = [
'id' => $id,
'name' => $item->name,
];
}
}
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (PiggyBank $item) {
$id = (int)$item->id;
$currencyId = (int)$item->transaction_currency_id;
$currency = $this->currencies[$currencyId] ?? $this->primaryCurrency;
$targetAmount = null;
if (0 !== bccomp($item->target_amount, '0')) {
$targetAmount = $item->target_amount;
}
$meta = [
'notes' => $this->notes[$id] ?? null,
'currency' => $this->currencies[$currencyId] ?? null,
// 'auto_budget' => $this->autoBudgets[$id] ?? null,
// 'spent' => $this->spent[$id] ?? null,
// 'pc_spent' => $this->pcSpent[$id] ?? null,
'object_group_id' => null,
'object_group_order' => null,
'object_group_title' => null,
'current_amount' => null,
'pc_current_amount' => null,
'target_amount' => null === $targetAmount ? null : Steam::bcround($targetAmount, $currency->decimal_places),
'pc_target_amount' => null === $item->native_target_amount ? null : Steam::bcround($item->native_target_amount, $this->primaryCurrency->decimal_places),
'left_to_save' => null,
'pc_left_to_save' => null,
'save_per_month' => null,
'pc_save_per_month' => null,
'accounts' => [],
];
// add object group if available
if (array_key_exists($id, $this->mappedObjects)) {
$key = $this->mappedObjects[$id];
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
}
// add current amount(s).
foreach ($this->amounts[$id] as $accountId => $row) {
$meta['accounts'][] = [
'account_id' => (string)$accountId,
'name' => $this->accounts[$accountId]['name'] ?? '',
'current_amount' => Steam::bcround($row['current_amount'], $currency->decimal_places),
'pc_current_amount' => Steam::bcround($row['pc_current_amount'], $this->primaryCurrency->decimal_places),
];
$meta['current_amount'] = bcadd($meta['current_amount'], $row['current_amount']);
// only add pc_current_amount when the pc_current_amount is set
$meta['pc_current_amount'] = null === $row['pc_current_amount'] ? null : bcadd($meta['pc_current_amount'], $row['pc_current_amount']);
}
$meta['current_amount'] = Steam::bcround($meta['current_amount'], $currency->decimal_places);
// only round this number when pc_current_amount is set.
$meta['pc_current_amount'] = null === $meta['pc_current_amount'] ? null : Steam::bcround($meta['pc_current_amount'], $this->primaryCurrency->decimal_places);
// calculate left to save, only when there is a target amount.
if (null !== $targetAmount) {
$meta['left_to_save'] = bcsub($meta['target_amount'], $meta['current_amount']);
$meta['pc_left_to_save'] = null === $meta['pc_target_amount'] ? null : bcsub($meta['pc_target_amount'], $meta['pc_current_amount']);
}
// get suggested per month.
$meta['save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['target_amount'], $meta['current_amount']), $currency->decimal_places);
$meta['pc_save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['pc_target_amount'], $meta['pc_current_amount']), $currency->decimal_places);
$item->meta = $meta;
return $item;
});
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->ids)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', PiggyBank::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void
{
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->ids)
->where('object_groupable_type', PiggyBank::class)
->get(['object_groupable_id', 'object_group_id'])
;
$ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
}
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
foreach ($groups as $group) {
$group['id'] = (int)$group['id'];
$group['order'] = (int)$group['order'];
$this->objectGroups[(int)$group['id']] = $group;
}
}
private function collectCurrentAmounts(): void {}
/**
* Returns the suggested amount the user should save per month, or "".
*/
private function getSuggestedMonthlyAmount(?Carbon $startDate, ?Carbon $targetDate, ?string $targetAmount, string $currentAmount): string
{
if (null === $targetAmount || null === $targetDate || null === $startDate) {
return '0';
}
$savePerMonth = '0';
if (1 === bccomp($targetAmount, $currentAmount)) {
$now = today(config('app.timezone'));
$diffInMonths = (int)$startDate->diffInMonths($targetDate);
$remainingAmount = bcsub($targetAmount, $currentAmount);
// more than 1 month to go and still need money to save:
if ($diffInMonths > 0 && 1 === bccomp($remainingAmount, '0')) {
$savePerMonth = bcdiv($remainingAmount, (string)$diffInMonths);
}
// less than 1 month to go but still need money to save:
if (0 === $diffInMonths && 1 === bccomp($remainingAmount, '0')) {
$savePerMonth = $remainingAmount;
}
}
return $savePerMonth;
}
}

View File

@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class PiggyBankEventEnrichment implements EnrichmentInterface
{
private User $user;
private UserGroup $userGroup;
private Collection $collection;
private array $ids = [];
private array $journalIds = [];
private array $groupIds = [];
private array $accountIds = [];
private array $piggybankIds = [];
private array $accountCurrencies = [];
private array $currencies = [];
// private bool $convertToPrimary = false;
// private TransactionCurrency $primaryCurrency;
public function __construct()
{
// $this->convertToPrimary = Amount::convertToPrimary();
// $this->primaryCurrency = Amount::getPrimaryCurrency();
}
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
$this->appendCollectedData();
return $this->collection;
}
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
public function setUser(User $user): void
{
$this->user = $user;
$this->setUserGroup($user->userGroup);
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
private function collectIds(): void
{
/** @var PiggyBankEvent $event */
foreach ($this->collection as $event) {
$this->ids[] = (int)$event->id;
$this->journalIds[(int)$event->id] = (int)$event->transaction_journal_id;
$this->piggybankIds[(int)$event->id] = (int)$event->piggy_bank_id;
}
$this->ids = array_unique($this->ids);
// collect groups with journal info.
$set = TransactionJournal::whereIn('id', $this->journalIds)->get(['id', 'transaction_group_id']);
/** @var TransactionJournal $item */
foreach ($set as $item) {
$this->groupIds[(int)$item->id] = (int)$item->transaction_group_id;
}
// collect account info.
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggybankIds)->get(['piggy_bank_id', 'account_id']);
foreach ($set as $item) {
$id = (int)$item->piggy_bank_id;
if (!array_key_exists($id, $this->accountIds)) {
$this->accountIds[$id] = (int)$item->account_id;
}
}
// get account currency preference for ALL.
// TODO This method does a find in a loop.
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($set as $item) {
$accountId = (int)$item->account_id;
$currencyId = (int)$item->data;
if (!array_key_exists($currencyId, $this->currencies)) {
$this->currencies[$currencyId] = TransactionCurrency::find($currencyId);
}
$this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
}
}
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (PiggyBankEvent $item) {
$id = (int)$item->id;
$piggyId = (int)$item->piggy_bank_id;
$journalId = (int)$item->transaction_journal_id;
$currency = null;
if (array_key_exists($piggyId, $this->accountIds)) {
$accountId = $this->accountIds[$piggyId];
if (array_key_exists($accountId, $this->accountCurrencies)) {
$currency = $this->accountCurrencies[$accountId];
}
}
$meta = [
'transaction_group_id' => array_key_exists($journalId, $this->groupIds) ? (string)$this->groupIds[$journalId] : null,
'currency' => $currency,
];
$item->meta = $meta;
return $item;
});
}
}

View File

@@ -0,0 +1,586 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Enums\RecurrenceRepetitionWeekend;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\CategoryFactory;
use FireflyIII\Models\Account;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Note;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Preference;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\RecurrenceRepetition;
use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\RecurrenceTransactionMeta;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class RecurringEnrichment implements EnrichmentInterface
{
private Collection $collection;
private array $ids = [];
private array $transactionTypeIds = [];
private array $transactionTypes = [];
private array $notes = [];
private array $repetitions = [];
private array $transactions = [];
private User $user;
private UserGroup $userGroup;
private string $language = 'en_US';
private array $currencyIds = [];
private array $foreignCurrencyIds = [];
private array $sourceAccountIds = [];
private array $destinationAccountIds = [];
private array $accounts = [];
private array $currencies = [];
private array $recurrenceIds = [];
private TransactionCurrency $primaryCurrency;
private bool $convertToPrimary = false;
public function __construct()
{
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
}
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
$this->collectRepetitions();
$this->collectTransactions();
$this->collectCurrencies();
$this->collectNotes();
$this->collectAccounts();
$this->collectTransactionMetaData();
$this->appendCollectedData();
return $this->collection;
}
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
public function setUser(User $user): void
{
$this->user = $user;
$this->setUserGroup($user->userGroup);
$this->getLanguage();
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
private function collectIds(): void
{
/** @var Recurrence $recurrence */
foreach ($this->collection as $recurrence) {
$id = (int)$recurrence->id;
$typeId = (int)$recurrence->transaction_type_id;
$this->ids[] = $id;
$this->transactionTypeIds[$id] = $typeId;
}
$this->ids = array_unique($this->ids);
// collect transaction types.
$transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get();
foreach ($transactionTypes as $transactionType) {
$id = (int)$transactionType->id;
$this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type);
}
}
private function collectRepetitions(): void
{
Log::debug('Start of enrichment: collectRepetitions()');
$repository = app(RecurringRepositoryInterface::class);
$repository->setUserGroup($this->userGroup);
$set = RecurrenceRepetition::whereIn('recurrence_id', $this->ids)->get();
/** @var RecurrenceRepetition $repetition */
foreach ($set as $repetition) {
$recurrence = $this->collection->filter(function (Recurrence $item) use ($repetition) {
return (int)$item->id === (int)$repetition->recurrence_id;
})->first();
$fromDate = $recurrence->latest_date ?? $recurrence->first_date;
$id = (int)$repetition->recurrence_id;
$repId = (int)$repetition->id;
$this->repetitions[$id] ??= [];
// get the (future) occurrences for this specific type of repetition:
$amount = 'daily' === $repetition->repetition_type ? 9 : 5;
$set = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount);
/** @var Carbon $carbon */
foreach ($set as $carbon) {
$occurrences[] = $carbon->toAtomString();
}
$this->repetitions[$id][$repId] = [
'id' => (string)$repId,
'created_at' => $repetition->created_at->toAtomString(),
'updated_at' => $repetition->updated_at->toAtomString(),
'type' => $repetition->repetition_type,
'moment' => (string)$repetition->moment,
'skip' => (int)$repetition->skip,
'weekend' => RecurrenceRepetitionWeekend::from((int)$repetition->weekend),
'description' => $this->getRepetitionDescription($repetition),
'occurrences' => $occurrences,
];
}
Log::debug('End of enrichment: collectRepetitions()');
}
private function collectTransactions(): void
{
$set = RecurrenceTransaction::whereIn('recurrence_id', $this->ids)->get();
/** @var RecurrenceTransaction $transaction */
foreach ($set as $transaction) {
$id = (int)$transaction->recurrence_id;
$transactionId = (int)$transaction->id;
$this->recurrenceIds[$transactionId] = $id;
$this->transactions[$id] ??= [];
$amount = $transaction->amount;
$foreignAmount = $transaction->foreign_amount;
$this->transactions[$id][$transactionId] = [
'id' => (string)$transactionId,
'recurrence_id' => $id,
'transaction_currency_id' => (int)$transaction->transaction_currency_id,
'foreign_currency_id' => null === $transaction->foreign_currency_id ? null : (int)$transaction->foreign_currency_id,
'source_id' => (int)$transaction->source_id,
'object_has_currency_setting' => true,
'destination_id' => (int)$transaction->destination_id,
'amount' => $amount,
'foreign_amount' => $foreignAmount,
'pc_amount' => null,
'pc_foreign_amount' => null,
'description' => $transaction->description,
'tags' => [],
'category_id' => null,
'category_name' => null,
'budget_id' => null,
'budget_name' => null,
'piggy_bank_id' => null,
'piggy_bank_name' => null,
'subscription_id' => null,
'subscription_name' => null,
];
// collect all kinds of meta data to be collected later.
$this->currencyIds[$transactionId] = (int)$transaction->transaction_currency_id;
$this->sourceAccountIds[$transactionId] = (int)$transaction->source_id;
$this->destinationAccountIds[$transactionId] = (int)$transaction->destination_id;
if (null !== $transaction->foreign_currency_id) {
$this->foreignCurrencyIds[$transactionId] = (int)$transaction->foreign_currency_id;
}
}
}
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (Recurrence $item) {
$id = (int)$item->id;
$meta = [
'notes' => $this->notes[$id] ?? null,
'repetitions' => array_values($this->repetitions[$id] ?? []),
'transactions' => $this->processTransactions(array_values($this->transactions[$id] ?? [])),
];
$item->meta = $meta;
return $item;
});
}
/**
* Parse the repetition in a string that is user readable.
* TODO duplicate with repository.
*/
public function getRepetitionDescription(RecurrenceRepetition $repetition): string
{
if ('daily' === $repetition->repetition_type) {
return (string)trans('firefly.recurring_daily', [], $this->language);
}
if ('weekly' === $repetition->repetition_type) {
$dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $this->language);
if ($repetition->repetition_skip > 0) {
return (string)trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $this->language);
}
return (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $this->language);
}
if ('monthly' === $repetition->repetition_type) {
if ($repetition->repetition_skip > 0) {
return (string)trans('firefly.recurring_monthly_skip', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip + 1], $this->language);
}
return (string)trans('firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1], $this->language);
}
if ('ndom' === $repetition->repetition_type) {
$parts = explode(',', $repetition->repetition_moment);
// first part is number of week, second is weekday.
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $this->language);
if ($repetition->repetition_skip > 0) {
return (string)trans('firefly.recurring_ndom_skip', ['skip' => $repetition->repetition_skip, 'weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $this->language);
}
return (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $this->language);
}
if ('yearly' === $repetition->repetition_type) {
$today = today(config('app.timezone'))->endOfYear();
$repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment);
if (!$repDate instanceof Carbon) {
$repDate = clone $today;
}
// $diffInYears = (int)$today->diffInYears($repDate, true);
// $repDate->addYears($diffInYears); // technically not necessary.
$string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
return (string)trans('firefly.recurring_yearly', ['date' => $string], $this->language);
}
return '';
}
private function getLanguage(): void
{
/** @var Preference $preference */
$preference = Preferences::getForUser($this->user, 'language', config('firefly.default_language', 'en_US'));
$language = $preference->data;
if (is_array($language)) {
$language = 'en_US';
}
$language = (string)$language;
$this->language = $language;
}
private function collectCurrencies(): void
{
$all = array_merge(array_unique($this->currencyIds), array_unique($this->foreignCurrencyIds));
$currencies = TransactionCurrency::whereIn('id', array_unique($all))->get();
foreach ($currencies as $currency) {
$id = (int)$currency->id;
$this->currencies[$id] = $currency;
}
}
private function processTransactions(array $transactions): array
{
$return = [];
$converter = new ExchangeRateConverter();
foreach ($transactions as $transaction) {
$currencyId = $transaction['transaction_currency_id'];
$pcAmount = null;
$pcForeignAmount = null;
// set the same amount in the primary currency, if both are the same anyway.
if (true === $this->convertToPrimary && $currencyId === (int)$this->primaryCurrency->id) {
$pcAmount = $transaction['amount'];
}
// convert the amount to the primary currency, if it is not the same.
if (true === $this->convertToPrimary && $currencyId !== (int)$this->primaryCurrency->id) {
$pcAmount = $converter->convert($this->currencies[$currencyId], $this->primaryCurrency, today(), $transaction['amount']);
}
if (null !== $transaction['foreign_amount']) {
$foreignCurrencyId = $transaction['foreign_currency_id'];
if ($foreignCurrencyId !== $this->primaryCurrency->id) {
$pcForeignAmount = $converter->convert($this->currencies[$foreignCurrencyId], $this->primaryCurrency, today(), $transaction['foreign_amount']);
}
}
$transaction['pc_amount'] = $pcAmount;
$transaction['pc_foreign_amount'] = $pcForeignAmount;
$sourceId = $transaction['source_id'];
$transaction['source_name'] = $this->accounts[$sourceId]->name;
$transaction['source_iban'] = $this->accounts[$sourceId]->iban;
$transaction['source_type'] = $this->accounts[$sourceId]->accountType->type;
$transaction['source_id'] = (string)$transaction['source_id'];
$destId = $transaction['destination_id'];
$transaction['destination_name'] = $this->accounts[$destId]->name;
$transaction['destination_iban'] = $this->accounts[$destId]->iban;
$transaction['destination_type'] = $this->accounts[$destId]->accountType->type;
$transaction['destination_id'] = (string)$transaction['destination_id'];
$transaction['currency_id'] = (string)$currencyId;
$transaction['currency_name'] = $this->currencies[$currencyId]->name;
$transaction['currency_code'] = $this->currencies[$currencyId]->code;
$transaction['currency_symbol'] = $this->currencies[$currencyId]->symbol;
$transaction['currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
$transaction['primary_currency_id'] = (string)$this->primaryCurrency->id;
$transaction['primary_currency_name'] = $this->primaryCurrency->name;
$transaction['primary_currency_code'] = $this->primaryCurrency->code;
$transaction['primary_currency_symbol'] = $this->primaryCurrency->symbol;
$transaction['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
// $transaction['foreign_currency_id'] = null;
$transaction['foreign_currency_name'] = null;
$transaction['foreign_currency_code'] = null;
$transaction['foreign_currency_symbol'] = null;
$transaction['foreign_currency_decimal_places'] = null;
if (null !== $transaction['foreign_currency_id']) {
$currencyId = $transaction['foreign_currency_id'];
$transaction['foreign_currency_id'] = (string)$currencyId;
$transaction['foreign_currency_name'] = $this->currencies[$currencyId]->name;
$transaction['foreign_currency_code'] = $this->currencies[$currencyId]->code;
$transaction['foreign_currency_symbol'] = $this->currencies[$currencyId]->symbol;
$transaction['foreign_currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
}
unset($transaction['transaction_currency_id']);
$return[] = $transaction;
}
return $return;
}
private function collectAccounts(): void
{
$all = array_merge(array_unique($this->sourceAccountIds), array_unique($this->destinationAccountIds));
$accounts = Account::with(['accountType'])->whereIn('id', array_unique($all))->get();
/** @var Account $account */
foreach ($accounts as $account) {
$id = (int)$account->id;
$this->accounts[$id] = $account;
}
}
private function collectTransactionMetaData(): void
{
$ids = array_keys($this->transactions);
$meta = RecurrenceTransactionMeta::whereIn('rt_id', $ids)->get();
// other meta-data to be collected:
$billIds = [];
$piggyBankIds = [];
$categoryIds = [];
$categoryNames = [];
$budgetIds = [];
foreach ($meta as $entry) {
$id = (int)$entry->id;
$transactionId = (int)$entry->rt_id;
$recurrenceId = $this->recurrenceIds[$transactionId];
$name = (string)$entry->name;
switch ($name) {
default:
throw new FireflyException(sprintf('Recurrence transformer cant handle field "%s"', $name));
case 'bill_id':
if ((int)$entry->value > 0) {
$this->transactions[$recurrenceId][$transactionId]['subscription_id'] = $entry->value;
if (!array_key_exists($id, $billIds)) {
$billIds[$id] = [
'recurrence_id' => $recurrenceId,
'transaction_id' => $transactionId,
'bill_id' => (int)$entry->value,
];
}
}
break;
case 'tags':
$this->transactions[$recurrenceId][$transactionId]['tags'] = json_decode((string)$entry->value);
break;
case 'piggy_bank_id':
if ((int)$entry->value > 0) {
$this->transactions[$recurrenceId][$transactionId]['piggy_bank_id'] = (string)$entry->value;
if (!array_key_exists($id, $piggyBankIds)) {
$piggyBankIds[$id] = [
'recurrence_id' => $recurrenceId,
'transaction_id' => $transactionId,
'piggy_bank_id' => (int)$entry->value,
];
}
}
break;
case 'category_id':
if ((int)$entry->value > 0) {
$this->transactions[$recurrenceId][$transactionId]['category_id'] = (string)$entry->value;
if (!array_key_exists($id, $categoryIds)) {
$categoryIds[$id] = [
'recurrence_id' => $recurrenceId,
'transaction_id' => $transactionId,
'category_id' => (int)$entry->value,
];
}
}
break;
case 'category_name':
if ('' !== (string)$entry->value) {
$this->transactions[$recurrenceId][$transactionId]['category_name'] = (string)$entry->value;
if (!array_key_exists($id, $categoryIds)) {
$categoryNames[$id] = [
'recurrence_id' => $recurrenceId,
'transaction_id' => $transactionId,
'category_name' => $entry->value,
];
}
}
break;
case 'budget_id':
if ((int)$entry->value > 0) {
$this->transactions[$recurrenceId][$transactionId]['budget_id'] = (string)$entry->value;
if (!array_key_exists($id, $budgetIds)) {
$budgetIds[$id] = [
'recurrence_id' => $recurrenceId,
'transaction_id' => $transactionId,
'budget_id' => (int)$entry->value,
];
}
}
break;
}
}
$this->collectBillInfo($billIds);
$this->collectPiggyBankInfo($piggyBankIds);
$this->collectCategoryIdInfo($categoryIds);
$this->collectCategoryNameInfo($categoryNames);
$this->collectBudgetInfo($budgetIds);
}
private function collectBillInfo(array $billIds): void
{
if (0 === count($billIds)) {
return;
}
$ids = Arr::pluck($billIds, 'bill_id');
$bills = Bill::whereIn('id', $ids)->get();
$mapped = [];
foreach ($bills as $bill) {
$mapped[(int)$bill->id] = $bill;
}
foreach ($billIds as $info) {
$recurrenceId = $info['recurrence_id'];
$transactionId = $info['transaction_id'];
$this->transactions[$recurrenceId][$transactionId]['subscription_name'] = $mapped[$info['bill_id']]->name ?? '';
}
}
private function collectPiggyBankInfo(array $piggyBankIds): void
{
if (0 === count($piggyBankIds)) {
return;
}
$ids = Arr::pluck($piggyBankIds, 'piggy_bank_id');
$piggyBanks = PiggyBank::whereIn('id', $ids)->get();
$mapped = [];
foreach ($piggyBanks as $piggyBank) {
$mapped[(int)$piggyBank->id] = $piggyBank;
}
foreach ($piggyBankIds as $info) {
$recurrenceId = $info['recurrence_id'];
$transactionId = $info['transaction_id'];
$this->transactions[$recurrenceId][$transactionId]['piggy_bank_name'] = $mapped[$info['piggy_bank_id']]->name ?? '';
}
}
private function collectCategoryIdInfo(array $categoryIds): void
{
if (0 === count($categoryIds)) {
return;
}
$ids = Arr::pluck($categoryIds, 'category_id');
$categories = Category::whereIn('id', $ids)->get();
$mapped = [];
foreach ($categories as $category) {
$mapped[(int)$category->id] = $category;
}
foreach ($categoryIds as $info) {
$recurrenceId = $info['recurrence_id'];
$transactionId = $info['transaction_id'];
$this->transactions[$recurrenceId][$transactionId]['category_name'] = $mapped[$info['category_id']]->name ?? '';
}
}
/**
* TODO This method does look-up in a loop.
*/
private function collectCategoryNameInfo(array $categoryNames): void
{
if (0 === count($categoryNames)) {
return;
}
$factory = app(CategoryFactory::class);
$factory->setUser($this->user);
foreach ($categoryNames as $info) {
$recurrenceId = $info['recurrence_id'];
$transactionId = $info['transaction_id'];
$category = $factory->findOrCreate(null, $info['category_name']);
if (null !== $category) {
$this->transactions[$recurrenceId][$transactionId]['category_id'] = (string)$category->id;
$this->transactions[$recurrenceId][$transactionId]['category_name'] = $category->name;
}
}
}
private function collectBudgetInfo(array $budgetIds): void
{
if (0 === count($budgetIds)) {
return;
}
$ids = Arr::pluck($budgetIds, 'budget_id');
$categories = Budget::whereIn('id', $ids)->get();
$mapped = [];
foreach ($categories as $category) {
$mapped[(int)$category->id] = $category;
}
foreach ($budgetIds as $info) {
$recurrenceId = $info['recurrence_id'];
$transactionId = $info['transaction_id'];
$this->transactions[$recurrenceId][$transactionId]['budget_name'] = $mapped[$info['budget_id']]->name ?? '';
}
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->ids)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Recurrence::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
}

View File

@@ -12,6 +12,7 @@ use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Models\BillDateCalculator;
@@ -38,6 +39,12 @@ class SubscriptionEnrichment implements EnrichmentInterface
private TransactionCurrency $primaryCurrency;
private BillDateCalculator $calculator;
public function __construct()
{
$this->convertToPrimary = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
}
public function enrich(Collection $collection): Collection
{
Log::debug(sprintf('%s(%s item(s))', __METHOD__, $collection->count()));
@@ -49,12 +56,14 @@ class SubscriptionEnrichment implements EnrichmentInterface
$this->collectPaidDates();
$this->collectPayDates();
// TODO clean me up.
$notes = $this->notes;
$objectGroups = $this->objectGroups;
$paidDates = $this->paidDates;
$payDates = $this->payDates;
$this->collection = $this->collection->map(function (Bill $item) use ($notes, $objectGroups, $paidDates, $payDates) {
$id = (int)$item->id;
$id = (int) $item->id;
$currency = $item->transactionCurrency;
$nem = $this->getNextExpectedMatch($payDates[$id] ?? []);
@@ -70,10 +79,23 @@ class SubscriptionEnrichment implements EnrichmentInterface
'nem_diff' => $this->getNextExpectedMatchDiff($nem, $payDates[$id] ?? []),
];
$amounts = [
'amount_min' => Steam::bcround($item->amount_min, $currency->decimal_places),
'amount_max' => Steam::bcround($item->amount_max, $currency->decimal_places),
'average' => Steam::bcround(bcdiv(bcadd($item->amount_min, $item->amount_max), '2'), $currency->decimal_places),
'amount_min' => Steam::bcround($item->amount_min, $currency->decimal_places),
'amount_max' => Steam::bcround($item->amount_max, $currency->decimal_places),
'average' => Steam::bcround(bcdiv(bcadd($item->amount_min, $item->amount_max), '2'), $currency->decimal_places),
'pc_amount_min' => null,
'pc_amount_max' => null,
'pc_average' => null,
];
if ($this->convertToPrimary && $currency->id === $this->primaryCurrency->id) {
$amounts['pc_amount_min'] = $amounts['amount_min'];
$amounts['pc_amount_max'] = $amounts['amount_max'];
$amounts['pc_average'] = $amounts['average'];
}
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
$amounts['pc_amount_min'] = Steam::bcround($item->native_amount_min, $this->primaryCurrency->decimal_places);
$amounts['pc_amount_max'] = Steam::bcround($item->native_amount_max, $this->primaryCurrency->decimal_places);
$amounts['pc_average'] = Steam::bcround(bcdiv(bcadd($item->native_amount_min, $item->native_amount_max), '2'), $this->primaryCurrency->decimal_places);
}
// add object group if available
if (array_key_exists($id, $this->mappedObjects)) {
@@ -88,16 +110,6 @@ class SubscriptionEnrichment implements EnrichmentInterface
$meta['notes'] = $notes[$item->id];
}
// Convert amounts to primary currency if needed
if ($this->convertToPrimary && $item->currency_id !== $this->primaryCurrency->id) {
Log::debug('Convert to primary currency');
$converter = new ExchangeRateConverter();
$amounts = [
'amount_min' => Steam::bcround($converter->convert($item->transactionCurrency, $this->primaryCurrency, today(), $item->amount_min), $this->primaryCurrency->decimal_places),
'amount_max' => Steam::bcround($converter->convert($item->transactionCurrency, $this->primaryCurrency, today(), $item->amount_max), $this->primaryCurrency->decimal_places),
];
$amounts['average'] = Steam::bcround(bcdiv(bcadd($amounts['amount_min'], $amounts['amount_max']), '2'), $this->primaryCurrency->decimal_places);
}
$item->amounts = $amounts;
$item->meta = $meta;
@@ -124,7 +136,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
@@ -140,21 +152,11 @@ class SubscriptionEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
public function setConvertToPrimary(bool $convertToPrimary): void
{
$this->convertToPrimary = $convertToPrimary;
}
public function setPrimaryCurrency(TransactionCurrency $primaryCurrency): void
{
$this->primaryCurrency = $primaryCurrency;
}
private function collectSubscriptionIds(): void
{
/** @var Bill $bill */
foreach ($this->collection as $bill) {
$this->subscriptionIds[] = (int)$bill->id;
$this->subscriptionIds[] = (int) $bill->id;
}
$this->subscriptionIds = array_unique($this->subscriptionIds);
}
@@ -170,14 +172,14 @@ class SubscriptionEnrichment implements EnrichmentInterface
$ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
$this->mappedObjects[(int) $entry->object_groupable_id] = (int) $entry->object_group_id;
}
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
foreach ($groups as $group) {
$group['id'] = (int)$group['id'];
$group['order'] = (int)$group['order'];
$this->objectGroups[(int)$group['id']] = $group;
$group['id'] = (int) $group['id'];
$group['order'] = (int) $group['order'];
$this->objectGroups[(int) $group['id']] = $group;
}
}
@@ -224,9 +226,11 @@ class SubscriptionEnrichment implements EnrichmentInterface
'transaction_journals.transaction_group_id',
'transactions.transaction_currency_id',
'currency.code AS transaction_currency_code',
'currency.symbol AS transaction_currency_symbol',
'currency.decimal_places AS transaction_currency_decimal_places',
'transactions.foreign_currency_id',
'foreign_currency.code AS foreign_currency_code',
'foreign_currency.symbol AS foreign_currency_symbol',
'foreign_currency.decimal_places AS foreign_currency_decimal_places',
'transactions.amount',
'transactions.foreign_amount',
@@ -252,30 +256,50 @@ class SubscriptionEnrichment implements EnrichmentInterface
});
foreach ($filtered as $entry) {
$array = [
'transaction_group_id' => (string)$entry->transaction_group_id,
'transaction_journal_id' => (string)$entry->id,
'date' => $entry->date->toAtomString(),
'date_object' => $entry->date,
'bill_id' => $entry->bill_id,
'currency_id' => $entry->transaction_currency_id,
'currency_code' => $entry->transaction_currency_code,
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
'transaction_group_id' => (string) $entry->transaction_group_id,
'transaction_journal_id' => (string) $entry->id,
'date' => $entry->date->toAtomString(),
'date_object' => $entry->date,
'subscription_id' => (string) $entry->bill_id,
'currency_id' => (string) $entry->transaction_currency_id,
'currency_code' => $entry->transaction_currency_code,
'currency_symbol' => $entry->transaction_currency_symbol,
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => $this->primaryCurrency->decimal_places,
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
'pc_amount' => null,
'foreign_amount' => null,
'pc_foreign_amount' => null,
];
if (null !== $entry->foreign_amount && null !== $entry->foreign_currency_code) {
$array['foreign_currency_id'] = $entry->foreign_currency_id;
$array['foreign_currency_id'] = (string) $entry->foreign_currency_id;
$array['foreign_currency_code'] = $entry->foreign_currency_code;
$array['foreign_currency_symbol'] = $entry->foreign_currency_symbol;
$array['foreign_currency_decimal_places'] = $entry->foreign_currency_decimal_places;
$array['foreign_amount'] = Steam::bcround($entry->foreign_amount, $entry->foreign_currency_decimal_places);
}
if ($this->convertToPrimary) {
$array['amount'] = $converter->convert($entry->transactionCurrency, $this->primaryCurrency, $entry->date, $entry->amount);
$array['currency_id'] = $this->primaryCurrency->id;
$array['currency_code'] = $this->primaryCurrency->code;
$array['currency_decimal_places'] = $this->primaryCurrency->decimal_places;
// convert to primary, but is already primary.
if ($this->convertToPrimary && (int) $entry->transaction_currency_id === $this->primaryCurrency->id) {
$array['pc_amount'] = $array['amount'];
}
// convert to primary, but is NOT already primary.
if ($this->convertToPrimary && (int) $entry->transaction_currency_id !== $this->primaryCurrency->id) {
$array['pc_amount'] = $converter->convert($entry->transactionCurrency, $this->primaryCurrency, $entry->date, $entry->amount);
}
// convert to primary, but foreign is already primary.
if ($this->convertToPrimary && (int) $entry->foreign_currency_id === $this->primaryCurrency->id) {
$array['pc_foreign_amount'] = $array['foreign_amount'];
}
// convert to primary, but foreign is NOT already primary.
if ($this->convertToPrimary && null !== $entry->foreign_currency_id && (int) $entry->foreign_currency_id !== $this->primaryCurrency->id) {
// TODO this is very database intensive.
$foreignCurrency = TransactionCurrency::find($entry->foreign_currency_id);
$array['pc_foreign_amount'] = $converter->convert($foreignCurrency, $this->primaryCurrency, $entry->date, $entry->amount);
}
$result[] = $array;
}
$this->paidDates[(int) $subscription->id] = $result;
@@ -352,7 +376,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
/** @var Bill $subscription */
foreach ($this->collection as $subscription) {
$id = (int)$subscription->id;
$id = (int) $subscription->id;
$lastPaidDate = $this->getLastPaidDate($paidDates[$id] ?? []);
$payDates = $this->calculator->getPayDates($this->start, $this->end, $subscription->date, $subscription->repeat_freq, $subscription->skip, $lastPaidDate);
$payDatesFormatted = [];

View File

@@ -565,11 +565,12 @@ class Navigation
public function preferredCarbonLocalizedFormat(Carbon $start, Carbon $end): string
{
$locale = app('steam')->getLocale();
if ($start->diffInMonths($end, true) > 1) {
$diff = $start->diffInMonths($end, true);
if ($diff >= 1.001) {
return (string) trans('config.month_js', [], $locale);
}
if ($start->diffInMonths($end, true) > 12) {
if ($diff >= 12.001) {
return (string) trans('config.year_js', [], $locale);
}
@@ -582,11 +583,12 @@ class Navigation
*/
public function preferredEndOfPeriod(Carbon $start, Carbon $end): string
{
if ((int) $start->diffInMonths($end, true) > 1 && (int) $start->diffInMonths($end, true) <= 12) {
$diff = $start->diffInMonths($end, true);
if ($diff >= 1.001) {
return 'endOfMonth';
}
if ((int) $start->diffInMonths($end, true) > 12) {
if ($diff >= 12.001) {
return 'endOfYear';
}
@@ -599,11 +601,12 @@ class Navigation
*/
public function preferredRangeFormat(Carbon $start, Carbon $end): string
{
if ((int) $start->diffInMonths($end, true) > 1 && (int) $start->diffInMonths($end, true) <= 12) {
$diff = $start->diffInMonths($end, true);
if ($diff >= 1.001) {
return '1M';
}
if ((int) $start->diffInMonths($end, true) > 12) {
if ($diff >= 12.001) {
return '1Y';
}
@@ -616,11 +619,12 @@ class Navigation
*/
public function preferredSqlFormat(Carbon $start, Carbon $end): string
{
if ((int) $start->diffInMonths($end, true) > 1 && (int) $start->diffInMonths($end, true) <= 12) {
$diff = $start->diffInMonths($end, true);
if ($diff >= 1.001) {
return '%Y-%m';
}
if ((int) $start->diffInMonths($end, true) > 12) {
if ($diff >= 12.001) {
return '%Y';
}

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Repositories\Recurring;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
/**
* Class CalculateXOccurrencesSince
@@ -37,7 +38,7 @@ trait CalculateXOccurrencesSince
*/
protected function getXDailyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$return = [];
$mutator = clone $date;
$total = 0;
@@ -62,7 +63,7 @@ trait CalculateXOccurrencesSince
*/
protected function getXMonthlyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
{
app('log')->debug(sprintf('Now in %s(%s, %s, %d)', __METHOD__, $date->format('Y-m-d'), $afterDate->format('Y-m-d'), $count));
Log::debug(sprintf('Now in %s(%s, %s, %d)', __METHOD__, $date->format('Y-m-d'), $afterDate->format('Y-m-d'), $count));
$return = [];
$mutator = clone $date;
$total = 0;
@@ -70,24 +71,25 @@ trait CalculateXOccurrencesSince
$dayOfMonth = (int) $moment;
$dayOfMonth = 0 === $dayOfMonth ? 1 : $dayOfMonth;
if ($mutator->day > $dayOfMonth) {
app('log')->debug(sprintf('%d is after %d, add a month. Mutator is now', $mutator->day, $dayOfMonth));
Log::debug(sprintf('%d is after %d, add a month. Mutator is now...', $mutator->day, $dayOfMonth));
// day has passed already, add a month.
$mutator->addMonth();
app('log')->debug(sprintf('%s', $mutator->format('Y-m-d')));
Log::debug(sprintf('%s', $mutator->toAtomString()));
}
while ($total < $count) {
$domCorrected = min($dayOfMonth, $mutator->daysInMonth);
$mutator->day = $domCorrected;
app('log')->debug(sprintf('Mutator is now %s', $mutator->format('Y-m-d')));
$mutator->setTime(0, 0, 0);
if (0 === $attempts % $skipMod && $mutator->gte($afterDate)) {
app('log')->debug('Is added to the list.');
Log::debug(sprintf('Mutator is now %s and is added to the list.', $mutator->toAtomString()));
$return[] = clone $mutator;
++$total;
}
++$attempts;
$mutator = $mutator->endOfMonth()->addDay();
}
Log::debug('Collected enough occurrences.');
return $return;
}
@@ -100,7 +102,7 @@ trait CalculateXOccurrencesSince
*/
protected function getXNDomOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$return = [];
$total = 0;
$attempts = 0;
@@ -134,7 +136,7 @@ trait CalculateXOccurrencesSince
*/
protected function getXWeeklyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$return = [];
$total = 0;
$attempts = 0;
@@ -173,7 +175,7 @@ trait CalculateXOccurrencesSince
*/
protected function getXYearlyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
{
app('log')->debug(sprintf('Now in %s(%s, %d, %d, %s)', __METHOD__, $date->format('Y-m-d'), $date->format('Y-m-d'), $count, $skipMod));
Log::debug(sprintf('Now in %s(%s, %d, %d, %s)', __METHOD__, $date->format('Y-m-d'), $date->format('Y-m-d'), $count, $skipMod));
$return = [];
$mutator = clone $date;
$total = 0;
@@ -181,19 +183,19 @@ trait CalculateXOccurrencesSince
$date = new Carbon($moment);
$date->year = $mutator->year;
if ($mutator > $date) {
app('log')->debug(
Log::debug(
sprintf('mutator (%s) > date (%s), so add a year to date (%s)', $mutator->format('Y-m-d'), $date->format('Y-m-d'), $date->format('Y-m-d'))
);
$date->addYear();
app('log')->debug(sprintf('Date is now %s', $date->format('Y-m-d')));
Log::debug(sprintf('Date is now %s', $date->format('Y-m-d')));
}
$obj = clone $date;
while ($total < $count) {
app('log')->debug(sprintf('total (%d) < count (%d) so go.', $total, $count));
app('log')->debug(sprintf('attempts (%d) %% skipmod (%d) === %d', $attempts, $skipMod, $attempts % $skipMod));
app('log')->debug(sprintf('Obj (%s) gte afterdate (%s)? %s', $obj->format('Y-m-d'), $afterDate->format('Y-m-d'), var_export($obj->gte($afterDate), true)));
Log::debug(sprintf('total (%d) < count (%d) so go.', $total, $count));
Log::debug(sprintf('attempts (%d) %% skipmod (%d) === %d', $attempts, $skipMod, $attempts % $skipMod));
Log::debug(sprintf('Obj (%s) gte afterdate (%s)? %s', $obj->format('Y-m-d'), $afterDate->format('Y-m-d'), var_export($obj->gte($afterDate), true)));
if (0 === $attempts % $skipMod && $obj->gte($afterDate)) {
app('log')->debug('All conditions true, add obj.');
Log::debug('All conditions true, add obj.');
$return[] = clone $obj;
++$total;
}

View File

@@ -24,8 +24,10 @@ declare(strict_types=1);
namespace FireflyIII\Support;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
@@ -34,11 +36,10 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Exception;
use ValueError;
use function Safe\preg_replace;
use function Safe\parse_url;
use function Safe\preg_replace;
/**
* Class Steam.
@@ -277,7 +278,7 @@ class Steam
$carbon = new Carbon($entry->date, $entry->date_tz);
$carbonKey = $carbon->format('Y-m-d');
// make sure sum is a string:
$sumOfDay = (string) ($entry->sum_of_day ?? '0');
$sumOfDay = (string)($entry->sum_of_day ?? '0');
// #10426 make sure sum is not in scientific notation.
$sumOfDay = $this->floatalize($sumOfDay);
@@ -292,20 +293,20 @@ class Steam
// add amount to current balance in currency code.
$currentBalance[$entryCurrency->code] ??= '0';
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string) $currentBalance[$entryCurrency->code]);
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string)$currentBalance[$entryCurrency->code]);
// if not requested to convert to primary currency, add the amount to "balance", do nothing else.
if (!$convertToPrimary) {
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
$currentBalance['balance'] = bcadd((string)$currentBalance['balance'], $sumOfDay);
}
// if convert to primary currency add the converted amount to "pc_balance".
// if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
if ($convertToPrimary) {
$pcSumOfDay = $converter->convert($entryCurrency, $primaryCurrency, $carbon, $sumOfDay);
$currentBalance['pc_balance'] = bcadd((string) ($currentBalance['pc_balance'] ?? '0'), $pcSumOfDay);
$currentBalance['pc_balance'] = bcadd((string)($currentBalance['pc_balance'] ?? '0'), $pcSumOfDay);
// if it's the same currency as the entry, also add to balance (see other code).
if ($currency->id === $entryCurrency->id) {
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
$currentBalance['balance'] = bcadd((string)$currentBalance['balance'], $sumOfDay);
}
}
// add to final array.
@@ -318,6 +319,72 @@ class Steam
return $balances;
}
public function finalAccountsBalanceOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
{
$result = [];
$convertToPrimary ??= Amount::convertToPrimary();
$primary ??= Amount::getPrimaryCurrency();
$currencies = $this->getCurrencies($accounts);
// balance(s) in all currencies for ALL accounts.
$array = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->get(['transactions.account_id', 'transaction_currencies.code', 'transactions.amount'])->toArray()
;
/** @var Account $account */
foreach ($accounts as $account) {
// filter array back to this account:
$filtered = array_filter($array, function ($item) use ($account) {
return (int)$item['account_id'] === $account->id;
});
$currency = $currencies[$account->id];
// this array is PER account, so we wait a bit before we change code here.
$return = [
'pc_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
];
// balance(s) in all currencies.
$others = $this->groupAndSumTransactions($filtered, 'code', 'amount');
// Log::debug('All balances are (joined)', $others);
// if there is no request to convert, take this as "balance" and "pc_balance".
$return['balance'] = $others[$currency->code] ?? '0';
if (!$convertToPrimary) {
unset($return['pc_balance']);
// Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
}
// if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
if ($convertToPrimary) {
$return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
// Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
}
// either way, the balance is always combined with the virtual balance:
$virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
if ($convertToPrimary) {
// the primary currency balance is combined with a converted virtual_balance:
$converter = new ExchangeRateConverter();
$pcVirtualBalance = $converter->convert($currency, $primary, $date, $virtualBalance);
$return['pc_balance'] = bcadd($pcVirtualBalance, $return['pc_balance']);
// Log::debug(sprintf('Primary virtual balance makes the primary total %s', $return['pc_balance']));
}
if (!$convertToPrimary) {
// if not, also increase the balance + primary balance for consistency.
$return['balance'] = bcadd($return['balance'], $virtualBalance);
// Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
}
$final = array_merge($return, $others);
$result[$account->id] = $final;
// Log::debug('Final balance is', $final);
}
return $result;
}
/**
* Returns smaller than or equal to, so be careful with END OF DAY.
*
@@ -340,7 +407,7 @@ class Steam
if ($cache->has()) {
Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
return $cache->get();
// return $cache->get();
}
// Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
if (null === $convertToPrimary) {
@@ -361,8 +428,8 @@ class Steam
$hasCurrency = null !== $accountCurrency;
$currency = $hasCurrency ? $accountCurrency : $primary;
$return = [
'pc_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
'pc_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
];
// balance(s) in all currencies.
$array = $account->transactions()
@@ -381,12 +448,12 @@ class Steam
}
// if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
if ($convertToPrimary) {
$return['primary_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
$return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
// Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
}
// either way, the balance is always combined with the virtual balance:
$virtualBalance = (string) ('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance);
$virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
if ($convertToPrimary) {
// the primary currency balance is combined with a converted virtual_balance:
@@ -421,7 +488,7 @@ class Steam
return null;
}
return TransactionCurrency::find((int) $result->data);
return TransactionCurrency::find((int)$result->data);
}
private function groupAndSumTransactions(array $array, string $group, string $field): array
@@ -430,7 +497,7 @@ class Steam
foreach ($array as $item) {
$groupKey = $item[$group] ?? 'unknown';
$return[$groupKey] = bcadd($return[$groupKey] ?? '0', (string) $item[$field]);
$return[$groupKey] = bcadd($return[$groupKey] ?? '0', (string)$item[$field]);
}
return $return;
@@ -478,11 +545,11 @@ class Steam
$hostName = $ipAddress;
}
if ('' !== (string) $hostName && $hostName !== $ipAddress) {
if ('' !== (string)$hostName && $hostName !== $ipAddress) {
$host = $hostName;
}
return (string) $host;
return (string)$host;
}
public function getLastActivities(array $accounts): array
@@ -497,9 +564,9 @@ class Steam
/** @var Transaction $entry */
foreach ($set as $entry) {
$date = new Carbon($entry->max_date, config('app.timezone'));
$date = new Carbon($entry->max_date, config('app.timezone'));
$date->setTimezone(config('app.timezone'));
$list[(int) $entry->account_id] = $date;
$list[(int)$entry->account_id] = $date;
}
return $list;
@@ -517,7 +584,7 @@ class Steam
if ('equal' === $locale) {
$locale = $this->getLanguage();
}
$locale = (string) $locale;
$locale = (string)$locale;
// Check for Windows to replace the locale correctly.
if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) {
@@ -617,20 +684,20 @@ class Steam
}
Log::debug(sprintf('Floatalizing %s', $value));
$number = substr($value, 0, (int) strpos($value, 'E'));
$number = substr($value, 0, (int)strpos($value, 'E'));
if (str_contains($number, '.')) {
$post = strlen(substr($number, (int) strpos($number, '.') + 1));
$mantis = substr($value, (int) strpos($value, 'E') + 1);
$post = strlen(substr($number, (int)strpos($number, '.') + 1));
$mantis = substr($value, (int)strpos($value, 'E') + 1);
if ($mantis < 0) {
$post += abs((int) $mantis);
$post += abs((int)$mantis);
}
// TODO careless float could break financial math.
return number_format((float) $value, $post, '.', '');
return number_format((float)$value, $post, '.', '');
}
// TODO careless float could break financial math.
return number_format((float) $value, 0, '.', '');
return number_format((float)$value, 0, '.', '');
}
public function opposite(?string $amount = null): ?string
@@ -650,24 +717,24 @@ class Steam
// has a K in it, remove the K and multiply by 1024.
$bytes = bcmul(rtrim($string, 'k'), '1024');
return (int) $bytes;
return (int)$bytes;
}
if (false !== stripos($string, 'm')) {
// has a M in it, remove the M and multiply by 1048576.
$bytes = bcmul(rtrim($string, 'm'), '1048576');
return (int) $bytes;
return (int)$bytes;
}
if (false !== stripos($string, 'g')) {
// has a G in it, remove the G and multiply by (1024)^3.
$bytes = bcmul(rtrim($string, 'g'), '1073741824');
return (int) $bytes;
return (int)$bytes;
}
return (int) $string;
return (int)$string;
}
public function positive(string $amount): string
@@ -689,4 +756,44 @@ class Steam
return $amount;
}
private function getCurrencies(Collection $accounts): array
{
$currencies = [];
$accountCurrencies = [];
$accountPreferences = [];
$primary = Amount::getPrimaryCurrency();
$ids = $accounts->pluck('id')->toArray();
$result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($result as $item) {
$accountPreferences[(int)$item->account_id] = (int)$item->data;
}
// collect those currencies.
$set = TransactionCurrency::whereIn('id', $accountPreferences)->get();
foreach ($set as $item) {
$currencies[$item->id] = $item;
}
/** @var Account $account */
foreach ($accounts as $account) {
$accountId = $account->id;
$currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
if ($currencyPresent) {
$currencyId = $account->meta['currency']->id;
$currencies[$currencyId] ??= $account->meta['currency'];
$accountCurrencies[$accountId] = $account->meta['currency'];
}
if (!$currencyPresent && !array_key_exists($account->id, $accountPreferences)) {
$accountCurrencies[$accountId] = $primary;
}
if (!$currencyPresent && array_key_exists($account->id, $accountPreferences)) {
$accountCurrencies[$account->id] = $currencies[$accountPreferences[$account->id]];
}
}
return $accountCurrencies;
}
}

View File

@@ -30,9 +30,6 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
@@ -63,103 +60,46 @@ class AccountTransformer extends AbstractTransformer
public function transform(Account $account): array
{
if (null === $account->meta) {
$account->meta = [];
$account->meta = [
'currency' => null,
];
}
// get account type:
$accountType = (string)config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
$liabilityType = (string)config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
$liabilityDirection = $account->meta['liability_direction'] ?? null;
// get account role (will only work if the type is asset).
$accountRole = $this->getAccountRole($account, $accountType);
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
$liabilityDirection = $account->meta['liability_direction'] ?? null;
$accountRole = $this->getAccountRole($account, $accountType);
$hasCurrencySettings = null !== $account->meta['currency'];
$includeNetWorth = 1 === (int) ($account->meta['include_net_worth'] ?? 0);
$longitude = $account->meta['location']['longitude'] ?? null;
$latitude = $account->meta['location']['latitude'] ?? null;
$zoomLevel = $account->meta['location']['zoom_level'] ?? null;
$order = $account->order;
// date (for balance etc.)
$date = $this->getDate();
$date = $this->getDate();
$date->endOfDay();
[$creditCardType, $monthlyPaymentDate] = $this->getCCInfo($account, $accountRole, $accountType);
[$openingBalance, $pcOpeningBalance, $openingBalanceDate] = $this->getOpeningBalance($account, $accountType);
[$interest, $interestPeriod] = $this->getInterest($account, $accountType);
$primary = $this->primary;
if (!$this->convertToPrimary) {
// reset primary currency to NULL, not interesting.
$primary = null;
// get primary currency as fallback:
$currency = $this->primary; // assume primary currency
if ($hasCurrencySettings) {
$currency = $account->meta['currency'];
}
$decimalPlaces = (int)$account->meta['currency']?->decimal_places;
$decimalPlaces = 0 === $decimalPlaces ? 2 : $decimalPlaces;
$openingBalanceRounded = Steam::bcround($openingBalance, $decimalPlaces);
$includeNetWorth = 1 === (int)($account->meta['include_net_worth'] ?? 0);
$longitude = $account->meta['location']['longitude'] ?? null;
$latitude = $account->meta['location']['latitude'] ?? null;
$zoomLevel = $account->meta['location']['zoom_level'] ?? null;
// no order for some accounts:
$order = $account->order;
if (!in_array(strtolower($accountType), ['liability', 'liabilities', 'asset'], true)) {
$order = null;
}
Log::debug(sprintf('transform: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
$finalBalance = Steam::finalAccountBalance($account, $date, $this->primary, $this->convertToPrimary);
if ($this->convertToPrimary) {
$finalBalance['balance'] = $finalBalance[$account->meta['currency']?->code] ?? '0';
}
$currentBalance = Steam::bcround($finalBalance['balance'] ?? '0', $decimalPlaces);
$pcCurrentBalance = $this->convertToPrimary ? Steam::bcround($finalBalance['pc_balance'] ?? '0', $primary->decimal_places) : null;
// set up balances array:
$balances = [];
$balances[]
= [
'type' => 'current',
'amount' => $currentBalance,
'currency_id' => $account->meta['currency_id'] ?? null,
'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'date' => $date->toAtomString(),
];
if (null !== $pcCurrentBalance) {
$balances[] = [
'type' => 'pc_current',
'amount' => $pcCurrentBalance,
'currency_id' => $primary instanceof TransactionCurrency ? (string)$primary->id : null,
'currency_code' => $primary?->code,
'currency_symbol' => $primary?->symbol,
'ccurrency_decimal_places' => $primary?->decimal_places,
'date' => $date->toAtomString(),
];
}
if (null !== $openingBalance) {
$balances[] = [
'type' => 'opening',
'amount' => $openingBalanceRounded,
'currency_id' => $account->meta['currency_id'] ?? null,
'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'date' => $openingBalanceDate,
];
}
if (null !== $account->virtual_balance) {
$balances[] = [
'type' => 'virtual',
'amount' => Steam::bcround($account->virtual_balance, $decimalPlaces),
'currency_id' => $account->meta['currency_id'] ?? null,
'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'date' => $date->toAtomString(),
];
}
// get some listed information from the account meta-data:
[$creditCardType, $monthlyPaymentDate] = $this->getCCInfo($account, $accountRole, $accountType);
$openingBalanceDate = $this->getOpeningBalance($account, $accountType);
[$interest, $interestPeriod] = $this->getInterest($account, $accountType);
return [
'id' => (string)$account->id,
'id' => (string) $account->id,
'created_at' => $account->created_at->toAtomString(),
'updated_at' => $account->updated_at->toAtomString(),
'active' => $account->active,
@@ -167,39 +107,55 @@ class AccountTransformer extends AbstractTransformer
'name' => $account->name,
'type' => strtolower($accountType),
'account_role' => $accountRole,
'currency_id' => $account->meta['currency_id'] ?? null,
'currency_code' => $account->meta['currency']?->code,
'currency_symbol' => $account->meta['currency']?->symbol,
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
'primary_currency_id' => $primary instanceof TransactionCurrency ? (string)$primary->id : null,
'primary_currency_code' => $primary?->code,
'primary_currency_symbol' => $primary?->symbol,
'primary_currency_decimal_places' => $primary?->decimal_places,
'current_balance' => $currentBalance,
'pc_current_balance' => $pcCurrentBalance,
// TODO object group
// currency information, structured for 6.3.0.
'object_has_currency_setting' => $hasCurrencySettings,
// currency is object specific or primary, already determined above.
'currency_id' => (string) $currency['id'],
'currency_name' => $currency['name'],
'currency_code' => $currency['code'],
'currency_symbol' => $currency['symbol'],
'currency_decimal_places' => $currency['decimal_places'],
'primary_currency_id' => (string) $this->primary->id,
'primary_currency_name' => $this->primary->name,
'primary_currency_code' => $this->primary->code,
'primary_currency_symbol' => $this->primary->symbol,
'primary_currency_decimal_places' => $this->primary->decimal_places,
// balances, structured for 6.3.0.
'current_balance' => $account->meta['balances']['current_balance'],
'pc_current_balance' => $account->meta['balances']['pc_current_balance'],
'opening_balance' => $account->meta['balances']['opening_balance'],
'pc_opening_balance' => $account->meta['balances']['pc_opening_balance'],
'virtual_balance' => $account->meta['balances']['virtual_balance'],
'pc_virtual_balance' => $account->meta['balances']['pc_virtual_balance'],
'debt_amount' => $account->meta['balances']['debt_amount'],
'pc_debt_amount' => $account->meta['balances']['pc_debt_amount'],
'current_balance_date' => $date->toAtomString(),
'notes' => $account->meta['notes'] ?? null,
'monthly_payment_date' => $monthlyPaymentDate,
'credit_card_type' => $creditCardType,
'account_number' => $account->meta['account_number'] ?? null,
'account_number' => $account->meta['account_number'],
'iban' => '' === $account->iban ? null : $account->iban,
'bic' => $account->meta['BIC'] ?? null,
'virtual_balance' => Steam::bcround($account->virtual_balance, $decimalPlaces),
'pc_virtual_balance' => $this->convertToPrimary ? Steam::bcround($account->native_virtual_balance, $primary->decimal_places) : null,
'opening_balance' => $openingBalanceRounded,
'pc_opening_balance' => $pcOpeningBalance,
'opening_balance_date' => $openingBalanceDate,
'liability_type' => $liabilityType,
'liability_direction' => $liabilityDirection,
'interest' => $interest,
'interest_period' => $interestPeriod,
'current_debt' => $account->meta['current_debt'] ?? null,
'include_net_worth' => $includeNetWorth,
'longitude' => $longitude,
'latitude' => $latitude,
'zoom_level' => $zoomLevel,
'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null,
'balances' => $balances,
'last_activity' => $account->meta['last_activity']?->toAtomString(),
'links' => [
[
'rel' => 'self',
@@ -212,7 +168,7 @@ class AccountTransformer extends AbstractTransformer
private function getAccountRole(Account $account, string $accountType): ?string
{
$accountRole = $account->meta['account_role'] ?? null;
if ('asset' !== $accountType || '' === (string)$accountRole) {
if ('asset' !== $accountType || '' === (string) $accountRole) {
return null;
}
@@ -248,7 +204,7 @@ class AccountTransformer extends AbstractTransformer
}
$monthlyPaymentDate = $object->toAtomString();
}
if (10 !== strlen((string)$monthlyPaymentDate)) {
if (10 !== strlen((string) $monthlyPaymentDate)) {
$monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString();
}
}
@@ -256,15 +212,10 @@ class AccountTransformer extends AbstractTransformer
return [$creditCardType, $monthlyPaymentDate];
}
private function getOpeningBalance(Account $account, string $accountType): array
private function getOpeningBalance(Account $account, string $accountType): ?string
{
$openingBalance = null;
$openingBalanceDate = null;
$pcOpeningBalance = null;
if (in_array($accountType, ['asset', 'liabilities'], true)) {
// grab from meta.
$openingBalance = $account->meta['opening_balance_amount'] ?? null;
$pcOpeningBalance = null;
$openingBalanceDate = $account->meta['opening_balance_date'] ?? null;
}
if (null !== $openingBalanceDate) {
@@ -274,15 +225,9 @@ class AccountTransformer extends AbstractTransformer
}
$openingBalanceDate = $object->toAtomString();
// NOW do conversion.
if ($this->convertToPrimary && null !== $account->meta['currency']) {
$converter = new ExchangeRateConverter();
$pcOpeningBalance = $converter->convert($account->meta['currency'], $this->primary, $object, $openingBalance);
}
}
return [$openingBalance, $pcOpeningBalance, $openingBalanceDate];
return $openingBalanceDate;
}
private function getInterest(Account $account, string $accountType): array

View File

@@ -26,32 +26,24 @@ namespace FireflyIII\Transformers;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
/**
* Class AvailableBudgetTransformer
*/
class AvailableBudgetTransformer extends AbstractTransformer
{
private readonly bool $convertToPrimary;
private readonly TransactionCurrency $primary;
private readonly NoBudgetRepositoryInterface $noBudgetRepository;
private readonly OperationsRepositoryInterface $opsRepository;
private readonly BudgetRepositoryInterface $repository;
private readonly bool $convertToPrimary;
private readonly TransactionCurrency $primary;
/**
* CurrencyTransformer constructor.
*/
public function __construct()
{
$this->repository = app(BudgetRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->noBudgetRepository = app(NoBudgetRepositoryInterface::class);
$this->primary = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
$this->primary = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
}
/**
@@ -59,31 +51,41 @@ class AvailableBudgetTransformer extends AbstractTransformer
*/
public function transform(AvailableBudget $availableBudget): array
{
$this->repository->setUser($availableBudget->user);
$currency = $availableBudget->meta['currency'];
$amount = Steam::bcround($availableBudget->amount, $currency->decimal_places);
$pcAmount = null;
$currency = $availableBudget->transactionCurrency;
$primary = $this->primary;
if (!$this->convertToPrimary) {
$primary = null;
if ($this->convertToPrimary) {
$pcAmount = Steam::bcround($availableBudget->native_amount, $this->primary->decimal_places);
}
$data = [
'id' => (string)$availableBudget->id,
return [
'id' => (string) $availableBudget->id,
'created_at' => $availableBudget->created_at->toAtomString(),
'updated_at' => $availableBudget->updated_at->toAtomString(),
'currency_id' => (string)$currency->id,
// currencies according to 6.3.0
'object_has_currency_setting' => true,
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'primary_currency_id' => $primary instanceof TransactionCurrency ? (string)$primary->id : null,
'primary_currency_code' => $primary?->code,
'primary_currency_symbol' => $primary?->symbol,
'primary_currency_decimal_places' => $primary?->decimal_places,
'amount' => app('steam')->bcround($availableBudget->amount, $currency->decimal_places),
'pc_amount' => $this->convertToPrimary ? app('steam')->bcround($availableBudget->native_amount, $currency->decimal_places) : null,
'primary_currency_id' => (string) $this->primary->id,
'primary_currency_name' => $this->primary->name,
'primary_currency_code' => $this->primary->code,
'primary_currency_symbol' => $this->primary->symbol,
'primary_currency_decimal_places' => $this->primary->decimal_places,
'amount' => $amount,
'pc_amount' => $pcAmount,
'start' => $availableBudget->start_date->toAtomString(),
'end' => $availableBudget->end_date->endOfDay()->toAtomString(),
'spent_in_budgets' => [],
'spent_no_budget' => [],
'spent_in_budgets' => $availableBudget->meta['spent_in_budgets'],
'pc_spent_in_budgets' => $availableBudget->meta['pc_spent_in_budgets'],
'spent_outside_budgets' => $availableBudget->meta['spent_outside_budgets'],
'pc_spent_outside_budgets' => $availableBudget->meta['pc_spent_outside_budgets'],
'links' => [
[
'rel' => 'self',
@@ -91,28 +93,5 @@ class AvailableBudgetTransformer extends AbstractTransformer
],
],
];
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
if (null !== $start && null !== $end) {
$data['spent_in_budgets'] = $this->getSpentInBudgets();
$data['spent_no_budget'] = $this->spentOutsideBudgets();
}
return $data;
}
private function getSpentInBudgets(): array
{
$allActive = $this->repository->getActiveBudgets();
$sums = $this->opsRepository->sumExpenses($this->parameters->get('start'), $this->parameters->get('end'), null, $allActive);
return array_values($sums);
}
private function spentOutsideBudgets(): array
{
$sums = $this->noBudgetRepository->sumExpenses($this->parameters->get('start'), $this->parameters->get('end'));
return array_values($sums);
}
}

View File

@@ -55,20 +55,32 @@ class BillTransformer extends AbstractTransformer
'id' => $bill->id,
'created_at' => $bill->created_at->toAtomString(),
'updated_at' => $bill->updated_at->toAtomString(),
'currency_id' => (string)$bill->transaction_currency_id,
'name' => $bill->name,
// currencies according to 6.3.0
'object_has_currency_setting' => true,
'currency_id' => (string) $bill->transaction_currency_id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'primary_currency_id' => (string)$this->primary->id,
'primary_currency_id' => (string) $this->primary->id,
'primary_currency_name' => $this->primary->name,
'primary_currency_code' => $this->primary->code,
'primary_currency_symbol' => $this->primary->symbol,
'primary_currency_decimal_places' => $this->primary->decimal_places,
'name' => $bill->name,
// amounts according to 6.3.0
'amount_min' => $bill->amounts['amount_min'],
'pc_amount_min' => $bill->amounts['pc_amount_min'],
'amount_max' => $bill->amounts['amount_max'],
'pc_amount_max' => $bill->amounts['pc_amount_max'],
'amount_avg' => $bill->amounts['average'],
'pc_amount_avg' => $bill->amounts['pc_average'],
'date' => $bill->date->toAtomString(),
'end_date' => $bill->end_date?->toAtomString(),
'extension_date' => $bill->extension_date?->toAtomString(),
@@ -87,10 +99,6 @@ class BillTransformer extends AbstractTransformer
'next_expected_match' => $bill->meta['nem']?->toAtomString(),
'next_expected_match_diff' => $bill->meta['nem_diff'],
// these fields need work:
// 'next_expected_match' => $nem,
// 'next_expected_match_diff' => $nemDiff,
// 'pay_dates' => $payDatesFormatted,
'links' => [
[
'rel' => 'self',

View File

@@ -26,10 +26,8 @@ namespace FireflyIII\Transformers;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepository;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Support\Collection;
use FireflyIII\Support\Facades\Steam;
use League\Fractal\Resource\Item;
/**
@@ -42,11 +40,11 @@ class BudgetLimitTransformer extends AbstractTransformer
'budget',
];
protected bool $convertToPrimary;
protected TransactionCurrency $primary;
protected TransactionCurrency $primaryCurrency;
public function __construct()
{
$this->primary = Amount::getPrimaryCurrency();
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
}
@@ -65,39 +63,19 @@ class BudgetLimitTransformer extends AbstractTransformer
*/
public function transform(BudgetLimit $budgetLimit): array
{
$repository = app(OperationsRepository::class);
$limitRepos = app(BudgetLimitRepositoryInterface::class);
$repository->setUser($budgetLimit->budget->user);
$limitRepos->setUser($budgetLimit->budget->user);
$expenses = $repository->sumExpenses(
$budgetLimit->start_date,
$budgetLimit->end_date,
null,
new Collection([$budgetLimit->budget]),
$budgetLimit->transactionCurrency
);
$currency = $budgetLimit->transactionCurrency;
$amount = $budgetLimit->amount;
$notes = $limitRepos->getNoteText($budgetLimit);
$currencyDecimalPlaces = 2;
$currencyId = null;
$currencyName = null;
$currencyCode = null;
$currencySymbol = null;
if (null !== $currency) {
$amount = $budgetLimit->amount;
$currencyId = $currency->id;
$currencyName = $currency->name;
$currencyCode = $currency->code;
$currencySymbol = $currency->symbol;
$currencyDecimalPlaces = $currency->decimal_places;
}
$amount = app('steam')->bcround($amount, $currencyDecimalPlaces);
$primary = $this->primary;
if (!$this->convertToPrimary) {
$primary = null;
}
$currency = $budgetLimit->transactionCurrency;
if (null === $currency) {
$currency = $this->primaryCurrency;
}
$amount = Steam::bcround($budgetLimit->amount, $currency->decimal_places);
$pcAmount = null;
if ($this->convertToPrimary && $currency->id === $this->primaryCurrency->id) {
$pcAmount = $amount;
}
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
$pcAmount = Steam::bcround($budgetLimit->native_amount, $this->primaryCurrency->decimal_places);
}
return [
'id' => (string)$budgetLimit->id,
@@ -106,20 +84,28 @@ class BudgetLimitTransformer extends AbstractTransformer
'start' => $budgetLimit->start_date->toAtomString(),
'end' => $budgetLimit->end_date->endOfDay()->toAtomString(),
'budget_id' => (string)$budgetLimit->budget_id,
'currency_id' => (string)$currencyId,
'currency_code' => $currencyCode,
'currency_name' => $currencyName,
'currency_decimal_places' => $currencyDecimalPlaces,
'currency_symbol' => $currencySymbol,
'primary_currency_id' => $primary instanceof TransactionCurrency ? (string)$primary->id : null,
'primary_currency_code' => $primary?->code,
'primary_currency_symbol' => $primary?->symbol,
'primary_currency_decimal_places' => $primary?->decimal_places,
// currency settings according to 6.3.0
'object_has_currency_setting' => true,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'primary_currency_id' => (int)$this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => $this->primaryCurrency->decimal_places,
'amount' => $amount,
'pc_amount' => $this->convertToPrimary ? app('steam')->bcround($budgetLimit->native_amount, $primary->decimal_places) : null,
'pc_amount' => $pcAmount,
'period' => $budgetLimit->period,
'spent' => $expenses[$currencyId]['sum'] ?? '0', // will be in primary currency if convertToPrimary.
'notes' => '' === $notes ? null : $notes,
'spent' => $budgetLimit->meta['spent'],
'pc_spent' => $budgetLimit->meta['pc_spent'],
'notes' => $budgetLimit->meta['notes'],
'links' => [
[
'rel' => 'self',

View File

@@ -27,10 +27,8 @@ namespace FireflyIII\Transformers;
use FireflyIII\Enums\AutoBudgetType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Support\Collection;
use FireflyIII\Support\Facades\Steam;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
@@ -38,21 +36,23 @@ use Symfony\Component\HttpFoundation\ParameterBag;
*/
class BudgetTransformer extends AbstractTransformer
{
private readonly bool $convertToPrimary;
private readonly TransactionCurrency $primary;
private readonly OperationsRepositoryInterface $opsRepository;
private readonly BudgetRepositoryInterface $repository;
private readonly bool $convertToPrimary;
private readonly TransactionCurrency $primaryCurrency;
private array $types;
/**
* BudgetTransformer constructor.
*/
public function __construct()
{
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->repository = app(BudgetRepositoryInterface::class);
$this->parameters = new ParameterBag();
$this->primary = Amount::getPrimaryCurrency();
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
$this->types = [
AutoBudgetType::AUTO_BUDGET_RESET->value => 'reset',
AutoBudgetType::AUTO_BUDGET_ROLLOVER->value => 'rollover',
AutoBudgetType::AUTO_BUDGET_ADJUSTED->value => 'adjusted',
];
}
/**
@@ -60,69 +60,53 @@ class BudgetTransformer extends AbstractTransformer
*/
public function transform(Budget $budget): array
{
$this->opsRepository->setUser($budget->user);
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
$autoBudget = $this->repository->getAutoBudget($budget);
$spent = [];
if (null !== $start && null !== $end) {
$spent = $this->beautify($this->opsRepository->sumExpenses($start, $end, null, new Collection([$budget])));
}
// info for auto budget.
$abType = null;
$abAmount = null;
$abPrimary = null;
$abPeriod = null;
$notes = $this->repository->getNoteText($budget);
$abType = null;
$abAmount = null;
$abPrimary = null;
$abPeriod = null;
$types = [
AutoBudgetType::AUTO_BUDGET_RESET->value => 'reset',
AutoBudgetType::AUTO_BUDGET_ROLLOVER->value => 'rollover',
AutoBudgetType::AUTO_BUDGET_ADJUSTED->value => 'adjusted',
];
$currency = $autoBudget?->transactionCurrency;
$primary = $this->primary;
if (!$this->convertToPrimary) {
$primary = null;
}
if (null === $autoBudget) {
$currency = $primary;
}
if (null !== $autoBudget) {
$abType = $types[$autoBudget->auto_budget_type];
$abAmount = app('steam')->bcround($autoBudget->amount, $currency->decimal_places);
$abPrimary = $this->convertToPrimary ? app('steam')->bcround($autoBudget->native_amount, $primary->decimal_places) : null;
$abPeriod = $autoBudget->period;
$currency = $budget->meta['currency'] ?? null;
if (null !== $budget->meta['auto_budget']) {
$abType = $this->types[$budget->meta['auto_budget']['type']];
$abAmount = Steam::bcround($budget->meta['auto_budget']['amount'], $currency->decimal_places);
$abPrimary = $this->convertToPrimary ? Steam::bcround($budget->meta['auto_budget']['pc_amount'], $this->primaryCurrency->decimal_places) : null;
$abPeriod = $budget->meta['auto_budget']['period'];
}
return [
'id' => (string) $budget->id,
'id' => (string)$budget->id,
'created_at' => $budget->created_at->toAtomString(),
'updated_at' => $budget->updated_at->toAtomString(),
'active' => $budget->active,
'name' => $budget->name,
'order' => $budget->order,
'notes' => $notes,
'notes' => $budget->meta['notes'],
'auto_budget_type' => $abType,
'auto_budget_period' => $abPeriod,
'currency_id' => null === $autoBudget ? null : (string) $autoBudget->transactionCurrency->id,
'currency_code' => $autoBudget?->transactionCurrency->code,
'currency_name' => $autoBudget?->transactionCurrency->name,
'currency_decimal_places' => $autoBudget?->transactionCurrency->decimal_places,
'currency_symbol' => $autoBudget?->transactionCurrency->symbol,
// TODO object group
'primary_currency_id' => $primary instanceof TransactionCurrency ? (string) $primary->id : null,
'primary_currency_code' => $primary?->code,
'primary_currency_symbol' => $primary?->symbol,
'primary_currency_decimal_places' => $primary?->decimal_places,
// new currency settings.
'object_has_currency_setting' => null !== $budget->meta['currency'],
'currency_id' => null === $currency ? null : (string)$currency->id,
'currency_code' => $currency?->code,
'currency_name' => $currency?->name,
'currency_symbol' => $currency?->symbol,
'currency_decimal_places' => $currency?->decimal_places,
// amount and primary currency amount if present.
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => $this->primaryCurrency->decimal_places,
'auto_budget_amount' => $abAmount,
'pc_auto_budget_amount' => $abPrimary,
'spent' => $spent, // always in primary currency.
'spent' => $this->beautify($budget->meta['spent']),
'pc_spent' => $this->beautify($budget->meta['pc_spent']),
'links' => [
[
'rel' => 'self',
@@ -136,7 +120,7 @@ class BudgetTransformer extends AbstractTransformer
{
$return = [];
foreach ($array as $data) {
$data['sum'] = app('steam')->bcround($data['sum'], (int) $data['currency_decimal_places']);
$data['sum'] = Steam::bcround($data['sum'], (int)$data['currency_decimal_places']);
$return[] = $data;
}

View File

@@ -26,30 +26,22 @@ namespace FireflyIII\Transformers;
use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Category\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Support\Collection;
use FireflyIII\Support\Facades\Steam;
/**
* Class CategoryTransformer
*/
class CategoryTransformer extends AbstractTransformer
{
private readonly bool $convertToNative;
private readonly TransactionCurrency $primary;
private readonly OperationsRepositoryInterface $opsRepository;
private readonly CategoryRepositoryInterface $repository;
private readonly TransactionCurrency $primaryCurrency;
/**
* CategoryTransformer constructor.
*/
public function __construct()
{
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->repository = app(CategoryRepositoryInterface::class);
$this->primary = Amount::getPrimaryCurrency();
$this->convertToNative = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
}
/**
@@ -57,35 +49,27 @@ class CategoryTransformer extends AbstractTransformer
*/
public function transform(Category $category): array
{
$this->opsRepository->setUser($category->user);
$this->repository->setUser($category->user);
$spent = [];
$earned = [];
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
if (null !== $start && null !== $end) {
$earned = $this->beautify($this->opsRepository->sumIncome($start, $end, null, new Collection([$category])));
$spent = $this->beautify($this->opsRepository->sumExpenses($start, $end, null, new Collection([$category])));
}
$primary = $this->primary;
if (!$this->convertToNative) {
$primary = null;
}
$notes = $this->repository->getNoteText($category);
return [
'id' => $category->id,
'created_at' => $category->created_at->toAtomString(),
'updated_at' => $category->updated_at->toAtomString(),
'name' => $category->name,
'notes' => $notes,
'primary_currency_id' => $primary instanceof TransactionCurrency ? (string)$primary->id : null,
'primary_currency_code' => $primary?->code,
'primary_currency_symbol' => $primary?->symbol,
'primary_currency_decimal_places' => $primary?->decimal_places,
'spent' => $spent,
'earned' => $earned,
'notes' => $category->meta['notes'],
// category never has currency settings.
'object_has_currency_setting' => false,
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => (int)$this->primaryCurrency->decimal_places,
'spent' => $this->beautify($category->meta['spent']),
'pc_spent' => $this->beautify($category->meta['pc_spent']),
'earned' => $this->beautify($category->meta['earned']),
'pc_earned' => $this->beautify($category->meta['pc_earned']),
'transferred' => $this->beautify($category->meta['transfers']),
'pc_transferred' => $this->beautify($category->meta['pc_transfers']),
'links' => [
[
'rel' => 'self',
@@ -99,7 +83,7 @@ class CategoryTransformer extends AbstractTransformer
{
$return = [];
foreach ($array as $data) {
$data['sum'] = app('steam')->bcround($data['sum'], (int)$data['currency_decimal_places']);
$data['sum'] = Steam::bcround($data['sum'], (int)$data['currency_decimal_places']);
$return[] = $data;
}

View File

@@ -42,6 +42,7 @@ class CurrencyTransformer extends AbstractTransformer
'updated_at' => $currency->updated_at->toAtomString(),
'native' => $currency->userGroupNative,
'default' => $currency->userGroupNative,
'primary' => $currency->userGroupNative,
'enabled' => $currency->userGroupEnabled,
'name' => $currency->name,
'code' => $currency->code,

View File

@@ -26,24 +26,25 @@ namespace FireflyIII\Transformers;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
/**
* Class PiggyBankEventTransformer
*/
class PiggyBankEventTransformer extends AbstractTransformer
{
private readonly PiggyBankRepositoryInterface $piggyRepos;
private readonly AccountRepositoryInterface $repository;
private TransactionCurrency $primaryCurrency;
private bool $convertToPrimary = false;
/**
* PiggyBankEventTransformer constructor.
*/
public function __construct()
{
$this->repository = app(AccountRepositoryInterface::class);
$this->piggyRepos = app(PiggyBankRepositoryInterface::class);
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
}
/**
@@ -53,39 +54,43 @@ class PiggyBankEventTransformer extends AbstractTransformer
*/
public function transform(PiggyBankEvent $event): array
{
// get account linked to piggy bank
$account = $event->piggyBank->accounts()->first();
// set up repositories.
$this->repository->setUser($account->user);
$this->piggyRepos->setUser($account->user);
// get associated currency or fall back to the default:
$currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getPrimaryCurrencyByUserGroup($account->user->userGroup);
// get associated journal and transaction, if any:
$journalId = $event->transaction_journal_id;
$groupId = null;
if (0 !== (int) $journalId) {
$groupId = (int) $event->transactionJournal->transaction_group_id;
$journalId = (int) $journalId;
$currency = $event->meta['currency'] ?? $this->primaryCurrency;
$amount = Steam::bcround($event->amount, $currency->decimal_places);
$primaryAmount = null;
if ($this->convertToPrimary && $currency->id === $this->primaryCurrency->id) {
$primaryAmount = $amount;
}
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
$primaryAmount = Steam::bcround($event->native_amount, $this->primaryCurrency->decimal_places);
}
return [
'id' => (string) $event->id,
'created_at' => $event->created_at?->toAtomString(),
'updated_at' => $event->updated_at?->toAtomString(),
'amount' => app('steam')->bcround($event->amount, $currency->decimal_places),
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'transaction_journal_id' => null !== $journalId ? (string) $journalId : null,
'transaction_group_id' => null !== $groupId ? (string) $groupId : null,
'links' => [
'id' => (string)$event->id,
'created_at' => $event->created_at?->toAtomString(),
'updated_at' => $event->updated_at?->toAtomString(),
'amount' => $amount,
'pc_amount' => $primaryAmount,
// currencies according to 6.3.0
'has_currency_setting' => true,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => $this->primaryCurrency->decimal_places,
'transaction_journal_id' => null !== $event->transaction_journal_id ? (string)$event->transaction_journal_id : null,
'transaction_group_id' => $event->meta['transaction_group_id'],
'links' => [
[
'rel' => 'self',
'uri' => '/piggy_bank_events/'.$event->id,
'uri' => sprintf('/piggy-banks/%d/events/%s', $event->piggy_bank_id, $event->id),
],
],
];

View File

@@ -25,26 +25,23 @@ declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
/**
* Class PiggyBankTransformer
*/
class PiggyBankTransformer extends AbstractTransformer
{
private readonly AccountRepositoryInterface $accountRepos;
private readonly PiggyBankRepositoryInterface $piggyRepos;
private TransactionCurrency $primaryCurrency;
/**
* PiggyBankTransformer constructor.
*/
public function __construct()
{
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->piggyRepos = app(PiggyBankRepositoryInterface::class);
$this->primaryCurrency = Amount::getPrimaryCurrency();
}
/**
@@ -54,74 +51,58 @@ class PiggyBankTransformer extends AbstractTransformer
*/
public function transform(PiggyBank $piggyBank): array
{
$user = $piggyBank->accounts()->first()->user;
// set up repositories
$this->accountRepos->setUser($user);
$this->piggyRepos->setUser($user);
// note
$notes = $this->piggyRepos->getNoteText($piggyBank);
$notes = '' === $notes ? null : $notes;
$objectGroupId = null;
$objectGroupOrder = null;
$objectGroupTitle = null;
/** @var null|ObjectGroup $objectGroup */
$objectGroup = $piggyBank->objectGroups->first();
if (null !== $objectGroup) {
$objectGroupId = $objectGroup->id;
$objectGroupOrder = $objectGroup->order;
$objectGroupTitle = $objectGroup->title;
}
// get currently saved amount:
$currency = $piggyBank->transactionCurrency;
$currentAmount = $this->piggyRepos->getCurrentAmount($piggyBank);
// Amounts, depending on 0.0 state of target amount
$percentage = null;
$targetAmount = $piggyBank->target_amount;
$leftToSave = null;
$savePerMonth = null;
if (0 !== bccomp($targetAmount, '0')) { // target amount is not 0.00
$leftToSave = bcsub($piggyBank->target_amount, $currentAmount);
$percentage = (int) bcmul(bcdiv($currentAmount, $targetAmount), '100');
$targetAmount = app('steam')->bcround($targetAmount, $currency->decimal_places);
$leftToSave = app('steam')->bcround($leftToSave, $currency->decimal_places);
$savePerMonth = app('steam')->bcround($this->piggyRepos->getSuggestedMonthlyAmount($piggyBank), $currency->decimal_places);
$percentage = null;
if (null !== $piggyBank->meta['target_amount'] && 0 !== bccomp($piggyBank->meta['current_amount'], '0')) { // target amount is not 0.00
$percentage = (int)bcmul(bcdiv($piggyBank->meta['current_amount'], $piggyBank->meta['target_amount']), '100');
}
$startDate = $piggyBank->start_date?->format('Y-m-d');
$targetDate = $piggyBank->target_date?->format('Y-m-d');
$startDate = $piggyBank->start_date?->toAtomString();
$targetDate = $piggyBank->target_date?->toAtomString();
return [
'id' => (string) $piggyBank->id,
'created_at' => $piggyBank->created_at->toAtomString(),
'updated_at' => $piggyBank->updated_at->toAtomString(),
'name' => $piggyBank->name,
'accounts' => $this->renderAccounts($piggyBank),
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'target_amount' => $targetAmount,
'percentage' => $percentage,
'current_amount' => $currentAmount,
'left_to_save' => $leftToSave,
'save_per_month' => $savePerMonth,
'start_date' => $startDate,
'target_date' => $targetDate,
'order' => $piggyBank->order,
'active' => true,
'notes' => $notes,
'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null,
'object_group_order' => $objectGroupOrder,
'object_group_title' => $objectGroupTitle,
'links' => [
'id' => (string)$piggyBank->id,
'created_at' => $piggyBank->created_at->toAtomString(),
'updated_at' => $piggyBank->updated_at->toAtomString(),
'name' => $piggyBank->name,
'percentage' => $percentage,
'start_date' => $startDate,
'target_date' => $targetDate,
'order' => $piggyBank->order,
'active' => true,
'notes' => $piggyBank->meta['notes'],
'object_group_id' => $piggyBank->meta['object_group_id'],
'object_group_order' => $piggyBank->meta['object_group_order'],
'object_group_title' => $piggyBank->meta['object_group_title'],
'accounts' => $piggyBank->meta['accounts'],
// currency settings, 6.3.0.
'object_has_currency_setting' => true,
'currency_id' => (string)$piggyBank->meta['currency']->id,
'currency_name' => $piggyBank->meta['currency']->name,
'currency_code' => $piggyBank->meta['currency']->code,
'currency_symbol' => $piggyBank->meta['currency']->symbol,
'currency_decimal_places' => $piggyBank->meta['currency']->decimal_places,
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => (int)$this->primaryCurrency->decimal_places,
'target_amount' => $piggyBank->meta['target_amount'],
'pc_target_amount' => $piggyBank->meta['pc_target_amount'],
'current_amount' => $piggyBank->meta['current_amount'],
'pc_current_amount' => $piggyBank->meta['pc_current_amount'],
'left_to_save' => $piggyBank->meta['left_to_save'],
'pc_left_to_save' => $piggyBank->meta['pc_left_to_save'],
'save_per_month' => $piggyBank->meta['save_per_month'],
'pc_save_per_month' => $piggyBank->meta['pc_save_per_month'],
'links' => [
[
'rel' => 'self',
'uri' => '/piggy_banks/'.$piggyBank->id,
'uri' => sprintf('/piggy-banks/%d', $piggyBank->id),
],
],
];
@@ -132,10 +113,10 @@ class PiggyBankTransformer extends AbstractTransformer
$return = [];
foreach ($piggyBank->accounts()->get() as $account) {
$return[] = [
'id' => (string) $account->id,
'name' => $account->name,
'current_amount' => (string) $account->pivot->current_amount,
'pc_current_amount' => (string) $account->pivot->native_current_amount,
'id' => (string)$account->id,
'name' => $account->name,
'current_amount' => (string)$account->pivot->current_amount,
'pc_current_amount' => (string)$account->pivot->native_current_amount,
// TODO add balance, add left to save.
];
}

View File

@@ -24,43 +24,19 @@ declare(strict_types=1);
namespace FireflyIII\Transformers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\CategoryFactory;
use FireflyIII\Models\Account;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\RecurrenceRepetition;
use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\RecurrenceTransactionMeta;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use function Safe\json_decode;
use Illuminate\Support\Facades\Log;
/**
* Class RecurringTransactionTransformer
*/
class RecurrenceTransformer extends AbstractTransformer
{
private readonly BillRepositoryInterface $billRepos;
private readonly BudgetRepositoryInterface $budgetRepos;
private readonly CategoryFactory $factory;
private readonly PiggyBankRepositoryInterface $piggyRepos;
private readonly RecurringRepositoryInterface $repository;
/**
* RecurrenceTransformer constructor.
*/
public function __construct()
{
$this->repository = app(RecurringRepositoryInterface::class);
$this->piggyRepos = app(PiggyBankRepositoryInterface::class);
$this->factory = app(CategoryFactory::class);
$this->budgetRepos = app(BudgetRepositoryInterface::class);
$this->billRepos = app(BillRepositoryInterface::class);
}
public function __construct() {}
/**
* Transform the recurring transaction.
@@ -69,35 +45,29 @@ class RecurrenceTransformer extends AbstractTransformer
*/
public function transform(Recurrence $recurrence): array
{
app('log')->debug('Now in Recurrence::transform()');
$this->repository->setUser($recurrence->user);
$this->piggyRepos->setUser($recurrence->user);
$this->factory->setUser($recurrence->user);
$this->budgetRepos->setUser($recurrence->user);
app('log')->debug('Set user.');
Log::debug('Now in Recurrence::transform()');
$shortType = (string) config(sprintf('firefly.transactionTypesToShort.%s', $recurrence->transactionType->type));
$notes = $this->repository->getNoteText($recurrence);
$reps = 0 === (int) $recurrence->repetitions ? null : (int) $recurrence->repetitions;
app('log')->debug('Get basic data.');
$shortType = (string)config(sprintf('firefly.transactionTypesToShort.%s', $recurrence->transactionType->type));
$reps = 0 === (int)$recurrence->repetitions ? null : (int)$recurrence->repetitions;
Log::debug('Get basic data.');
// basic data.
return [
'id' => (string) $recurrence->id,
'id' => (string)$recurrence->id,
'created_at' => $recurrence->created_at->toAtomString(),
'updated_at' => $recurrence->updated_at->toAtomString(),
'type' => $shortType,
'title' => $recurrence->title,
'description' => $recurrence->description,
'first_date' => $recurrence->first_date->format('Y-m-d'),
'latest_date' => $recurrence->latest_date?->format('Y-m-d'),
'repeat_until' => $recurrence->repeat_until?->format('Y-m-d'),
'first_date' => $recurrence->first_date->toAtomString(),
'latest_date' => $recurrence->latest_date?->toAtomString(),
'repeat_until' => $recurrence->repeat_until?->toAtomString(),
'apply_rules' => $recurrence->apply_rules,
'active' => $recurrence->active,
'nr_of_repetitions' => $reps,
'notes' => '' === $notes ? null : $notes,
'repetitions' => $this->getRepetitions($recurrence),
'transactions' => $this->getTransactions($recurrence),
'notes' => $recurrence->meta['notes'],
'repetitions' => $recurrence->meta['repetitions'],
'new_transactions' => $recurrence->meta['transactions'],
'links' => [
[
'rel' => 'self',
@@ -106,208 +76,4 @@ class RecurrenceTransformer extends AbstractTransformer
],
];
}
/**
* @throws FireflyException
*/
private function getRepetitions(Recurrence $recurrence): array
{
app('log')->debug('Now in getRepetitions().');
$fromDate = $recurrence->latest_date ?? $recurrence->first_date;
$return = [];
/** @var RecurrenceRepetition $repetition */
foreach ($recurrence->recurrenceRepetitions as $repetition) {
$repetitionArray = [
'id' => (string) $repetition->id,
'created_at' => $repetition->created_at->toAtomString(),
'updated_at' => $repetition->updated_at->toAtomString(),
'type' => $repetition->repetition_type,
'moment' => $repetition->repetition_moment,
'skip' => $repetition->repetition_skip,
'weekend' => $repetition->weekend,
'description' => $this->repository->repetitionDescription($repetition),
'occurrences' => [],
];
// get the (future) occurrences for this specific type of repetition:
$amount = 'daily' === $repetition->repetition_type ? 9 : 5;
$occurrences = $this->repository->getXOccurrencesSince($repetition, $fromDate, now(), $amount);
/** @var Carbon $carbon */
foreach ($occurrences as $carbon) {
$repetitionArray['occurrences'][] = $carbon->toAtomString();
}
$return[] = $repetitionArray;
}
return $return;
}
/**
* @throws FireflyException
*/
private function getTransactions(Recurrence $recurrence): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
$return = [];
// get all transactions:
/** @var RecurrenceTransaction $transaction */
foreach ($recurrence->recurrenceTransactions()->get() as $transaction) {
/** @var null|Account $sourceAccount */
$sourceAccount = $transaction->sourceAccount;
/** @var null|Account $destinationAccount */
$destinationAccount = $transaction->destinationAccount;
$foreignCurrencyCode = null;
$foreignCurrencySymbol = null;
$foreignCurrencyDp = null;
$foreignCurrencyId = null;
if (null !== $transaction->foreign_currency_id) {
$foreignCurrencyId = (int) $transaction->foreign_currency_id;
$foreignCurrencyCode = $transaction->foreignCurrency->code;
$foreignCurrencySymbol = $transaction->foreignCurrency->symbol;
$foreignCurrencyDp = $transaction->foreignCurrency->decimal_places;
}
// source info:
$sourceName = '';
$sourceId = null;
$sourceType = null;
$sourceIban = null;
if (null !== $sourceAccount) {
$sourceName = $sourceAccount->name;
$sourceId = $sourceAccount->id;
$sourceType = $sourceAccount->accountType->type;
$sourceIban = $sourceAccount->iban;
}
$destinationName = '';
$destinationId = null;
$destinationType = null;
$destinationIban = null;
if (null !== $destinationAccount) {
$destinationName = $destinationAccount->name;
$destinationId = $destinationAccount->id;
$destinationType = $destinationAccount->accountType->type;
$destinationIban = $destinationAccount->iban;
}
$amount = app('steam')->bcround($transaction->amount, $transaction->transactionCurrency->decimal_places);
$foreignAmount = null;
if (null !== $transaction->foreign_currency_id && null !== $transaction->foreign_amount) {
$foreignAmount = app('steam')->bcround($transaction->foreign_amount, $foreignCurrencyDp);
}
$transactionArray = [
'id' => (string) $transaction->id,
'currency_id' => (string) $transaction->transaction_currency_id,
'currency_code' => $transaction->transactionCurrency->code,
'currency_symbol' => $transaction->transactionCurrency->symbol,
'currency_decimal_places' => $transaction->transactionCurrency->decimal_places,
'foreign_currency_id' => null === $foreignCurrencyId ? null : (string) $foreignCurrencyId,
'foreign_currency_code' => $foreignCurrencyCode,
'foreign_currency_symbol' => $foreignCurrencySymbol,
'foreign_currency_decimal_places' => $foreignCurrencyDp,
'source_id' => (string) $sourceId,
'source_name' => $sourceName,
'source_iban' => $sourceIban,
'source_type' => $sourceType,
'destination_id' => (string) $destinationId,
'destination_name' => $destinationName,
'destination_iban' => $destinationIban,
'destination_type' => $destinationType,
'amount' => $amount,
'foreign_amount' => $foreignAmount,
'description' => $transaction->description,
];
$transactionArray = $this->getTransactionMeta($transaction, $transactionArray);
if (null !== $transaction->foreign_currency_id) {
$transactionArray['foreign_currency_code'] = $transaction->foreignCurrency->code;
$transactionArray['foreign_currency_symbol'] = $transaction->foreignCurrency->symbol;
$transactionArray['foreign_currency_decimal_places'] = $transaction->foreignCurrency->decimal_places;
}
// store transaction in recurrence array.
$return[] = $transactionArray;
}
return $return;
}
/**
* @throws FireflyException
*/
private function getTransactionMeta(RecurrenceTransaction $transaction, array $array): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
$array['tags'] = [];
$array['category_id'] = null;
$array['category_name'] = null;
$array['budget_id'] = null;
$array['budget_name'] = null;
$array['piggy_bank_id'] = null;
$array['piggy_bank_name'] = null;
$array['bill_id'] = null;
$array['bill_name'] = null;
/** @var RecurrenceTransactionMeta $transactionMeta */
foreach ($transaction->recurrenceTransactionMeta as $transactionMeta) {
switch ($transactionMeta->name) {
default:
throw new FireflyException(sprintf('Recurrence transformer cant handle field "%s"', $transactionMeta->name));
case 'bill_id':
$bill = $this->billRepos->find((int) $transactionMeta->value);
if (null !== $bill) {
$array['bill_id'] = (string) $bill->id;
$array['bill_name'] = $bill->name;
}
break;
case 'tags':
$array['tags'] = json_decode((string) $transactionMeta->value);
break;
case 'piggy_bank_id':
$piggy = $this->piggyRepos->find((int) $transactionMeta->value);
if (null !== $piggy) {
$array['piggy_bank_id'] = (string) $piggy->id;
$array['piggy_bank_name'] = $piggy->name;
}
break;
case 'category_id':
$category = $this->factory->findOrCreate((int) $transactionMeta->value, null);
if (null !== $category) {
$array['category_id'] = (string) $category->id;
$array['category_name'] = $category->name;
}
break;
case 'category_name':
$category = $this->factory->findOrCreate(null, $transactionMeta->value);
if (null !== $category) {
$array['category_id'] = (string) $category->id;
$array['category_name'] = $category->name;
}
break;
case 'budget_id':
$budget = $this->budgetRepos->find((int) $transactionMeta->value);
if (null !== $budget) {
$array['budget_id'] = (string) $budget->id;
$array['budget_name'] = $budget->name;
}
break;
}
}
return $array;
}
}

View File

@@ -35,6 +35,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\NullArrayObject;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -116,10 +117,10 @@ class TransactionGroupTransformer extends AbstractTransformer
private function transformTransaction(array $transaction): array
{
// amount:
$amount = app('steam')->positive((string) ($transaction['amount'] ?? '0'));
$amount = Steam::positive((string) ($transaction['amount'] ?? '0'));
$foreignAmount = null;
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string) $transaction['foreign_amount'])) {
$foreignAmount = app('steam')->positive($transaction['foreign_amount']);
$foreignAmount = Steam::positive($transaction['foreign_amount']);
}
// set primary amount to the normal amount if the currency matches.
@@ -128,7 +129,7 @@ class TransactionGroupTransformer extends AbstractTransformer
}
if (array_key_exists('pc_amount', $transaction) && null !== $transaction['pc_amount']) {
$transaction['pc_amount'] = app('steam')->positive($transaction['pc_amount']);
$transaction['pc_amount'] = Steam::positive($transaction['pc_amount']);
}
$type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value);
@@ -139,107 +140,107 @@ class TransactionGroupTransformer extends AbstractTransformer
$recurrenceCount = null !== $recurrenceCount ? (int) $recurrenceCount : null;
return [
'user' => (string) $transaction['user_id'],
'transaction_journal_id' => (string) $transaction['transaction_journal_id'],
'type' => strtolower((string) $type),
'date' => $transaction['date']->toAtomString(),
'order' => $transaction['order'],
'user' => (string) $transaction['user_id'],
'transaction_journal_id' => (string) $transaction['transaction_journal_id'],
'type' => strtolower((string) $type),
'date' => $transaction['date']->toAtomString(),
'order' => $transaction['order'],
'currency_id' => (string) $transaction['currency_id'],
'currency_code' => $transaction['currency_code'],
'currency_name' => $transaction['currency_name'],
'currency_symbol' => $transaction['currency_symbol'],
'currency_decimal_places' => (int) $transaction['currency_decimal_places'],
// currency information, structured for 6.3.0.
'object_has_currency_setting' => true,
'foreign_currency_id' => $this->stringFromArray($transaction, 'foreign_currency_id', null),
'foreign_currency_code' => $transaction['foreign_currency_code'],
'foreign_currency_name' => $transaction['foreign_currency_name'],
'foreign_currency_symbol' => $transaction['foreign_currency_symbol'],
'foreign_currency_decimal_places' => $transaction['foreign_currency_decimal_places'],
'currency_id' => (string) $transaction['currency_id'],
'currency_code' => $transaction['currency_code'],
'currency_name' => $transaction['currency_name'],
'currency_symbol' => $transaction['currency_symbol'],
'currency_decimal_places' => (int) $transaction['currency_decimal_places'],
'amount' => $amount,
'foreign_amount' => $foreignAmount,
'foreign_currency_id' => $this->stringFromArray($transaction, 'foreign_currency_id', null),
'foreign_currency_code' => $transaction['foreign_currency_code'],
'foreign_currency_name' => $transaction['foreign_currency_name'],
'foreign_currency_symbol' => $transaction['foreign_currency_symbol'],
'foreign_currency_decimal_places' => $transaction['foreign_currency_decimal_places'],
// primary currency amount, defaults to NULL when convertToPrimary is false.
'pc_amount' => $transaction['pc_amount'] ?? null,
'primary_currency_id' => $transaction['primary_currency']['id'] ?? null,
'primary_currency_code' => $transaction['primary_currency']['code'] ?? null,
'primary_currency_name' => $transaction['primary_currency']['name'] ?? null,
'primary_currency_symbol' => $transaction['primary_currency']['symbol'] ?? null,
'primary_currency_decimal_places' => $transaction['primary_currency']['decimal_places'] ?? null,
// primary currency, always present.
'primary_currency_id' => $transaction['primary_currency']['id'] ?? null,
'primary_currency_code' => $transaction['primary_currency']['code'] ?? null,
'primary_currency_name' => $transaction['primary_currency']['name'] ?? null,
'primary_currency_symbol' => $transaction['primary_currency']['symbol'] ?? null,
'primary_currency_decimal_places' => $transaction['primary_currency']['decimal_places'] ?? null,
// amounts, structured for 6.3.0.
'amount' => $amount,
'pc_amount' => $transaction['pc_amount'] ?? null,
// source balance after
'source_balance_after' => $transaction['source_balance_after'] ?? null,
'source_balance_dirty' => $transaction['source_balance_dirty'],
'foreign_amount' => $foreignAmount,
'pc_foreign_amount' => null,
'source_balance_after' => $transaction['source_balance_after'] ?? null,
'pc_source_balance_after' => null,
// destination balance after
'destination_balance_after' => $transaction['destination_balance_after'] ?? null,
'destination_balance_dirty' => $transaction['destination_balance_dirty'],
'destination_balance_after' => $transaction['destination_balance_after'] ?? null,
'pc_destination_balance_after' => null,
// balance before and after, if not dirty.
// 'running_balance_dirty' => $transaction['balance_dirty'] ?? false,
// 'running_balance_before' => $transaction['balance_before'] ?? null,
// 'running_balance_after' => $transaction['balance_after'] ?? null,
'source_balance_dirty' => $transaction['source_balance_dirty'],
'destination_balance_dirty' => $transaction['destination_balance_dirty'],
'description' => $transaction['description'],
'source_id' => (string) $transaction['source_account_id'],
'source_name' => $transaction['source_account_name'],
'source_iban' => $transaction['source_account_iban'],
'source_type' => $transaction['source_account_type'],
'description' => $transaction['description'],
'destination_id' => (string) $transaction['destination_account_id'],
'destination_name' => $transaction['destination_account_name'],
'destination_iban' => $transaction['destination_account_iban'],
'destination_type' => $transaction['destination_account_type'],
'source_id' => (string) $transaction['source_account_id'],
'source_name' => $transaction['source_account_name'],
'source_iban' => $transaction['source_account_iban'],
'source_type' => $transaction['source_account_type'],
'budget_id' => $this->stringFromArray($transaction, 'budget_id', null),
'budget_name' => $transaction['budget_name'],
'destination_id' => (string) $transaction['destination_account_id'],
'destination_name' => $transaction['destination_account_name'],
'destination_iban' => $transaction['destination_account_iban'],
'destination_type' => $transaction['destination_account_type'],
'category_id' => $this->stringFromArray($transaction, 'category_id', null),
'category_name' => $transaction['category_name'],
'budget_id' => $this->stringFromArray($transaction, 'budget_id', null),
'budget_name' => $transaction['budget_name'],
'bill_id' => $this->stringFromArray($transaction, 'bill_id', null),
'bill_name' => $transaction['bill_name'],
'subscription_id' => $this->stringFromArray($transaction, 'bill_id', null),
'subscription_name' => $transaction['bill_name'],
'category_id' => $this->stringFromArray($transaction, 'category_id', null),
'category_name' => $transaction['category_name'],
'reconciled' => $transaction['reconciled'],
'notes' => $transaction['notes'],
'tags' => $transaction['tags'],
'bill_id' => $this->stringFromArray($transaction, 'bill_id', null),
'bill_name' => $transaction['bill_name'],
'internal_reference' => $transaction['meta']['internal_reference'] ?? null,
'external_id' => $transaction['meta']['external_id'] ?? null,
'original_source' => $transaction['meta']['original_source'] ?? null,
'recurrence_id' => $transaction['meta']['recurrence_id'] ?? null,
'recurrence_total' => $recurrenceTotal,
'recurrence_count' => $recurrenceCount,
'external_url' => $transaction['meta']['external_url'] ?? null,
'import_hash_v2' => $transaction['meta']['import_hash_v2'] ?? null,
'reconciled' => $transaction['reconciled'],
'notes' => $transaction['notes'],
'tags' => $transaction['tags'],
'sepa_cc' => $transaction['meta']['sepa_cc'] ?? null,
'sepa_ct_op' => $transaction['meta']['sepa_ct_op'] ?? null,
'sepa_ct_id' => $transaction['meta']['sepa_ct_id'] ?? null,
'sepa_db' => $transaction['meta']['sepa_db'] ?? null,
'sepa_country' => $transaction['meta']['sepa_country'] ?? null,
'sepa_ep' => $transaction['meta']['sepa_ep'] ?? null,
'sepa_ci' => $transaction['meta']['sepa_ci'] ?? null,
'sepa_batch_id' => $transaction['meta']['sepa_batch_id'] ?? null,
'internal_reference' => $transaction['meta']['internal_reference'] ?? null,
'external_id' => $transaction['meta']['external_id'] ?? null,
'original_source' => $transaction['meta']['original_source'] ?? null,
'recurrence_id' => $transaction['meta']['recurrence_id'] ?? null,
'recurrence_total' => $recurrenceTotal,
'recurrence_count' => $recurrenceCount,
'bunq_payment_id' => $transaction['meta']['bunq_payment_id'] ?? null,
'external_url' => $transaction['meta']['external_url'] ?? null,
'import_hash_v2' => $transaction['meta']['import_hash_v2'] ?? null,
'interest_date' => array_key_exists('interest_date', $transaction['meta_date']) ? $transaction['meta_date']['interest_date']->toW3CString() : null,
'book_date' => array_key_exists('book_date', $transaction['meta_date']) ? $transaction['meta_date']['book_date']->toW3CString() : null,
'process_date' => array_key_exists('process_date', $transaction['meta_date']) ? $transaction['meta_date']['process_date']->toW3CString() : null,
'due_date' => array_key_exists('due_date', $transaction['meta_date']) ? $transaction['meta_date']['due_date']->toW3CString() : null,
'payment_date' => array_key_exists('payment_date', $transaction['meta_date']) ? $transaction['meta_date']['payment_date']->toW3CString() : null,
'invoice_date' => array_key_exists('invoice_date', $transaction['meta_date']) ? $transaction['meta_date']['invoice_date']->toW3CString() : null,
'sepa_cc' => $transaction['meta']['sepa_cc'] ?? null,
'sepa_ct_op' => $transaction['meta']['sepa_ct_op'] ?? null,
'sepa_ct_id' => $transaction['meta']['sepa_ct_id'] ?? null,
'sepa_db' => $transaction['meta']['sepa_db'] ?? null,
'sepa_country' => $transaction['meta']['sepa_country'] ?? null,
'sepa_ep' => $transaction['meta']['sepa_ep'] ?? null,
'sepa_ci' => $transaction['meta']['sepa_ci'] ?? null,
'sepa_batch_id' => $transaction['meta']['sepa_batch_id'] ?? null,
'interest_date' => array_key_exists('interest_date', $transaction['meta_date']) ? $transaction['meta_date']['interest_date']->toW3CString() : null,
'book_date' => array_key_exists('book_date', $transaction['meta_date']) ? $transaction['meta_date']['book_date']->toW3CString() : null,
'process_date' => array_key_exists('process_date', $transaction['meta_date']) ? $transaction['meta_date']['process_date']->toW3CString() : null,
'due_date' => array_key_exists('due_date', $transaction['meta_date']) ? $transaction['meta_date']['due_date']->toW3CString() : null,
'payment_date' => array_key_exists('payment_date', $transaction['meta_date']) ? $transaction['meta_date']['payment_date']->toW3CString() : null,
'invoice_date' => array_key_exists('invoice_date', $transaction['meta_date']) ? $transaction['meta_date']['invoice_date']->toW3CString() : null,
// location data
'longitude' => $transaction['location']['longitude'],
'latitude' => $transaction['location']['latitude'],
'zoom_level' => $transaction['location']['zoom_level'],
'has_attachments' => $transaction['attachment_count'] > 0,
'longitude' => $transaction['location']['longitude'],
'latitude' => $transaction['location']['latitude'],
'zoom_level' => $transaction['location']['zoom_level'],
'has_attachments' => $transaction['attachment_count'] > 0,
];
}
@@ -287,8 +288,8 @@ class TransactionGroupTransformer extends AbstractTransformer
],
];
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException(sprintf('Transaction group #%d is broken. Please check out your log files.', $group->id), 0, $e);
}
@@ -324,7 +325,7 @@ class TransactionGroupTransformer extends AbstractTransformer
$destination = $this->getDestinationTransaction($journal);
$type = $journal->transactionType->type;
$currency = $source->transactionCurrency;
$amount = app('steam')->bcround($this->getAmount($source->amount), $currency->decimal_places ?? 0);
$amount = Steam::bcround($this->getAmount($source->amount), $currency->decimal_places ?? 0);
$foreignAmount = $this->getForeignAmount($source->foreign_amount ?? null);
$metaFieldData = $this->groupRepos->getMetaFields($journal->id, $this->metaFields);
$metaDates = $this->getDates($this->groupRepos->getMetaDateFields($journal->id, $this->metaDateFields));
@@ -334,7 +335,7 @@ class TransactionGroupTransformer extends AbstractTransformer
$bill = $this->getBill($journal->bill);
if (null !== $foreignAmount && null !== $source->foreignCurrency) {
$foreignAmount = app('steam')->bcround($foreignAmount, $foreignCurrency['decimal_places'] ?? 0);
$foreignAmount = Steam::bcround($foreignAmount, $foreignCurrency['decimal_places'] ?? 0);
}
$longitude = null;
@@ -364,7 +365,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'foreign_currency_symbol' => $foreignCurrency['symbol'],
'foreign_currency_decimal_places' => $foreignCurrency['decimal_places'],
'amount' => app('steam')->bcround($amount, $currency->decimal_places),
'amount' => Steam::bcround($amount, $currency->decimal_places),
'foreign_amount' => $foreignAmount,
'description' => $journal->description,
@@ -458,13 +459,13 @@ class TransactionGroupTransformer extends AbstractTransformer
private function getAmount(string $amount): string
{
return app('steam')->positive($amount);
return Steam::positive($amount);
}
private function getForeignAmount(?string $foreignAmount): ?string
{
if (null !== $foreignAmount && '' !== $foreignAmount && 0 !== bccomp('0', $foreignAmount)) {
return app('steam')->positive($foreignAmount);
return Steam::positive($foreignAmount);
}
return null;

View File

@@ -89,7 +89,7 @@ class UserGroupTransformer extends AbstractTransformer
foreach ($members as $member) {
$mail = $member['user_email'];
$new[$groupId][$mail] ??= [
'user_id' => $member['user_id'],
'user_id' => (string) $member['user_id'],
'user_email' => $member['user_email'],
'you' => $member['you'],
'roles' => [],

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Models\WebhookMessage;
use Illuminate\Support\Facades\Log;
use JsonException;
use function Safe\json_encode;
@@ -44,7 +45,7 @@ class WebhookMessageTransformer extends AbstractTransformer
try {
$json = json_encode($message->message, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
app('log')->error(sprintf('Could not encode webhook message #%d: %s', $message->id, $e->getMessage()));
Log::error(sprintf('Could not encode webhook message #%d: %s', $message->id, $e->getMessage()));
}
return [

32
composer.lock generated
View File

@@ -3711,16 +3711,16 @@
},
{
"name": "nesbot/carbon",
"version": "3.10.1",
"version": "3.10.2",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
"reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00"
"reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00",
"reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24",
"reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24",
"shasum": ""
},
"require": {
@@ -3812,7 +3812,7 @@
"type": "tidelift"
}
],
"time": "2025-06-21T15:19:35+00:00"
"time": "2025-08-02T09:36:06+00:00"
},
{
"name": "nette/schema",
@@ -5101,16 +5101,16 @@
},
{
"name": "predis/predis",
"version": "v3.1.0",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/predis/predis.git",
"reference": "202e0c5322b906ec4c761c0cefebad6d0959a699"
"reference": "9e9deec4dfd3ebf65d32eb368f498c646ba2ecd8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/predis/predis/zipball/202e0c5322b906ec4c761c0cefebad6d0959a699",
"reference": "202e0c5322b906ec4c761c0cefebad6d0959a699",
"url": "https://api.github.com/repos/predis/predis/zipball/9e9deec4dfd3ebf65d32eb368f498c646ba2ecd8",
"reference": "9e9deec4dfd3ebf65d32eb368f498c646ba2ecd8",
"shasum": ""
},
"require": {
@@ -5152,7 +5152,7 @@
],
"support": {
"issues": "https://github.com/predis/predis/issues",
"source": "https://github.com/predis/predis/tree/v3.1.0"
"source": "https://github.com/predis/predis/tree/v3.2.0"
},
"funding": [
{
@@ -5160,7 +5160,7 @@
"type": "github"
}
],
"time": "2025-07-22T15:37:44+00:00"
"time": "2025-08-06T06:41:24+00:00"
},
{
"name": "psr/cache",
@@ -11128,16 +11128,16 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.21",
"version": "2.1.22",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4",
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4",
"shasum": ""
},
"require": {
@@ -11182,7 +11182,7 @@
"type": "github"
}
],
"time": "2025-07-28T19:35:08+00:00"
"time": "2025-08-04T19:17:37+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-08-01',
'build_time' => 1754049768,
'version' => 'develop/2025-08-06',
'build_time' => 1754505490,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 26,
@@ -121,6 +121,7 @@ return [
'languages' => [
// currently enabled languages
// 'af_ZA' => ['name_locale' => 'Afrikaans', 'name_english' => 'Afrikaans'],
'ar_SA' => ['name_locale' => 'العربية', 'name_english' => 'Arabic'],
'bg_BG' => ['name_locale' => 'Български', 'name_english' => 'Bulgarian'],
'cs_CZ' => ['name_locale' => 'Czech', 'name_english' => 'Czech'],
'da_DK' => ['name_locale' => 'Danish', 'name_english' => 'Danish'],

View File

@@ -23,243 +23,244 @@
declare(strict_types=1);
return [
'operators' => [
'user_action' => ['alias' => false, 'needs_context' => true],
'account_id' => ['alias' => false, 'needs_context' => true],
'reconciled' => ['alias' => false, 'needs_context' => false],
'source_account_id' => ['alias' => false, 'needs_context' => true],
'destination_account_id' => ['alias' => false, 'needs_context' => true],
'transaction_type' => ['alias' => false, 'needs_context' => true],
'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true],
'tag_is' => ['alias' => false, 'needs_context' => true],
'tag_is_not' => ['alias' => false, 'needs_context' => true],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true],
'tag_contains' => ['alias' => false, 'needs_context' => true],
'tag_ends' => ['alias' => false, 'needs_context' => true],
'tag_starts' => ['alias' => false, 'needs_context' => true],
'description_is' => ['alias' => false, 'needs_context' => true],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true],
'description_contains' => ['alias' => false, 'needs_context' => true],
'description_ends' => ['alias' => false, 'needs_context' => true],
'description_starts' => ['alias' => false, 'needs_context' => true],
'notes_is' => ['alias' => false, 'needs_context' => true],
'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true],
'notes_contains' => ['alias' => false, 'needs_context' => true],
'notes_contain' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes_ends' => ['alias' => false, 'needs_context' => true],
'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true],
'notes_starts' => ['alias' => false, 'needs_context' => true],
'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true],
'source_account_is' => ['alias' => false, 'needs_context' => true],
'from_account_is' => ['alias' => true, 'alias_for' => 'source_account_is', 'needs_context' => true],
'source_account_contains' => ['alias' => false, 'needs_context' => true],
'source' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from_account_contains' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'source_account_ends' => ['alias' => false, 'needs_context' => true],
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true],
'source_account_starts' => ['alias' => false, 'needs_context' => true],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true],
'source_account_nr_is' => ['alias' => false, 'needs_context' => true],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true],
'source_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'from_account_nr_contains' => ['alias' => true, 'alias_for' => 'source_account_nr_contains', 'needs_context' => true],
'source_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true],
'source_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true],
'destination_account_is' => ['alias' => false, 'needs_context' => true],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true],
'destination_account_contains' => ['alias' => false, 'needs_context' => true],
'destination' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to_account_contains' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'destination_account_ends' => ['alias' => false, 'needs_context' => true],
'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true],
'destination_account_starts' => ['alias' => false, 'needs_context' => true],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true],
'destination_account_nr_is' => ['alias' => false, 'needs_context' => true],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true],
'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'to_account_nr_contains' => ['alias' => true, 'alias_for' => 'destination_account_nr_contains', 'needs_context' => true],
'destination_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true],
'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true],
'account_is' => ['alias' => false, 'needs_context' => true],
'account_contains' => ['alias' => false, 'needs_context' => true],
'account_ends' => ['alias' => false, 'needs_context' => true],
'account_starts' => ['alias' => false, 'needs_context' => true],
'account_nr_is' => ['alias' => false, 'needs_context' => true],
'account_nr_contains' => ['alias' => false, 'needs_context' => true],
'account_nr_ends' => ['alias' => false, 'needs_context' => true],
'account_nr_starts' => ['alias' => false, 'needs_context' => true],
'category_is' => ['alias' => false, 'needs_context' => true],
'category_contains' => ['alias' => false, 'needs_context' => true],
'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true],
'category_ends' => ['alias' => false, 'needs_context' => true],
'category_starts' => ['alias' => false, 'needs_context' => true],
'budget_is' => ['alias' => false, 'needs_context' => true],
'budget_contains' => ['alias' => false, 'needs_context' => true],
'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true],
'budget_ends' => ['alias' => false, 'needs_context' => true],
'budget_starts' => ['alias' => false, 'needs_context' => true],
'bill_is' => ['alias' => false, 'needs_context' => true],
'bill_contains' => ['alias' => false, 'needs_context' => true],
'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'bill_ends' => ['alias' => false, 'needs_context' => true],
'bill_starts' => ['alias' => false, 'needs_context' => true],
'subscription_is' => ['alias' => true, 'alias_for' => 'bill_is', 'needs_context' => true],
'subscription_contains' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription_ends' => ['alias' => true, 'alias_for' => 'bill_ends', 'needs_context' => true],
'subscription_starts' => ['alias' => true, 'alias_for' => 'bill_starts', 'needs_context' => true],
'external_id_is' => ['alias' => false, 'needs_context' => true],
'external_id_contains' => ['alias' => false, 'needs_context' => true],
'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true],
'external_id_ends' => ['alias' => false, 'needs_context' => true],
'external_id_starts' => ['alias' => false, 'needs_context' => true],
'internal_reference_is' => ['alias' => false, 'needs_context' => true],
'internal_reference_contains' => ['alias' => false, 'needs_context' => true],
'internal_reference' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true],
'internal_reference_ends' => ['alias' => false, 'needs_context' => true],
'internal_reference_starts' => ['alias' => false, 'needs_context' => true],
'external_url_is' => ['alias' => false, 'needs_context' => true],
'external_url_contains' => ['alias' => false, 'needs_context' => true],
'external_url' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true],
'external_url_ends' => ['alias' => false, 'needs_context' => true],
'external_url_starts' => ['alias' => false, 'needs_context' => true],
'has_attachments' => ['alias' => false, 'needs_context' => false],
'has_any_category' => ['alias' => false, 'needs_context' => false],
'has_any_budget' => ['alias' => false, 'needs_context' => false],
'has_any_bill' => ['alias' => false, 'needs_context' => false],
'has_any_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_any_bill'],
'has_any_tag' => ['alias' => false, 'needs_context' => false],
'any_notes' => ['alias' => false, 'needs_context' => false],
'has_any_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'has_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'any_external_url' => ['alias' => false, 'needs_context' => false],
'has_any_external_url' => ['alias' => true, 'alias_for' => 'any_external_url', 'needs_context' => false],
'has_no_attachments' => ['alias' => false, 'needs_context' => false],
'has_no_category' => ['alias' => false, 'needs_context' => false],
'has_no_budget' => ['alias' => false, 'needs_context' => false],
'has_no_bill' => ['alias' => false, 'needs_context' => false],
'has_no_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_no_bill'],
'has_no_tag' => ['alias' => false, 'needs_context' => false],
'no_notes' => ['alias' => false, 'needs_context' => false],
'no_external_url' => ['alias' => false, 'needs_context' => false],
'source_is_cash' => ['alias' => false, 'needs_context' => false],
'destination_is_cash' => ['alias' => false, 'needs_context' => false],
'account_is_cash' => ['alias' => false, 'needs_context' => false],
'currency_is' => ['alias' => false, 'needs_context' => true],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true],
'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'journal_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'recurrence_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'date_on' => ['alias' => false, 'needs_context' => true],
'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_is' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'on' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_before' => ['alias' => false, 'needs_context' => true],
'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true],
'date_after' => ['alias' => false, 'needs_context' => true],
'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true],
'interest_date_on' => ['alias' => false, 'needs_context' => true],
'interest_date' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_is' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_before' => ['alias' => false, 'needs_context' => true],
'interest_date_after' => ['alias' => false, 'needs_context' => true],
'book_date_on' => ['alias' => false, 'needs_context' => true],
'book_date' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_is' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_before' => ['alias' => false, 'needs_context' => true],
'book_date_after' => ['alias' => false, 'needs_context' => true],
'process_date_on' => ['alias' => false, 'needs_context' => true],
'process_date' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_is' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_before' => ['alias' => false, 'needs_context' => true],
'process_date_after' => ['alias' => false, 'needs_context' => true],
'due_date_on' => ['alias' => false, 'needs_context' => true],
'due_date' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_is' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_before' => ['alias' => false, 'needs_context' => true],
'due_date_after' => ['alias' => false, 'needs_context' => true],
'payment_date_on' => ['alias' => false, 'needs_context' => true],
'payment_date' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_is' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_before' => ['alias' => false, 'needs_context' => true],
'payment_date_after' => ['alias' => false, 'needs_context' => true],
'invoice_date_on' => ['alias' => false, 'needs_context' => true],
'invoice_date' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_is' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_before' => ['alias' => false, 'needs_context' => true],
'invoice_date_after' => ['alias' => false, 'needs_context' => true],
'created_at_on' => ['alias' => false, 'needs_context' => true],
'created_at' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_is' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_before' => ['alias' => false, 'needs_context' => true],
'created_at_after' => ['alias' => false, 'needs_context' => true],
'updated_at_on' => ['alias' => false, 'needs_context' => true],
'updated_at' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_is' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_before' => ['alias' => false, 'needs_context' => true],
'updated_at_after' => ['alias' => false, 'needs_context' => true],
'created_on_on' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_on' => ['alias' => true, 'alias_for' => 'created_at', 'needs_context' => true],
'created_on_before' => ['alias' => true, 'alias_for' => 'created_at_before', 'needs_context' => true],
'created_on_after' => ['alias' => true, 'alias_for' => 'created_at_after', 'needs_context' => true],
'updated_on_on' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_on' => ['alias' => true, 'alias_for' => 'updated_at', 'needs_context' => true],
'updated_on_before' => ['alias' => true, 'alias_for' => 'updated_at_before', 'needs_context' => true],
'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true],
'amount_is' => ['alias' => false, 'needs_context' => true],
'amount' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_exactly' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_less' => ['alias' => false, 'needs_context' => true],
'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'less' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'amount_more' => ['alias' => false, 'needs_context' => true],
'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'more' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'foreign_amount_is' => ['alias' => false, 'needs_context' => true],
'foreign_amount' => ['alias' => true, 'alias_for' => 'foreign_amount_is', 'needs_context' => true],
'foreign_amount_less' => ['alias' => false, 'needs_context' => true],
'foreign_amount_max' => ['alias' => true, 'alias_for' => 'foreign_amount_less', 'needs_context' => true],
'foreign_amount_more' => ['alias' => false, 'needs_context' => true],
'foreign_amount_min' => ['alias' => true, 'alias_for' => 'foreign_amount_more', 'needs_context' => true],
'attachment_name_is' => ['alias' => false, 'needs_context' => true],
'attachment' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_is' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name_contains' => ['alias' => false, 'needs_context' => true],
'attachment_name_starts' => ['alias' => false, 'needs_context' => true],
'attachment_name_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes' => ['alias' => true, 'alias_for' => 'attachment_notes_are', 'needs_context' => true],
'attachment_notes_are' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contains' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contain' => ['alias' => true, 'alias_for' => 'attachment_notes_contains', 'needs_context' => true],
'attachment_notes_starts' => ['alias' => false, 'needs_context' => true],
'attachment_notes_start' => ['alias' => true, 'alias_for' => 'attachment_notes_starts', 'needs_context' => true],
'attachment_notes_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true],
'exists' => ['alias' => false, 'needs_context' => false],
'sepa_ct_is' => ['alias' => false, 'needs_context' => true],
'no_external_id' => ['alias' => false, 'needs_context' => false],
'any_external_id' => ['alias' => false, 'needs_context' => false],
'operators' => [
'user_action' => ['alias' => false, 'needs_context' => true],
'account_id' => ['alias' => false, 'needs_context' => true],
'reconciled' => ['alias' => false, 'needs_context' => false],
'source_account_id' => ['alias' => false, 'needs_context' => true],
'destination_account_id' => ['alias' => false, 'needs_context' => true],
'transaction_type' => ['alias' => false, 'needs_context' => true],
'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true],
'tag_is' => ['alias' => false, 'needs_context' => true],
'tag_is_not' => ['alias' => false, 'needs_context' => true],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true],
'tag_contains' => ['alias' => false, 'needs_context' => true],
'tag_ends' => ['alias' => false, 'needs_context' => true],
'tag_starts' => ['alias' => false, 'needs_context' => true],
'description_is' => ['alias' => false, 'needs_context' => true],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true],
'description_contains' => ['alias' => false, 'needs_context' => true],
'description_ends' => ['alias' => false, 'needs_context' => true],
'description_starts' => ['alias' => false, 'needs_context' => true],
'notes_is' => ['alias' => false, 'needs_context' => true],
'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true],
'notes_contains' => ['alias' => false, 'needs_context' => true],
'notes_contain' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes_ends' => ['alias' => false, 'needs_context' => true],
'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true],
'notes_starts' => ['alias' => false, 'needs_context' => true],
'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true],
'source_account_is' => ['alias' => false, 'needs_context' => true],
'from_account_is' => ['alias' => true, 'alias_for' => 'source_account_is', 'needs_context' => true],
'source_account_contains' => ['alias' => false, 'needs_context' => true],
'source' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from_account_contains' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'source_account_ends' => ['alias' => false, 'needs_context' => true],
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true],
'source_account_starts' => ['alias' => false, 'needs_context' => true],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true],
'source_account_nr_is' => ['alias' => false, 'needs_context' => true],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true],
'source_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'from_account_nr_contains' => ['alias' => true, 'alias_for' => 'source_account_nr_contains', 'needs_context' => true],
'source_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true],
'source_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true],
'destination_account_is' => ['alias' => false, 'needs_context' => true],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true],
'destination_account_contains' => ['alias' => false, 'needs_context' => true],
'destination' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to_account_contains' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'destination_account_ends' => ['alias' => false, 'needs_context' => true],
'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true],
'destination_account_starts' => ['alias' => false, 'needs_context' => true],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true],
'destination_account_nr_is' => ['alias' => false, 'needs_context' => true],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true],
'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'to_account_nr_contains' => ['alias' => true, 'alias_for' => 'destination_account_nr_contains', 'needs_context' => true],
'destination_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true],
'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true],
'account_is' => ['alias' => false, 'needs_context' => true],
'account_contains' => ['alias' => false, 'needs_context' => true],
'account_ends' => ['alias' => false, 'needs_context' => true],
'account_starts' => ['alias' => false, 'needs_context' => true],
'account_nr_is' => ['alias' => false, 'needs_context' => true],
'account_nr_contains' => ['alias' => false, 'needs_context' => true],
'account_nr_ends' => ['alias' => false, 'needs_context' => true],
'account_nr_starts' => ['alias' => false, 'needs_context' => true],
'category_is' => ['alias' => false, 'needs_context' => true],
'category_contains' => ['alias' => false, 'needs_context' => true],
'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true],
'category_ends' => ['alias' => false, 'needs_context' => true],
'category_starts' => ['alias' => false, 'needs_context' => true],
'budget_is' => ['alias' => false, 'needs_context' => true],
'budget_contains' => ['alias' => false, 'needs_context' => true],
'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true],
'budget_ends' => ['alias' => false, 'needs_context' => true],
'budget_starts' => ['alias' => false, 'needs_context' => true],
'bill_is' => ['alias' => false, 'needs_context' => true],
'bill_contains' => ['alias' => false, 'needs_context' => true],
'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'bill_ends' => ['alias' => false, 'needs_context' => true],
'bill_starts' => ['alias' => false, 'needs_context' => true],
'subscription_is' => ['alias' => true, 'alias_for' => 'bill_is', 'needs_context' => true],
'subscription_contains' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription_ends' => ['alias' => true, 'alias_for' => 'bill_ends', 'needs_context' => true],
'subscription_starts' => ['alias' => true, 'alias_for' => 'bill_starts', 'needs_context' => true],
'external_id_is' => ['alias' => false, 'needs_context' => true],
'external_id_contains' => ['alias' => false, 'needs_context' => true],
'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true],
'external_id_ends' => ['alias' => false, 'needs_context' => true],
'external_id_starts' => ['alias' => false, 'needs_context' => true],
'internal_reference_is' => ['alias' => false, 'needs_context' => true],
'internal_reference_contains' => ['alias' => false, 'needs_context' => true],
'internal_reference' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true],
'internal_reference_ends' => ['alias' => false, 'needs_context' => true],
'internal_reference_starts' => ['alias' => false, 'needs_context' => true],
'external_url_is' => ['alias' => false, 'needs_context' => true],
'external_url_contains' => ['alias' => false, 'needs_context' => true],
'external_url' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true],
'external_url_ends' => ['alias' => false, 'needs_context' => true],
'external_url_starts' => ['alias' => false, 'needs_context' => true],
'has_attachments' => ['alias' => false, 'needs_context' => false],
'has_any_category' => ['alias' => false, 'needs_context' => false],
'has_any_budget' => ['alias' => false, 'needs_context' => false],
'has_any_bill' => ['alias' => false, 'needs_context' => false],
'has_any_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_any_bill'],
'has_any_tag' => ['alias' => false, 'needs_context' => false],
'any_notes' => ['alias' => false, 'needs_context' => false],
'has_any_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'has_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'any_external_url' => ['alias' => false, 'needs_context' => false],
'has_any_external_url' => ['alias' => true, 'alias_for' => 'any_external_url', 'needs_context' => false],
'has_no_attachments' => ['alias' => false, 'needs_context' => false],
'has_no_category' => ['alias' => false, 'needs_context' => false],
'has_no_budget' => ['alias' => false, 'needs_context' => false],
'has_no_bill' => ['alias' => false, 'needs_context' => false],
'has_no_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_no_bill'],
'has_no_tag' => ['alias' => false, 'needs_context' => false],
'no_notes' => ['alias' => false, 'needs_context' => false],
'no_external_url' => ['alias' => false, 'needs_context' => false],
'source_is_cash' => ['alias' => false, 'needs_context' => false],
'destination_is_cash' => ['alias' => false, 'needs_context' => false],
'account_is_cash' => ['alias' => false, 'needs_context' => false],
'currency_is' => ['alias' => false, 'needs_context' => true],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true],
'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'journal_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'recurrence_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'date_on' => ['alias' => false, 'needs_context' => true],
'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_is' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'on' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_before' => ['alias' => false, 'needs_context' => true],
'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true],
'date_after' => ['alias' => false, 'needs_context' => true],
'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true],
'interest_date_on' => ['alias' => false, 'needs_context' => true],
'interest_date' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_is' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_before' => ['alias' => false, 'needs_context' => true],
'interest_date_after' => ['alias' => false, 'needs_context' => true],
'book_date_on' => ['alias' => false, 'needs_context' => true],
'book_date' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_is' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_before' => ['alias' => false, 'needs_context' => true],
'book_date_after' => ['alias' => false, 'needs_context' => true],
'process_date_on' => ['alias' => false, 'needs_context' => true],
'process_date' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_is' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_before' => ['alias' => false, 'needs_context' => true],
'process_date_after' => ['alias' => false, 'needs_context' => true],
'due_date_on' => ['alias' => false, 'needs_context' => true],
'due_date' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_is' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_before' => ['alias' => false, 'needs_context' => true],
'due_date_after' => ['alias' => false, 'needs_context' => true],
'payment_date_on' => ['alias' => false, 'needs_context' => true],
'payment_date' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_is' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_before' => ['alias' => false, 'needs_context' => true],
'payment_date_after' => ['alias' => false, 'needs_context' => true],
'invoice_date_on' => ['alias' => false, 'needs_context' => true],
'invoice_date' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_is' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_before' => ['alias' => false, 'needs_context' => true],
'invoice_date_after' => ['alias' => false, 'needs_context' => true],
'created_at_on' => ['alias' => false, 'needs_context' => true],
'created_at' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_is' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_before' => ['alias' => false, 'needs_context' => true],
'created_at_after' => ['alias' => false, 'needs_context' => true],
'updated_at_on' => ['alias' => false, 'needs_context' => true],
'updated_at' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_is' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_before' => ['alias' => false, 'needs_context' => true],
'updated_at_after' => ['alias' => false, 'needs_context' => true],
'created_on_on' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_on' => ['alias' => true, 'alias_for' => 'created_at', 'needs_context' => true],
'created_on_before' => ['alias' => true, 'alias_for' => 'created_at_before', 'needs_context' => true],
'created_on_after' => ['alias' => true, 'alias_for' => 'created_at_after', 'needs_context' => true],
'updated_on_on' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_on' => ['alias' => true, 'alias_for' => 'updated_at', 'needs_context' => true],
'updated_on_before' => ['alias' => true, 'alias_for' => 'updated_at_before', 'needs_context' => true],
'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true],
'amount_is' => ['alias' => false, 'needs_context' => true],
'amount' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_exactly' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_less' => ['alias' => false, 'needs_context' => true],
'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'less' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'amount_more' => ['alias' => false, 'needs_context' => true],
'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'more' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'foreign_amount_is' => ['alias' => false, 'needs_context' => true],
'foreign_amount' => ['alias' => true, 'alias_for' => 'foreign_amount_is', 'needs_context' => true],
'foreign_amount_less' => ['alias' => false, 'needs_context' => true],
'foreign_amount_max' => ['alias' => true, 'alias_for' => 'foreign_amount_less', 'needs_context' => true],
'foreign_amount_more' => ['alias' => false, 'needs_context' => true],
'foreign_amount_min' => ['alias' => true, 'alias_for' => 'foreign_amount_more', 'needs_context' => true],
'attachment_name_is' => ['alias' => false, 'needs_context' => true],
'attachment' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_is' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name_contains' => ['alias' => false, 'needs_context' => true],
'attachment_name_starts' => ['alias' => false, 'needs_context' => true],
'attachment_name_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes' => ['alias' => true, 'alias_for' => 'attachment_notes_are', 'needs_context' => true],
'attachment_notes_are' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contains' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contain' => ['alias' => true, 'alias_for' => 'attachment_notes_contains', 'needs_context' => true],
'attachment_notes_starts' => ['alias' => false, 'needs_context' => true],
'attachment_notes_start' => ['alias' => true, 'alias_for' => 'attachment_notes_starts', 'needs_context' => true],
'attachment_notes_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true],
'exists' => ['alias' => false, 'needs_context' => false],
'sepa_ct_is' => ['alias' => false, 'needs_context' => true],
'no_external_id' => ['alias' => false, 'needs_context' => false],
'any_external_id' => ['alias' => false, 'needs_context' => false],
'has_any_external_id' => ['alias' => true, 'alias_for' => 'any_external_id', 'needs_context' => false],
// based on source or destination balance. Very heavy search.
'source_balance_gte' => ['alias' => false, 'needs_context' => true],
'source_balance_gt' => ['alias' => false, 'needs_context' => true],
'source_balance_lte' => ['alias' => false, 'needs_context' => true],
'source_balance_lt' => ['alias' => false, 'needs_context' => true],
'source_balance_is' => ['alias' => false, 'needs_context' => true],
'destination_balance_gte' => ['alias' => false, 'needs_context' => true],
'destination_balance_gt' => ['alias' => false, 'needs_context' => true],
'destination_balance_lte' => ['alias' => false, 'needs_context' => true],
'destination_balance_lt' => ['alias' => false, 'needs_context' => true],
'destination_balance_is' => ['alias' => false, 'needs_context' => true],
'source_balance_gte' => ['alias' => false, 'needs_context' => true],
'source_balance_gt' => ['alias' => false, 'needs_context' => true],
'source_balance_lte' => ['alias' => false, 'needs_context' => true],
'source_balance_lt' => ['alias' => false, 'needs_context' => true],
'source_balance_is' => ['alias' => false, 'needs_context' => true],
'destination_balance_gte' => ['alias' => false, 'needs_context' => true],
'destination_balance_gt' => ['alias' => false, 'needs_context' => true],
'destination_balance_lte' => ['alias' => false, 'needs_context' => true],
'destination_balance_lt' => ['alias' => false, 'needs_context' => true],
'destination_balance_is' => ['alias' => false, 'needs_context' => true],
],
// Which query parser to use - 'new' or 'legacy'
'query_parser' => env('QUERY_PARSER_IMPLEMENTATION', 'legacy'),
'query_parser' => env('QUERY_PARSER_IMPLEMENTATION', 'legacy'),
];

View File

@@ -333,6 +333,7 @@ return [
'languages' => [
// currently enabled languages
'af_ZA',
'ar_SA',
'bg_BG',
'cs_CZ',
'da_DK',

View File

@@ -67,6 +67,7 @@ class TransactionCurrencySeeder extends Seeder
// asian currencies
$currencies[] = ['code' => 'JPY', 'name' => 'Japanese yen', 'symbol' => '¥', 'decimal_places' => 0];
$currencies[] = ['code' => 'CNY', 'name' => 'Chinese yuan', 'symbol' => '¥', 'decimal_places' => 2];
$currencies[] = ['code' => 'KRW', 'name' => 'South Korean won','symbol' => '₩', 'decimal_places' => 2,];
// $currencies[] = ['code' => 'RMB', 'name' => 'Chinese yuan', 'symbol' => '¥', 'decimal_places' => 2];
$currencies[] = ['code' => 'RUB', 'name' => 'Russian ruble', 'symbol' => '₽', 'decimal_places' => 2];
$currencies[] = ['code' => 'INR', 'name' => 'Indian rupee', 'symbol' => '₹', 'decimal_places' => 2];

44
package-lock.json generated
View File

@@ -3148,13 +3148,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
"undici-types": "~7.10.0"
}
},
"node_modules/@types/node-forge": {
@@ -4930,9 +4930,9 @@
"license": "MIT"
},
"node_modules/core-js-compat": {
"version": "3.44.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz",
"integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==",
"version": "3.45.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz",
"integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5700,9 +5700,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.194",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz",
"integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==",
"version": "1.5.197",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.197.tgz",
"integrity": "sha512-m1xWB3g7vJ6asIFz+2pBUbq3uGmfmln1M9SSvBe4QIFWYrRHylP73zL/3nMjDmwz8V+1xAXQDfBd6+HPW0WvDQ==",
"dev": true,
"license": "ISC"
},
@@ -5757,9 +5757,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
"version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10214,9 +10214,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.89.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz",
"integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==",
"version": "1.90.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11327,9 +11327,9 @@
}
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"dev": true,
"license": "MIT"
},
@@ -12459,9 +12459,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
"dev": true,
"license": "ISC",
"bin": {

View File

@@ -364,6 +364,8 @@ function updateTriggerInput(selectList) {
createAutoComplete(inputResult, 'api/v1/autocomplete/transactions');
break;
case 'has_no_category':
case 'no_external_id':
case 'any_external_id':
case 'has_any_category':
case 'has_no_budget':
case 'has_any_budget':

View File

@@ -0,0 +1,71 @@
//! moment.js locale configuration
//! locale : Afrikaans [af]
//! author : Werner Mollentze : https://github.com/wernerm
import moment from '../moment';
export default moment.defineLocale('af', {
months: 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split(
'_'
),
monthsShort: 'Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'),
weekdays: 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split(
'_'
),
weekdaysShort: 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'),
weekdaysMin: 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'),
meridiemParse: /vm|nm/i,
isPM: function (input) {
return /^nm$/i.test(input);
},
meridiem: function (hours, minutes, isLower) {
if (hours < 12) {
return isLower ? 'vm' : 'VM';
} else {
return isLower ? 'nm' : 'NM';
}
},
longDateFormat: {
LT: 'HH:mm',
LTS: 'HH:mm:ss',
L: 'DD/MM/YYYY',
LL: 'D MMMM YYYY',
LLL: 'D MMMM YYYY HH:mm',
LLLL: 'dddd, D MMMM YYYY HH:mm',
},
calendar: {
sameDay: '[Vandag om] LT',
nextDay: '[Môre om] LT',
nextWeek: 'dddd [om] LT',
lastDay: '[Gister om] LT',
lastWeek: '[Laas] dddd [om] LT',
sameElse: 'L',
},
relativeTime: {
future: 'oor %s',
past: '%s gelede',
s: "'n paar sekondes",
ss: '%d sekondes',
m: "'n minuut",
mm: '%d minute',
h: "'n uur",
hh: '%d ure',
d: "'n dag",
dd: '%d dae',
M: "'n maand",
MM: '%d maande',
y: "'n jaar",
yy: '%d jaar',
},
dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/,
ordinal: function (number) {
return (
number +
(number === 1 || number === 8 || number >= 20 ? 'ste' : 'de')
); // Thanks to Joris Röling : https://github.com/jjupiter
},
week: {
dow: 1, // Maandag is die eerste dag van die week.
doy: 4, // Die week wat die 4de Januarie bevat is die eerste week van die jaar.
},
});

View File

@@ -0,0 +1,105 @@
//! moment.js locale configuration
//! locale : Arabic (Saudi Arabia) [ar-sa]
//! author : Suhail Alkowaileet : https://github.com/xsoh
import moment from '../moment';
var symbolMap = {
1: '١',
2: '٢',
3: '٣',
4: '٤',
5: '٥',
6: '٦',
7: '٧',
8: '٨',
9: '٩',
0: '٠',
},
numberMap = {
'١': '1',
'٢': '2',
'٣': '3',
'٤': '4',
'٥': '5',
'٦': '6',
'٧': '7',
'٨': '8',
'٩': '9',
'٠': '0',
};
export default moment.defineLocale('ar-sa', {
months: 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split(
'_'
),
monthsShort:
'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split(
'_'
),
weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
weekdaysShort: 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'),
weekdaysParseExact: true,
longDateFormat: {
LT: 'HH:mm',
LTS: 'HH:mm:ss',
L: 'DD/MM/YYYY',
LL: 'D MMMM YYYY',
LLL: 'D MMMM YYYY HH:mm',
LLLL: 'dddd D MMMM YYYY HH:mm',
},
meridiemParse: /ص|م/,
isPM: function (input) {
return 'م' === input;
},
meridiem: function (hour, minute, isLower) {
if (hour < 12) {
return 'ص';
} else {
return 'م';
}
},
calendar: {
sameDay: '[اليوم على الساعة] LT',
nextDay: '[غدا على الساعة] LT',
nextWeek: 'dddd [على الساعة] LT',
lastDay: '[أمس على الساعة] LT',
lastWeek: 'dddd [على الساعة] LT',
sameElse: 'L',
},
relativeTime: {
future: 'في %s',
past: 'منذ %s',
s: 'ثوان',
ss: '%d ثانية',
m: 'دقيقة',
mm: '%d دقائق',
h: 'ساعة',
hh: '%d ساعات',
d: 'يوم',
dd: '%d أيام',
M: 'شهر',
MM: '%d أشهر',
y: 'سنة',
yy: '%d سنوات',
},
preparse: function (string) {
return string
.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
return numberMap[match];
})
.replace(/،/g, ',');
},
postformat: function (string) {
return string
.replace(/\d/g, function (match) {
return symbolMap[match];
})
.replace(/,/g, '،');
},
week: {
dow: 0, // Sunday is the first day of the week.
doy: 6, // The week that contains Jan 6th is the first week of the year.
},
});

View File

@@ -110,6 +110,8 @@
"/public/v1/js/lib/jquery.autocomplete.min.js": "/public/v1/js/lib/jquery.autocomplete.min.js",
"/public/v1/js/lib/jquery.color-2.1.2.min.js": "/public/v1/js/lib/jquery.color-2.1.2.min.js",
"/public/v1/js/lib/modernizr-custom.js": "/public/v1/js/lib/modernizr-custom.js",
"/public/v1/js/lib/moment/af_ZA.js": "/public/v1/js/lib/moment/af_ZA.js",
"/public/v1/js/lib/moment/ar_SA.js": "/public/v1/js/lib/moment/ar_SA.js",
"/public/v1/js/lib/moment/bg_BG.js": "/public/v1/js/lib/moment/bg_BG.js",
"/public/v1/js/lib/moment/ca_ES.js": "/public/v1/js/lib/moment/ca_ES.js",
"/public/v1/js/lib/moment/cs_CZ.js": "/public/v1/js/lib/moment/cs_CZ.js",

View File

@@ -24,6 +24,7 @@ module.exports = new vuei18n({
fallbackLocale: 'en',
messages: {
'af': require('./locales/af.json'),
'ar': require('./locales/ar.json'),
'bg': require('./locales/bg.json'),
'ca-es': require('./locales/ca.json'),
'cs': require('./locales/cs.json'),

View File

@@ -0,0 +1,188 @@
{
"firefly": {
"administrations_page_title": "\u0627\u0644\u0625\u062f\u0627\u0631\u0627\u062a \u0627\u0644\u0645\u0627\u0644\u064a\u0629",
"administrations_index_menu": "\u0627\u0644\u0625\u062f\u0627\u0631\u0627\u062a \u0627\u0644\u0645\u0627\u0644\u064a\u0629",
"expires_at": "\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0627\u0646\u062a\u0647\u0627\u0621",
"temp_administrations_introduction": "\u0633\u064a\u062d\u0635\u0644 Firefly III \u0642\u0631\u064a\u0628\u0627\u064b \u0639\u0644\u0649 \u0625\u0645\u0643\u0627\u0646\u064a\u0629 \u0625\u062f\u0627\u0631\u0629 \u0639\u062f\u0629 \u0625\u062f\u0627\u0631\u0627\u062a \u0645\u0627\u0644\u064a\u0629. \u062d\u0627\u0644\u064a\u0627\u064b \u0644\u062f\u064a\u0643 \u0648\u0627\u062d\u062f\u0629 \u0641\u0642\u0637. \u064a\u0645\u0643\u0646\u0643 \u062a\u0639\u064a\u064a\u0646 \u0639\u0646\u0648\u0627\u0646 \u0647\u0630\u0647 \u0627\u0644\u0625\u062f\u0627\u0631\u0629 \u0648\u0639\u0645\u0644\u062a\u0647\u0627 \u0627\u0644\u0623\u0633\u0627\u0633\u064a\u0629. \u0647\u0630\u0627 \u064a\u0633\u062a\u0628\u062f\u0644 \u0627\u0644\u0625\u0639\u062f\u0627\u062f \u0627\u0644\u0633\u0627\u0628\u0642 \u062d\u064a\u062b \u0643\u0646\u062a \u062a\u0639\u064a\u0646 \"\u0627\u0644\u0639\u0645\u0644\u0629 \u0627\u0644\u0627\u0641\u062a\u0631\u0627\u0636\u064a\u0629\". \u0647\u0630\u0627 \u0627\u0644\u0625\u0639\u062f\u0627\u062f \u0623\u0635\u0628\u062d \u0627\u0644\u0622\u0646 \u0645\u0631\u062a\u0628\u0637\u0627\u064b \u0628\u0627\u0644\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0645\u0627\u0644\u064a\u0629 \u0648\u064a\u0645\u0643\u0646 \u0623\u0646 \u064a\u062e\u062a\u0644\u0641 \u0644\u0643\u0644 \u0625\u062f\u0627\u0631\u0629.",
"administration_currency_form_help": "\u0642\u062f \u064a\u0633\u062a\u063a\u0631\u0642 \u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0635\u0641\u062d\u0629 \u0648\u0642\u062a\u0627\u064b \u0637\u0648\u064a\u0644\u0627\u064b \u0625\u0630\u0627 \u0642\u0645\u062a \u0628\u062a\u063a\u064a\u064a\u0631 \u0627\u0644\u0639\u0645\u0644\u0629 \u0627\u0644\u0623\u0633\u0627\u0633\u064a\u0629 \u0644\u0623\u0646 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0627\u062a \u0642\u062f \u062a\u062d\u062a\u0627\u062c \u0625\u0644\u0649 \u0627\u0644\u062a\u062d\u0648\u064a\u0644 \u0625\u0644\u0649 \u0639\u0645\u0644\u062a\u0643 (\u0627\u0644\u062c\u062f\u064a\u062f\u0629) \u0627\u0644\u0623\u0633\u0627\u0633\u064a\u0629.",
"administrations_page_edit_sub_title_js": "\u062a\u0639\u062f\u064a\u0644 \u0627\u0644\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0645\u0627\u0644\u064a\u0629 \"{title}\"",
"table": "\u062c\u062f\u0648\u0644",
"welcome_back": "\u0623\u0647\u0644\u0627\u064b \u0648\u0633\u0647\u0644\u0627\u064b!",
"flash_error": "\u062e\u0637\u0623!",
"flash_warning": "\u062a\u062d\u0630\u064a\u0631!",
"flash_success": "\u0646\u062c\u062d!",
"close": "\u0625\u063a\u0644\u0627\u0642",
"select_dest_account": "\u064a\u0631\u062c\u0649 \u0627\u062e\u062a\u064a\u0627\u0631 \u0623\u0648 \u0643\u062a\u0627\u0628\u0629 \u0627\u0633\u0645 \u062d\u0633\u0627\u0628 \u0648\u062c\u0647\u0629 \u0635\u0627\u0644\u062d",
"select_source_account": "\u064a\u0631\u062c\u0649 \u0627\u062e\u062a\u064a\u0627\u0631 \u0623\u0648 \u0643\u062a\u0627\u0628\u0629 \u0627\u0633\u0645 \u062d\u0633\u0627\u0628 \u0645\u0635\u062f\u0631 \u0635\u0627\u0644\u062d",
"split_transaction_title": "\u0648\u0635\u0641 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629 \u0627\u0644\u0645\u0646\u0642\u0633\u0645\u0629",
"errors_submission": "\u0643\u0627\u0646 \u0647\u0646\u0627\u0643 \u062e\u0637\u0623 \u0645\u0627 \u0641\u064a \u062a\u0642\u062f\u064a\u0645\u0643. \u064a\u0631\u062c\u0649 \u0627\u0644\u062a\u062d\u0642\u0642 \u0645\u0646 \u0627\u0644\u0623\u062e\u0637\u0627\u0621 \u0623\u062f\u0646\u0627\u0647.",
"is_reconciled": "\u062a\u0645\u062a \u0627\u0644\u062a\u0633\u0648\u064a\u0629",
"split": "\u062a\u0642\u0633\u064a\u0645",
"single_split": "\u062a\u0642\u0633\u064a\u0645",
"not_enough_currencies": "\u0639\u062f\u062f \u0627\u0644\u0639\u0645\u0644\u0627\u062a \u063a\u064a\u0631 \u0643\u0627\u0641\u064d",
"not_enough_currencies_enabled": "\u0625\u0630\u0627 \u0643\u0627\u0646 \u0644\u062f\u064a\u0643 \u0639\u0645\u0644\u0629 \u0648\u0627\u062d\u062f\u0629 \u0641\u0642\u0637 \u0645\u0641\u0639\u0644\u0629\u060c \u0641\u0644\u0627 \u062f\u0627\u0639\u064a \u0644\u0625\u0636\u0627\u0641\u0629 \u0623\u0633\u0639\u0627\u0631 \u0635\u0631\u0641.",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">\u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629 #{ID} (\"{title}\")<\/a> \u062a\u0645 \u062d\u0641\u0638\u0647\u0627.",
"webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">\u0648\u064a\u0628 \u0647\u0648\u0643 #{ID} (\"{title}\")<\/a> \u062a\u0645 \u062d\u0641\u0638\u0647.",
"webhook_updated_link": "<a href=\"webhooks\/show\/{ID}\">\u0648\u064a\u0628 \u0647\u0648\u0643 #{ID}<\/a> (\"{title}\") \u062a\u0645 \u062a\u062d\u062f\u064a\u062b\u0647.",
"transaction_updated_link": "<a href=\"transactions\/show\/{ID}\">\u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629 #{ID}<\/a> (\"{title}\") \u062a\u0645 \u062a\u062d\u062f\u064a\u062b\u0647\u0627.",
"transaction_new_stored_link": "<a href=\"transactions\/show\/{ID}\">\u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629 #{ID}<\/a> \u062a\u0645 \u062d\u0641\u0638\u0647\u0627.",
"transaction_journal_information": "\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629",
"submission_options": "\u062e\u064a\u0627\u0631\u0627\u062a \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
"apply_rules_checkbox": "\u062a\u0637\u0628\u064a\u0642 \u0627\u0644\u0642\u0648\u0627\u0639\u062f",
"fire_webhooks_checkbox": "\u062a\u0634\u063a\u064a\u0644 Webhooks",
"no_budget_pointer": "\u064a\u0628\u062f\u0648 \u0623\u0646\u0647 \u0644\u064a\u0633 \u0644\u062f\u064a\u0643 \u0645\u064a\u0632\u0627\u0646\u064a\u0627\u062a \u0628\u0639\u062f. \u064a\u062c\u0628 \u0623\u0646 \u062a\u0646\u0634\u0626 \u0628\u0639\u0636\u0647\u0627 \u0641\u064a <a href=\"budgets\">\u0635\u0641\u062d\u0629 \u0627\u0644\u0645\u064a\u0632\u0627\u0646\u064a\u0627\u062a<\/a>. \u0627\u0644\u0645\u064a\u0632\u0627\u0646\u064a\u0627\u062a \u064a\u0645\u0643\u0646 \u0623\u0646 \u062a\u0633\u0627\u0639\u062f\u0643 \u0641\u064a \u062a\u062a\u0628\u0639 \u0627\u0644\u0646\u0641\u0642\u0627\u062a.",
"no_bill_pointer": "\u064a\u0628\u062f\u0648 \u0623\u0646\u0647 \u0644\u064a\u0633 \u0644\u062f\u064a\u0643 \u0627\u0634\u062a\u0631\u0627\u0643\u0627\u062a \u0628\u0639\u062f. \u064a\u062c\u0628 \u0623\u0646 \u062a\u0646\u0634\u0626 \u0628\u0639\u0636\u0647\u0627 \u0641\u064a <a href=\"subscriptions\">\u0635\u0641\u062d\u0629 \u0627\u0644\u0627\u0634\u062a\u0631\u0627\u0643\u0627\u062a<\/a>. \u0627\u0644\u0627\u0634\u062a\u0631\u0627\u0643\u0627\u062a \u064a\u0645\u0643\u0646 \u0623\u0646 \u062a\u0633\u0627\u0639\u062f\u0643 \u0641\u064a \u062a\u062a\u0628\u0639 \u0627\u0644\u0646\u0641\u0642\u0627\u062a.",
"source_account": "\u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631",
"hidden_fields_preferences": "\u064a\u0645\u0643\u0646\u0643 \u062a\u0645\u0643\u064a\u0646 \u0627\u0644\u0645\u0632\u064a\u062f \u0645\u0646 \u062e\u064a\u0627\u0631\u0627\u062a \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0627\u062a \u0641\u064a <a href=\"preferences\">\u0627\u0644\u062a\u0641\u0636\u064a\u0644\u0627\u062a<\/a> \u0627\u0644\u062e\u0627\u0635\u0629 \u0628\u0643.",
"destination_account": "\u062d\u0633\u0627\u0628 \u0627\u0644\u0648\u062c\u0647\u0629",
"add_another_split": "\u0625\u0636\u0627\u0641\u0629 \u062a\u0642\u0633\u064a\u0645 \u0622\u062e\u0631",
"submission": "\u0625\u0631\u0633\u0627\u0644",
"stored_journal": "\u062a\u0645 \u0625\u0646\u0634\u0627\u0621 \u0645\u0639\u0627\u0645\u0644\u0629 \u062c\u062f\u064a\u062f\u0629 \":description\"",
"create_another": "\u0628\u0639\u062f \u0627\u0644\u062a\u062e\u0632\u064a\u0646\u060c \u0639\u062f \u0625\u0644\u0649 \u0647\u0646\u0627 \u0644\u0625\u0646\u0634\u0627\u0621 \u0648\u0627\u062d\u062f\u0629 \u0623\u062e\u0631\u0649.",
"reset_after": "\u0625\u0639\u0627\u062f\u0629 \u062a\u0639\u064a\u064a\u0646 \u0627\u0644\u0646\u0645\u0648\u0630\u062c \u0628\u0639\u062f \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
"submit": "\u0625\u0631\u0633\u0627\u0644",
"amount": "\u0627\u0644\u0645\u0628\u0644\u063a",
"date": "\u0627\u0644\u062a\u0627\u0631\u064a\u062e",
"is_reconciled_fields_dropped": "\u0644\u0623\u0646 \u0647\u0630\u0647 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629 \u062a\u0645\u062a \u062a\u0633\u0648\u064a\u062a\u0647\u0627\u060c \u0644\u0646 \u062a\u062a\u0645\u0643\u0646 \u0645\u0646 \u062a\u062d\u062f\u064a\u062b \u0627\u0644\u062d\u0633\u0627\u0628\u0627\u062a \u0623\u0648 \u0627\u0644\u0645\u0628\u0627\u0644\u063a \u0625\u0644\u0627 \u0625\u0630\u0627 \u0623\u0632\u0644\u062a \u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u062a\u0633\u0648\u064a\u0629.",
"tags": "\u0627\u0644\u0648\u0633\u0648\u0645",
"no_budget": "(\u0644\u0627 \u062a\u0648\u062c\u062f \u0645\u064a\u0632\u0627\u0646\u064a\u0629)",
"no_bill": "(\u0644\u0627 \u062a\u0648\u062c\u062f \u0627\u0634\u062a\u0631\u0627\u0643)",
"category": "\u0641\u0626\u0629",
"attachments": "\u0627\u0644\u0645\u0631\u0641\u0642\u0627\u062a",
"notes": "\u0645\u0644\u0627\u062d\u0638\u0627\u062a",
"external_url": "\u0631\u0627\u0628\u0637 \u062e\u0627\u0631\u062c\u064a",
"update_transaction": "\u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629",
"after_update_create_another": "\u0628\u0639\u062f \u0627\u0644\u062a\u062d\u062f\u064a\u062b\u060c \u0639\u062f \u0625\u0644\u0649 \u0647\u0646\u0627 \u0644\u0645\u062a\u0627\u0628\u0639\u0629 \u0627\u0644\u062a\u062d\u0631\u064a\u0631.",
"store_as_new": "\u062a\u062e\u0632\u064a\u0646 \u0643\u0645\u0639\u0627\u0645\u0644\u0629 \u062c\u062f\u064a\u062f\u0629 \u0628\u062f\u0644\u0627\u064b \u0645\u0646 \u0627\u0644\u062a\u062d\u062f\u064a\u062b.",
"split_title_help": "\u0625\u0630\u0627 \u0642\u0645\u062a \u0628\u0625\u0646\u0634\u0627\u0621 \u0645\u0639\u0627\u0645\u0644\u0629 \u0645\u0646\u0642\u0633\u0645\u0629\u060c \u064a\u062c\u0628 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0647\u0646\u0627\u0643 \u0648\u0635\u0641 \u0639\u0627\u0644\u0645\u064a \u0644\u062c\u0645\u064a\u0639 \u0627\u0646\u0642\u0633\u0627\u0645\u0627\u062a \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629.",
"none_in_select_list": "(\u0644\u0627 \u064a\u0648\u062c\u062f)",
"no_piggy_bank": "(\u0644\u0627 \u062a\u0648\u062c\u062f \u062d\u0635\u0627\u0644\u0629)",
"description": "\u0627\u0644\u0648\u0635\u0641",
"split_transaction_title_help": "\u0625\u0630\u0627 \u0642\u0645\u062a \u0628\u0625\u0646\u0634\u0627\u0621 \u0645\u0639\u0627\u0645\u0644\u0629 \u0645\u0646\u0642\u0633\u0645\u0629\u060c \u064a\u062c\u0628 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0647\u0646\u0627\u0643 \u0648\u0635\u0641 \u0639\u0627\u0644\u0645\u064a \u0644\u062c\u0645\u064a\u0639 \u0627\u0646\u0642\u0633\u0627\u0645\u0627\u062a \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629.",
"destination_account_reconciliation": "\u0644\u0627 \u064a\u0645\u0643\u0646\u0643 \u062a\u0639\u062f\u064a\u0644 \u062d\u0633\u0627\u0628 \u0627\u0644\u0648\u062c\u0647\u0629 \u0644\u0645\u0639\u0627\u0645\u0644\u0629 \u0627\u0644\u062a\u0633\u0648\u064a\u0629.",
"source_account_reconciliation": "\u0644\u0627 \u064a\u0645\u0643\u0646\u0643 \u062a\u0639\u062f\u064a\u0644 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 \u0644\u0645\u0639\u0627\u0645\u0644\u0629 \u0627\u0644\u062a\u0633\u0648\u064a\u0629.",
"budget": "\u0645\u064a\u0632\u0627\u0646\u064a\u0629",
"bill": "\u0627\u0634\u062a\u0631\u0627\u0643",
"you_create_withdrawal": "\u0623\u0646\u062a \u062a\u0642\u0648\u0645 \u0628\u0625\u0646\u0634\u0627\u0621 \u0633\u062d\u0628.",
"you_create_transfer": "\u0623\u0646\u062a \u062a\u0642\u0648\u0645 \u0628\u0625\u0646\u0634\u0627\u0621 \u062a\u062d\u0648\u064a\u0644.",
"you_create_deposit": "\u0623\u0646\u062a \u062a\u0642\u0648\u0645 \u0628\u0625\u0646\u0634\u0627\u0621 \u0625\u064a\u062f\u0627\u0639.",
"edit": "\u062a\u0639\u062f\u064a\u0644",
"delete": "\u062d\u0630\u0641",
"name": "\u0627\u0644\u0627\u0633\u0645",
"profile_whoops": "\u0639\u0630\u0631\u0627\u064b!",
"profile_something_wrong": "\u062d\u062f\u062b \u062e\u0637\u0623 \u0645\u0627!",
"profile_try_again": "\u062d\u062f\u062b \u062e\u0637\u0623 \u0645\u0627. \u064a\u0631\u062c\u0649 \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062e\u0631\u0649.",
"profile_oauth_clients": "\u0639\u0645\u0644\u0627\u0621 OAuth",
"profile_oauth_no_clients": "\u0644\u0645 \u062a\u0642\u0645 \u0628\u0625\u0646\u0634\u0627\u0621 \u0623\u064a \u0639\u0645\u0644\u0627\u0621 OAuth.",
"profile_oauth_clients_header": "\u0627\u0644\u0639\u0645\u0644\u0627\u0621",
"profile_oauth_client_id": "\u0645\u0639\u0631\u0651\u0641 \u0627\u0644\u0639\u0645\u064a\u0644",
"profile_oauth_client_name": "\u0627\u0644\u0627\u0633\u0645",
"profile_oauth_client_secret": "\u0627\u0644\u0633\u0631",
"profile_oauth_create_new_client": "\u0625\u0646\u0634\u0627\u0621 \u0639\u0645\u064a\u0644 \u062c\u062f\u064a\u062f",
"profile_oauth_create_client": "\u0625\u0646\u0634\u0627\u0621 \u0639\u0645\u064a\u0644",
"profile_oauth_edit_client": "\u062a\u0639\u062f\u064a\u0644 \u0627\u0644\u0639\u0645\u064a\u0644",
"profile_oauth_name_help": "\u0634\u064a\u0621 \u0633\u064a\u062a\u0639\u0631\u0641 \u0639\u0644\u064a\u0647 \u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u0648\u0646 \u0648\u064a\u062b\u0642\u0648\u0646 \u0628\u0647.",
"profile_oauth_redirect_url": "\u0631\u0627\u0628\u0637 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u062a\u0648\u062c\u064a\u0647",
"profile_oauth_clients_external_auth": "\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u062e\u062f\u0645 \u0645\u0648\u0641\u0631 \u0645\u0635\u0627\u062f\u0642\u0629 \u062e\u0627\u0631\u062c\u064a \u0645\u062b\u0644 Authelia\u060c \u0641\u0644\u0646 \u062a\u0639\u0645\u0644 \u0639\u0645\u0644\u0627\u0621 OAuth. \u064a\u0645\u0643\u0646\u0643 \u0641\u0642\u0637 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0631\u0645\u0648\u0632 \u0627\u0644\u0648\u0635\u0648\u0644 \u0627\u0644\u0634\u062e\u0635\u064a\u0629.",
"profile_oauth_redirect_url_help": "\u0631\u0627\u0628\u0637 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u062a\u0648\u062c\u064a\u0647 \u0644\u062a\u0637\u0628\u064a\u0642\u0643.",
"profile_authorized_apps": "\u0627\u0644\u062a\u0637\u0628\u064a\u0642\u0627\u062a \u0627\u0644\u0645\u0635\u0631\u062d \u0628\u0647\u0627",
"profile_authorized_clients": "\u0627\u0644\u0639\u0645\u0644\u0627\u0621 \u0627\u0644\u0645\u0635\u0631\u062d \u0644\u0647\u0645",
"profile_scopes": "\u0627\u0644\u0646\u0637\u0627\u0642\u0627\u062a",
"profile_revoke": "\u0625\u0644\u063a\u0627\u0621",
"profile_personal_access_tokens": "\u0631\u0645\u0648\u0632 \u0627\u0644\u0648\u0635\u0648\u0644 \u0627\u0644\u0634\u062e\u0635\u064a\u0629",
"profile_personal_access_token": "\u0631\u0645\u0632 \u0627\u0644\u0648\u0635\u0648\u0644 \u0627\u0644\u0634\u062e\u0635\u064a",
"profile_personal_access_token_explanation": "\u0647\u0630\u0627 \u0647\u0648 \u0631\u0645\u0632 \u0627\u0644\u0648\u0635\u0648\u0644 \u0627\u0644\u0634\u062e\u0635\u064a \u0627\u0644\u062c\u062f\u064a\u062f \u0627\u0644\u062e\u0627\u0635 \u0628\u0643. \u0647\u0630\u0647 \u0647\u064a \u0627\u0644\u0645\u0631\u0629 \u0627\u0644\u0648\u062d\u064a\u062f\u0629 \u0627\u0644\u062a\u064a \u0633\u064a\u0638\u0647\u0631 \u0641\u064a\u0647\u0627 \u0641\u0644\u0627 \u062a\u0641\u0642\u062f\u0647! \u064a\u0645\u0643\u0646\u0643 \u0627\u0644\u0622\u0646 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0647\u0630\u0627 \u0627\u0644\u0631\u0645\u0632 \u0644\u0625\u062c\u0631\u0627\u0621 \u0637\u0644\u0628\u0627\u062a API.",
"profile_no_personal_access_token": "\u0644\u0645 \u062a\u0642\u0645 \u0628\u0625\u0646\u0634\u0627\u0621 \u0623\u064a \u0631\u0645\u0648\u0632 \u0648\u0635\u0648\u0644 \u0634\u062e\u0635\u064a\u0629.",
"profile_create_new_token": "\u0625\u0646\u0634\u0627\u0621 \u0631\u0645\u0632 \u062c\u062f\u064a\u062f",
"profile_create_token": "\u0625\u0646\u0634\u0627\u0621 \u0631\u0645\u0632",
"profile_create": "\u0625\u0646\u0634\u0627\u0621",
"profile_save_changes": "\u062d\u0641\u0638 \u0627\u0644\u062a\u063a\u064a\u064a\u0631\u0627\u062a",
"default_group_title_name": "(\u063a\u064a\u0631 \u0645\u062c\u0645\u0639)",
"piggy_bank": "\u062d\u0635\u0627\u0644\u0629",
"profile_oauth_client_secret_title": "\u0633\u0631 \u0627\u0644\u0639\u0645\u064a\u0644",
"profile_oauth_client_secret_expl": "\u0647\u0630\u0627 \u0647\u0648 \u0633\u0631 \u0627\u0644\u0639\u0645\u064a\u0644 \u0627\u0644\u062c\u062f\u064a\u062f \u0627\u0644\u062e\u0627\u0635 \u0628\u0643. \u0647\u0630\u0647 \u0647\u064a \u0627\u0644\u0645\u0631\u0629 \u0627\u0644\u0648\u062d\u064a\u062f\u0629 \u0627\u0644\u062a\u064a \u0633\u064a\u0638\u0647\u0631 \u0641\u064a\u0647\u0627 \u0641\u0644\u0627 \u062a\u0641\u0642\u062f\u0647! \u064a\u0645\u0643\u0646\u0643 \u0627\u0644\u0622\u0646 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0647\u0630\u0627 \u0627\u0644\u0633\u0631 \u0644\u0625\u062c\u0631\u0627\u0621 \u0637\u0644\u0628\u0627\u062a API.",
"profile_oauth_confidential": "\u0633\u0631\u064a",
"profile_oauth_confidential_help": "\u064a\u062a\u0637\u0644\u0628 \u0645\u0646 \u0627\u0644\u0639\u0645\u064a\u0644 \u0627\u0644\u0645\u0635\u0627\u062f\u0642\u0629 \u0628\u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0633\u0631. \u064a\u0645\u0643\u0646 \u0644\u0644\u0639\u0645\u0644\u0627\u0621 \u0627\u0644\u0633\u0631\u064a\u064a\u0646 \u0627\u0644\u0627\u062d\u062a\u0641\u0627\u0638 \u0628\u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u0627\u0639\u062a\u0645\u0627\u062f \u0628\u0637\u0631\u064a\u0642\u0629 \u0622\u0645\u0646\u0629 \u062f\u0648\u0646 \u0643\u0634\u0641\u0647\u0627 \u0644\u0623\u0637\u0631\u0627\u0641 \u063a\u064a\u0631 \u0645\u0635\u0631\u062d \u0644\u0647\u0627. \u0627\u0644\u062a\u0637\u0628\u064a\u0642\u0627\u062a \u0627\u0644\u0639\u0627\u0645\u0629 \u0645\u062b\u0644 \u062a\u0637\u0628\u064a\u0642\u0627\u062a \u0633\u0637\u062d \u0627\u0644\u0645\u0643\u062a\u0628 \u0627\u0644\u0623\u0633\u0627\u0633\u064a\u0629 \u0623\u0648 \u062a\u0637\u0628\u064a\u0642\u0627\u062a SPA JavaScript \u063a\u064a\u0631 \u0642\u0627\u062f\u0631\u0629 \u0639\u0644\u0649 \u0627\u0644\u0627\u062d\u062a\u0641\u0627\u0638 \u0628\u0627\u0644\u0623\u0633\u0631\u0627\u0631 \u0628\u0623\u0645\u0627\u0646.",
"multi_account_warning_unknown": "\u0627\u0639\u062a\u0645\u0627\u062f\u0627\u064b \u0639\u0644\u0649 \u0646\u0648\u0639 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629 \u0627\u0644\u062a\u064a \u062a\u0646\u0634\u0626\u0647\u0627\u060c \u0642\u062f \u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 \u0648\/\u0623\u0648 \u0627\u0644\u0648\u062c\u0647\u0629 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u0645\u0639\u0627\u0645\u0644\u0629.",
"multi_account_warning_withdrawal": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u0633\u062d\u0628.",
"multi_account_warning_deposit": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0648\u062c\u0647\u0629 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u0625\u064a\u062f\u0627\u0639.",
"multi_account_warning_transfer": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 + \u0627\u0644\u0648\u062c\u0647\u0629 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647\u0645\u0627 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u062a\u062d\u0648\u064a\u0644.",
"webhook_trigger_STORE_TRANSACTION": "\u0628\u0639\u062f \u0625\u0646\u0634\u0627\u0621 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629",
"webhook_trigger_UPDATE_TRANSACTION": "\u0628\u0639\u062f \u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629",
"webhook_trigger_DESTROY_TRANSACTION": "\u0628\u0639\u062f \u062d\u0630\u0641 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629",
"webhook_response_TRANSACTIONS": "\u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629",
"webhook_response_ACCOUNTS": "\u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u062d\u0633\u0627\u0628",
"webhook_response_none_NONE": "\u0644\u0627 \u062a\u0648\u062c\u062f \u062a\u0641\u0627\u0635\u064a\u0644",
"webhook_delivery_JSON": "JSON",
"actions": "\u0627\u0644\u0625\u062c\u0631\u0627\u0621\u0627\u062a",
"meta_data": "\u0628\u064a\u0627\u0646\u0627\u062a \u0648\u0635\u0641\u064a\u0629",
"webhook_messages": "\u0631\u0633\u0627\u0644\u0629 \u0648\u064a\u0628 \u0647\u0648\u0643",
"inactive": "\u063a\u064a\u0631 \u0646\u0634\u0637",
"no_webhook_messages": "\u0644\u0627 \u062a\u0648\u062c\u062f \u0631\u0633\u0627\u0626\u0644 \u0648\u064a\u0628 \u0647\u0648\u0643",
"inspect": "\u062a\u0641\u0642\u062f",
"create_new_webhook": "\u0625\u0646\u0634\u0627\u0621 \u0648\u064a\u0628 \u0647\u0648\u0643 \u062c\u062f\u064a\u062f",
"webhooks": "\u0648\u064a\u0628 \u0647\u0648\u0643\u0633",
"webhook_trigger_form_help": "\u062d\u062f\u062f \u0627\u0644\u062d\u062f\u062b \u0627\u0644\u0630\u064a \u0633\u064a\u0634\u063a\u0644 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643",
"webhook_response_form_help": "\u062d\u062f\u062f \u0645\u0627 \u064a\u062c\u0628 \u0623\u0646 \u064a\u0631\u0633\u0644\u0647 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643 \u0625\u0644\u0649 \u0627\u0644\u0631\u0627\u0628\u0637.",
"webhook_delivery_form_help": "\u062d\u062f\u062f \u0623\u064a \u0635\u064a\u063a\u0629 \u064a\u062c\u0628 \u0623\u0646 \u064a\u0631\u0633\u0644 \u0628\u0647\u0627 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a.",
"webhook_active_form_help": "\u064a\u062c\u0628 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643 \u0646\u0634\u0637\u0627\u064b \u0648\u0625\u0644\u0627 \u0644\u0646 \u064a\u062a\u0645 \u0627\u0633\u062a\u062f\u0639\u0627\u0624\u0647.",
"edit_webhook_js": "\u062a\u0639\u062f\u064a\u0644 \u0648\u064a\u0628 \u0647\u0648\u0643 \"{title}\"",
"webhook_was_triggered": "\u062a\u0645 \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643 \u0639\u0644\u0649 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629 \u0627\u0644\u0645\u062d\u062f\u062f\u0629. \u064a\u0631\u062c\u0649 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u062d\u062a\u0649 \u062a\u0638\u0647\u0631 \u0627\u0644\u0646\u062a\u0627\u0626\u062c.",
"view_message": "\u0639\u0631\u0636 \u0627\u0644\u0631\u0633\u0627\u0644\u0629",
"view_attempts": "\u0639\u0631\u0636 \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0627\u062a \u0627\u0644\u0641\u0627\u0634\u0644\u0629",
"message_content_title": "\u0645\u062d\u062a\u0648\u0649 \u0631\u0633\u0627\u0644\u0629 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643",
"message_content_help": "\u0647\u0630\u0627 \u0647\u0648 \u0645\u062d\u062a\u0648\u0649 \u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u0627\u0644\u062a\u064a \u062a\u0645 \u0625\u0631\u0633\u0627\u0644\u0647\u0627 (\u0623\u0648 \u0645\u062d\u0627\u0648\u0644\u0629 \u0625\u0631\u0633\u0627\u0644\u0647\u0627) \u0628\u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0647\u0630\u0627 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643.",
"attempt_content_title": "\u0645\u062d\u0627\u0648\u0644\u0627\u062a \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643",
"attempt_content_help": "\u0647\u0630\u0647 \u0647\u064a \u062c\u0645\u064a\u0639 \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0627\u062a \u063a\u064a\u0631 \u0627\u0644\u0646\u0627\u062c\u062d\u0629 \u0644\u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u0644\u0625\u0631\u0633\u0627\u0644\u0647\u0627 \u0625\u0644\u0649 \u0627\u0644\u0631\u0627\u0628\u0637 \u0627\u0644\u0645\u062d\u062f\u062f. \u0628\u0639\u062f \u0641\u062a\u0631\u0629\u060c \u0633\u064a\u062a\u0648\u0642\u0641 Firefly III \u0639\u0646 \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0629.",
"no_attempts": "\u0644\u0627 \u062a\u0648\u062c\u062f \u0645\u062d\u0627\u0648\u0644\u0627\u062a \u063a\u064a\u0631 \u0646\u0627\u062c\u062d\u0629. \u0647\u0630\u0627 \u0623\u0645\u0631 \u062c\u064a\u062f!",
"webhook_attempt_at": "\u0645\u062d\u0627\u0648\u0644\u0629 \u0641\u064a {moment}",
"logs": "\u0627\u0644\u0633\u062c\u0644\u0627\u062a",
"response": "\u0627\u0644\u0627\u0633\u062a\u062c\u0627\u0628\u0629",
"visit_webhook_url": "\u0632\u064a\u0627\u0631\u0629 \u0631\u0627\u0628\u0637 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643",
"reset_webhook_secret": "\u0625\u0639\u0627\u062f\u0629 \u062a\u0639\u064a\u064a\u0646 \u0633\u0631 \u0627\u0644\u0648\u064a\u0628 \u0647\u0648\u0643",
"header_exchange_rates": "\u0623\u0633\u0639\u0627\u0631 \u0627\u0644\u0635\u0631\u0641",
"exchange_rates_intro": "\u064a\u062f\u0639\u0645 Firefly III \u062a\u0646\u0632\u064a\u0644 \u0648\u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0623\u0633\u0639\u0627\u0631 \u0627\u0644\u0635\u0631\u0641. \u0627\u0642\u0631\u0623 \u0627\u0644\u0645\u0632\u064a\u062f \u0639\u0646 \u0630\u0644\u0643 \u0641\u064a <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">\u0627\u0644\u0648\u062b\u0627\u0626\u0642<\/a>.",
"exchange_rates_from_to": "\u0628\u064a\u0646 {from} \u0648{to} (\u0648\u0628\u0627\u0644\u0639\u0643\u0633)",
"exchange_rates_intro_rates": "\u064a\u0633\u062a\u062e\u062f\u0645 Firefly III \u0623\u0633\u0639\u0627\u0631 \u0627\u0644\u0635\u0631\u0641 \u0627\u0644\u062a\u0627\u0644\u064a\u0629. \u064a\u062a\u0645 \u062d\u0633\u0627\u0628 \u0627\u0644\u0639\u0643\u0633 \u062a\u0644\u0642\u0627\u0626\u064a\u0627\u064b \u0625\u0630\u0627 \u0644\u0645 \u064a\u062a\u0645 \u062a\u0648\u0641\u064a\u0631\u0647. \u0625\u0630\u0627 \u0644\u0645 \u064a\u0648\u062c\u062f \u0633\u0639\u0631 \u0635\u0631\u0641 \u0644\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629\u060c \u0633\u064a\u0639\u0648\u062f Firefly III \u0628\u0627\u0644\u0632\u0645\u0646 \u0644\u0644\u0639\u062b\u0648\u0631 \u0639\u0644\u0649 \u0648\u0627\u062d\u062f. \u0625\u0630\u0627 \u0644\u0645 \u064a\u0648\u062c\u062f \u0623\u064a \u0633\u0639\u0631\u060c \u0633\u064a\u062a\u0645 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0627\u0644\u0633\u0639\u0631 \"1\".",
"header_exchange_rates_rates": "\u0623\u0633\u0639\u0627\u0631 \u0627\u0644\u0635\u0631\u0641",
"header_exchange_rates_table": "\u062c\u062f\u0648\u0644 \u0623\u0633\u0639\u0627\u0631 \u0627\u0644\u0635\u0631\u0641",
"help_rate_form": "\u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u064a\u0648\u0645\u060c \u0643\u0645 {to} \u0633\u062a\u062d\u0635\u0644 \u0645\u0642\u0627\u0628\u0644 \u0648\u0627\u062d\u062f {from}\u061f",
"add_new_rate": "\u0625\u0636\u0627\u0641\u0629 \u0633\u0639\u0631 \u0635\u0631\u0641 \u062c\u062f\u064a\u062f",
"save_new_rate": "\u062d\u0641\u0638 \u0633\u0639\u0631 \u062c\u062f\u064a\u062f"
},
"form": {
"url": "URL",
"active": "Active",
"interest_date": "Interest date",
"administration_currency": "Primary currency",
"title": "Title",
"date": "Date",
"book_date": "Book date",
"process_date": "Processing date",
"due_date": "Due date",
"foreign_amount": "Foreign amount",
"payment_date": "Payment date",
"invoice_date": "Invoice date",
"internal_reference": "Internal reference",
"webhook_response": "Response",
"webhook_trigger": "Trigger",
"webhook_delivery": "Delivery",
"from_currency_to_currency": "{from} &rarr; {to}",
"to_currency_from_currency": "{to} &rarr; {from}",
"rate": "Rate"
},
"list": {
"title": "Title",
"active": "Is active?",
"primary_currency": "Primary currency",
"trigger": "Trigger",
"response": "Response",
"delivery": "Delivery",
"url": "URL",
"secret": "Secret"
},
"config": {
"html_language": "ar",
"date_time_fns": "MMMM do, yyyy @ HH:mm:ss"
}
}

View File

@@ -3,8 +3,8 @@
"administrations_page_title": "Finanzverwaltungen",
"administrations_index_menu": "Finanzverwaltung",
"expires_at": "G\u00fcltig bis",
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its primary currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
"administration_currency_form_help": "It may take a long time for the page to load if you change the primary currency because transaction may need to be converted to your (new) primary currency.",
"temp_administrations_introduction": "Firefly III erh\u00e4lt in K\u00fcrze die M\u00f6glichkeit, mehrere Finanzverwaltungen zu verwalten. Derzeit ist nur eine einzige verf\u00fcgbar. Sie k\u00f6nnen den Titel dieser Verwaltung und ihre Landesw\u00e4hrung festlegen. Dies ersetzt die bisherige Einstellung, bei der Sie Ihre \u201eStandardw\u00e4hrung\u201c festlegen mussten. Diese Einstellung ist nun an die Finanzverwaltung gebunden und kann je nach Verwaltung unterschiedlich sein.",
"administration_currency_form_help": "Das Laden der Seite kann lange dauern, wenn Sie die Hauptw\u00e4hrung \u00e4ndern, da Buchungen m\u00f6glicherweise in Ihre (neue) Hauptw\u00e4hrung umgerechnet werden m\u00fcssen.",
"administrations_page_edit_sub_title_js": "Finanzverwaltung \u201e{title}\u201c bearbeiten",
"table": "Tabelle",
"welcome_back": "\u00dcberblick",
@@ -102,7 +102,7 @@
"profile_oauth_client_secret_title": "Client Secret",
"profile_oauth_client_secret_expl": "Hier ist Ihr neuer pers\u00f6nlicher Zugangsschl\u00fcssel. Dies ist das einzige Mal, dass er angezeigt wird, also verlieren Sie ihn nicht! Sie k\u00f6nnen diesen Token jetzt verwenden, um API-Anfragen zu stellen.",
"profile_oauth_confidential": "Vertraulich",
"profile_oauth_confidential_help": "Require the client to authenticate with a secret. Confidential clients can hold credentials in a secure way without exposing them to unauthorized parties. Public applications, such as primary desktop or JavaScript SPA applications, are unable to hold secrets securely.",
"profile_oauth_confidential_help": "Verlangen Sie vom Client, sich mit einem Geheimnis zu authentifizieren. Vertrauliche Clients k\u00f6nnen Anmeldedaten auf sichere Weise speichern, ohne sie Unbefugten zug\u00e4nglich zu machen. \u00d6ffentliche Anwendungen, wie prim\u00e4re Desktop- oder JavaScript-SPA-Anwendungen, sind nicht f\u00e4hig, Geheimnisse sicher zu speichern.",
"multi_account_warning_unknown": "Abh\u00e4ngig von der Art der Buchung, die Sie anlegen, kann das Quell- und\/oder Zielkonto nachfolgender Aufteilungen durch das \u00fcberschrieben werden, was in der ersten Aufteilung der Buchung definiert wurde.",
"multi_account_warning_withdrawal": "Bedenken Sie, dass das Quellkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Abhebung definiert ist, au\u00dfer Kraft gesetzt wird.",
"multi_account_warning_deposit": "Bedenken Sie, dass das Zielkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Einnahmen definiert ist, au\u00dfer Kraft gesetzt wird.",
@@ -154,7 +154,7 @@
"url": "URL",
"active": "Aktiv",
"interest_date": "Zinstermin",
"administration_currency": "Primary currency",
"administration_currency": "Standardw\u00e4hrung",
"title": "Titel",
"date": "Datum",
"book_date": "Buchungsdatum",
@@ -174,7 +174,7 @@
"list": {
"title": "Titel",
"active": "Aktiv?",
"primary_currency": "Primary currency",
"primary_currency": "Standardw\u00e4hrung",
"trigger": "Ausl\u00f6ser",
"response": "Antwort",
"delivery": "Zustellung",

View File

@@ -3,8 +3,8 @@
"administrations_page_title": "Ustawienia finansowe",
"administrations_index_menu": "Ustawienia finansowe",
"expires_at": "Expires at",
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its primary currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
"administration_currency_form_help": "It may take a long time for the page to load if you change the primary currency because transaction may need to be converted to your (new) primary currency.",
"temp_administrations_introduction": "Firefly III wkr\u00f3tce uzyska mo\u017cliwo\u015b\u0107 zarz\u0105dzania wieloma ustawieniami finansowymi. W tej chwili jest tylko jedno domy\u015blne ustawienie. Mo\u017cesz ustawi\u0107 jego tytu\u0142 i walut\u0119 g\u0142\u00f3wn\u0105. To zast\u0119puje poprzednie ustawienia, w kt\u00f3rym mo\u017cna by\u0142o ustawi\u0107 \"domy\u015bln\u0105 walut\u0119\". Jest ona obecnie powi\u0105zana z wybranym ustawieniem finansowym i mo\u017ce by\u0107 zmienione w tej zak\u0142adce.",
"administration_currency_form_help": "Wczytywanie strony mo\u017ce zaj\u0105\u0107 du\u017co czasu, je\u015bli zmienisz walut\u0119 g\u0142\u00f3wn\u0105, poniewa\u017c transakcja mo\u017ce wymaga\u0107 przewalutowania na (now\u0105) walut\u0119 g\u0142\u00f3wn\u0105.",
"administrations_page_edit_sub_title_js": "Edytuj ustawienia finansowe \"{title}\"",
"table": "Tabela",
"welcome_back": "Co jest grane?",
@@ -102,7 +102,7 @@
"profile_oauth_client_secret_title": "Sekret klienta",
"profile_oauth_client_secret_expl": "Oto tw\u00f3j nowy sekret klienta. Jest to jedyny raz, gdy zostanie wy\u015bwietlony, wi\u0119c nie zgub go! Mo\u017cesz teraz u\u017cy\u0107 tego sekretu, aby wykona\u0107 zapytania API.",
"profile_oauth_confidential": "Poufne",
"profile_oauth_confidential_help": "Require the client to authenticate with a secret. Confidential clients can hold credentials in a secure way without exposing them to unauthorized parties. Public applications, such as primary desktop or JavaScript SPA applications, are unable to hold secrets securely.",
"profile_oauth_confidential_help": "Wymagaj od klienta uwierzytelnienia za pomoc\u0105 sekretu. Poufni klienci mog\u0105 przechowywa\u0107 po\u015bwiadczenia w bezpieczny spos\u00f3b bez nara\u017cania ich na dost\u0119p przez nieuprawnione strony. Publiczne aplikacje, takie jak natywne aplikacje desktopowe lub JavaScript SPA, nie s\u0105 w stanie bezpiecznie trzyma\u0107 sekret\u00f3w.",
"multi_account_warning_unknown": "W zale\u017cno\u015bci od rodzaju transakcji, kt\u00f3r\u0105 tworzysz, konto \u017ar\u00f3d\u0142owe i\/lub docelowe kolejnych podzia\u0142\u00f3w mo\u017ce zosta\u0107 ustawione na konto zdefiniowane w pierwszym podziale transakcji.",
"multi_account_warning_withdrawal": "Pami\u0119taj, \u017ce konto \u017ar\u00f3d\u0142owe kolejnych podzia\u0142\u00f3w zostanie ustawione na konto zdefiniowane w pierwszym podziale wyp\u0142aty.",
"multi_account_warning_deposit": "Pami\u0119taj, \u017ce konto docelowe kolejnych podzia\u0142\u00f3w zostanie ustawione na konto zdefiniowane w pierwszym podziale wp\u0142aty.",
@@ -154,7 +154,7 @@
"url": "URL",
"active": "Aktywny",
"interest_date": "Data odsetek",
"administration_currency": "Primary currency",
"administration_currency": "Waluta g\u0142\u00f3wna",
"title": "Tytu\u0142",
"date": "Data",
"book_date": "Data ksi\u0119gowania",
@@ -174,7 +174,7 @@
"list": {
"title": "Tytu\u0142",
"active": "Jest aktywny?",
"primary_currency": "Primary currency",
"primary_currency": "Waluta g\u0142\u00f3wna",
"trigger": "Wyzwalacz",
"response": "Odpowied\u017a",
"delivery": "Dor\u0119czenie",

View File

@@ -183,6 +183,6 @@
},
"config": {
"html_language": "sv",
"date_time_fns": "MMMM do, yyyy @ HH:mm:ss"
"date_time_fns": "D MMMM YYYY @ HH:mm:ss"
}
}

View File

@@ -30,7 +30,7 @@ export default class Put {
* @returns {Promise<AxiosResponse<any>>}
*/
put(identifier, params) {
return api.put('/api/v2/accounts/' + identifier, params);
return api.put('/api/v1/accounts/' + identifier, params);
}
}

View File

@@ -29,7 +29,7 @@ export default class Get {
* @returns {Promise<AxiosResponse<any>>}
*/
list(params) {
return api.get('/api/v2/currencies', {params: params});
return api.get('/api/v1/currencies', {params: params});
}
}

View File

@@ -30,7 +30,7 @@ export default class Get {
* @returns {Promise<AxiosResponse<any>>}
*/
show(identifier, params) {
return api.get('/api/v2/user-groups/' + identifier, {params: params});
return api.get('/api/v1/user-groups/' + identifier, {params: params});
}
/**
@@ -39,7 +39,7 @@ export default class Get {
* @returns {Promise<AxiosResponse<any>>}
*/
index(params) {
return api.get('/api/v2/user-groups', {params: params});
return api.get('/api/v1/user-groups', {params: params});
}
}

View File

@@ -22,12 +22,12 @@ import {api} from "../../../../boot/axios";
export default class Post {
post(submission) {
let url = './api/v2/user-groups';
let url = './api/v1/user-groups';
return api.post(url, submission);
}
use(groupId) {
let url = './api/v2/user-groups/' + groupId + '/use';
let url = './api/v1/user-groups/' + groupId + '/use';
return api.post(url, {});
}
}

View File

@@ -22,7 +22,7 @@ import {api} from "../../../../boot/axios";
export default class Put {
put(submission, params) {
let url = '/api/v2/user-groups/' + parseInt(params.id);
let url = '/api/v1/user-groups/' + parseInt(params.id);
return api.put(url, submission);
}
}

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ export default class Get {
* @returns {Promise<AxiosResponse<any>>}
*/
show(identifier, params) {
return api.get('/api/v2/accounts/' + identifier, {params: params});
return api.get('/api/v1/accounts/' + identifier, {params: params});
}
/**
@@ -42,7 +42,7 @@ export default class Get {
index(params) {
// first, check API in some consistent manner.
// then, load if necessary.
const cacheKey = getCacheKey('/api/v2/accounts', params);
const cacheKey = getCacheKey('/api/v1/accounts', params);
const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey);
@@ -53,7 +53,7 @@ export default class Get {
// if not, store in cache and then return res.
return api.get('/api/v2/accounts', {params: params}).then(response => {
return api.get('/api/v1/accounts', {params: params}).then(response => {
console.log('Cache is invalid, return fresh.');
window.store.set(cacheKey, response.data);
return Promise.resolve({data: response.data.data, meta: response.data.meta});
@@ -77,6 +77,6 @@ export default class Get {
newParams.end = format(params.end, 'y-MM-dd');
}
return api.get('/api/v2/accounts/' + identifier + '/transactions', {params: newParams});
return api.get('/api/v1/accounts/' + identifier + '/transactions', {params: newParams});
}
}

View File

@@ -30,7 +30,7 @@ export default class Put {
* @returns {Promise<AxiosResponse<any>>}
*/
put(identifier, params) {
return api.put('/api/v2/accounts/' + identifier, params);
return api.put('/api/v1/accounts/' + identifier, params);
}
}

Some files were not shown because too many files have changed in this diff Show More