Compare commits

..

88 Commits

Author SHA1 Message Date
github-actions[bot]
4b0597d19a Merge pull request #10728 from firefly-iii/release-1754679825
🤖 Automatically merge the PR into the develop branch.
2025-08-08 21:03:55 +02:00
JC5
92f534bcb3 🤖 Auto commit for release 'develop' on 2025-08-08 2025-08-08 21:03:45 +02:00
James Cole
76e91be4dc Optimize array. 2025-08-08 20:59:24 +02:00
James Cole
deca4fed56 Clean up API and display of transactions. 2025-08-08 20:18:04 +02:00
James Cole
73512b0365 Add a basic singleton to save on queries. 2025-08-08 15:44:15 +02:00
github-actions[bot]
aaffc125e7 Merge pull request #10724 from firefly-iii/release-1754630264
🤖 Automatically merge the PR into the develop branch.
2025-08-08 07:17:53 +02:00
JC5
41a48c39a0 🤖 Auto commit for release 'develop' on 2025-08-08 2025-08-08 07:17:44 +02:00
James Cole
2d96bd84b5 Fix #9640 2025-08-08 06:43:05 +02:00
James Cole
ad1c1d2254 Fix #10071 2025-08-08 06:42:54 +02:00
github-actions[bot]
813206766d Merge pull request #10723 from firefly-iii/release-1754591858
🤖 Automatically merge the PR into the develop branch.
2025-08-07 20:37:46 +02:00
JC5
bb25d4a82a 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 20:37:38 +02:00
James Cole
f3b78beecc Fix another null 2025-08-07 20:33:24 +02:00
github-actions[bot]
64073768fe Merge pull request #10722 from firefly-iii/release-1754590638
🤖 Automatically merge the PR into the develop branch.
2025-08-07 20:17:25 +02:00
JC5
fe6dd0f901 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 20:17:19 +02:00
James Cole
aac8d11ff6 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-07 20:13:35 +02:00
James Cole
afa99a35b5 Fix NULLs 2025-08-07 20:13:29 +02:00
James Cole
e9cb0a51d7 Catch NULL. 2025-08-07 20:11:46 +02:00
github-actions[bot]
9fbcccfd02 Merge pull request #10721 from firefly-iii/release-1754589876
🤖 Automatically merge the PR into the develop branch.
2025-08-07 20:04:43 +02:00
JC5
468c9c9d56 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 20:04:36 +02:00
James Cole
f76b27a73d Fix #10565 and fix #10600 2025-08-07 19:59:02 +02:00
James Cole
579fe81616 Fix #10656 2025-08-07 19:52:12 +02:00
James Cole
ec9ba53690 Fix #10678 2025-08-07 19:48:00 +02:00
James Cole
85337c53d4 Fix currency collection. 2025-08-07 19:37:36 +02:00
James Cole
eb6d585bb2 Fix a variety of code. 2025-08-07 19:09:25 +02:00
James Cole
378ffbc609 Fix #10720 2025-08-07 17:55:25 +02:00
James Cole
3b3c8e5bcd Add object group to various items. 2025-08-07 07:46:49 +02:00
github-actions[bot]
75cbdb6a57 Merge pull request #10719 from firefly-iii/release-1754540665
🤖 Automatically merge the PR into the develop branch.
2025-08-07 06:24:32 +02:00
JC5
cdaff0d983 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 06:24:25 +02:00
James Cole
dda3863889 Fix account_id key. 2025-08-07 06:20:36 +02:00
github-actions[bot]
57dc423b3f Merge pull request #10718 from firefly-iii/release-1754540258
🤖 Automatically merge the PR into the develop branch.
2025-08-07 06:17:51 +02:00
JC5
c0570bc3b2 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 06:17:38 +02:00
James Cole
65110d1666 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-07 06:12:02 +02:00
James Cole
5a10b29402 Set to zero instead of null. 2025-08-07 06:11:56 +02:00
github-actions[bot]
028544ca2e Merge pull request #10717 from firefly-iii/release-1754539541
🤖 Automatically merge the PR into the develop branch.
2025-08-07 06:05:53 +02:00
JC5
af46729372 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 06:05:41 +02:00
James Cole
4c7789a668 Add missing access rights. 2025-08-07 06:01:14 +02:00
James Cole
292908048c Add enrichment to recurring 2025-08-07 05:59:26 +02:00
James Cole
e300314e05 Catch NULL. 2025-08-07 05:56:31 +02:00
James Cole
4f1f360346 Add primary to array. 2025-08-07 05:55:15 +02:00
James Cole
bff856aeff Rename method, remove 0. 2025-08-07 05:52:56 +02:00
James Cole
7f5a1bda8d Catch null 2025-08-06 20:53:30 +02:00
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
129 changed files with 4440 additions and 1853 deletions

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Chart\ChartData;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\AccountBalanceGrouped;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
use Illuminate\Http\JsonResponse;
/**
* Class BalanceController
*/
class BalanceController extends Controller
{
use CleansChartData;
use CollectsAccountsFromFilter;
private ChartData $chartData;
private GroupCollectorInterface $collector;
private AccountRepositoryInterface $repository;
// private TransactionCurrency $default;
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->collector = app(GroupCollectorInterface::class);
$userGroup = $this->validateUserGroup($request);
$this->repository->setUserGroup($userGroup);
$this->collector->setUserGroup($userGroup);
$this->chartData = new ChartData();
// $this->default = app('amount')->getPrimaryCurrency();
return $next($request);
}
);
}
/**
* The code is practically a duplicate of ReportController::operations.
*
* Currency is up to the account/transactions in question, but conversion to the default
* currency is possible.
*
* If the transaction being processed is already in native currency OR if the
* foreign amount is in the native currency, the amount will not be converted.
*
* @throws FireflyException
*/
public function balance(ChartRequest $request): JsonResponse
{
$queryParameters = $request->getParameters();
$accounts = $this->getAccountList($queryParameters);
// prepare for currency conversion and data collection:
/** @var TransactionCurrency $primary */
$primary = Amount::getPrimaryCurrency();
// get journals for entire period:
$this->collector->setRange($queryParameters['start'], $queryParameters['end'])
->withAccountInformation()
->setXorAccounts($accounts)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::RECONCILIATION->value, TransactionTypeEnum::TRANSFER->value])
;
$journals = $this->collector->getExtractedJournals();
$object = new AccountBalanceGrouped();
$object->setPreferredRange($queryParameters['period']);
$object->setPrimary($primary);
$object->setAccounts($accounts);
$object->setJournals($journals);
$object->setStart($queryParameters['start']);
$object->setEnd($queryParameters['end']);
$object->groupByCurrencyAndPeriod();
$data = $object->convertToChartData();
foreach ($data as $entry) {
$this->chartData->add($entry);
}
return response()->json($this->chartData->render());
}
}

View File

@@ -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

@@ -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

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Transformers\ExchangeRateTransformer;
@@ -39,7 +40,7 @@ class IndexController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'currency_exchange_rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
@@ -41,7 +42,7 @@ class ShowController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreRequest;
use FireflyIII\Api\V1\Controllers\Controller;
@@ -37,7 +38,7 @@ class StoreController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\UpdateRequest;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
@@ -37,7 +38,7 @@ class UpdateController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

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

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Recurrence\StoreRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -69,6 +71,13 @@ class StoreController extends Controller
$recurrence = $this->repository->store($data);
$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

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Recurrence\UpdateRequest;
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 League\Fractal\Resource\Item;
@@ -67,6 +69,13 @@ class UpdateController extends Controller
$recurrence = $this->repository->update($recurrence, $data);
$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,8 @@ 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\RecurringEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AccountTransformer;
@@ -107,8 +109,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 +190,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 +229,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);
@@ -270,17 +277,24 @@ class ListController extends Controller
}
);
$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.currencies.recurrences', [$currency->code]).$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);

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

@@ -248,10 +248,10 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
'value_parsed' => Amount::formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale',
'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
.' + '.app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
'sub_title' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
.' + '.Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
];
$return[] = [
'key' => sprintf('spent-in-%s', $currency->code),
@@ -261,7 +261,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
'value_parsed' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -273,7 +273,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
'value_parsed' => Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -289,10 +289,10 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
'value_parsed' => Amount::formatAnything($currency, '0', false),
'local_icon' => 'balance-scale',
'sub_title' => app('amount')->formatAnything($currency, '0', false)
.' + '.app('amount')->formatAnything($currency, '0', false),
'sub_title' => Amount::formatAnything($currency, '0', false)
.' + '.Amount::formatAnything($currency, '0', false),
];
$return[] = [
'key' => sprintf('spent-in-%s', $currency->code),
@@ -302,7 +302,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
'value_parsed' => Amount::formatAnything($currency, '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -314,7 +314,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
'value_parsed' => Amount::formatAnything($currency, '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -405,7 +405,7 @@ class BasicController extends Controller
'currency_code' => $info['code'],
'currency_symbol' => $info['symbol'],
'currency_decimal_places' => $info['decimal_places'],
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'local_icon' => 'check',
'sub_title' => '',
];
@@ -424,7 +424,7 @@ class BasicController extends Controller
'currency_code' => $info['code'],
'currency_symbol' => $info['symbol'],
'currency_decimal_places' => $info['decimal_places'],
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'local_icon' => 'calendar-o',
'sub_title' => '',
];
@@ -443,7 +443,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'local_icon' => 'check',
'sub_title' => '',
];
@@ -455,7 +455,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'local_icon' => 'calendar-o',
'sub_title' => '',
];
@@ -493,14 +493,9 @@ class BasicController extends Controller
'currency_code' => $currencies[$currencyId]->code,
'currency_symbol' => $currencies[$currencyId]->symbol,
'currency_decimal_places' => $currencies[$currencyId]->decimal_places,
'value_parsed' => app('amount')->formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
'value_parsed' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
'local_icon' => 'money',
'sub_title' => app('amount')->formatFlat(
$currencies[$currencyId]->symbol,
$currencies[$currencyId]->decimal_places,
$availableBudget,
false
),
'sub_title' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
];
}
foreach ($spent as $row) {
@@ -529,18 +524,14 @@ class BasicController extends Controller
'currency_code' => $row['currency_code'],
'currency_symbol' => $row['currency_symbol'],
'currency_decimal_places' => $row['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
'local_icon' => 'money',
'sub_title' => app('amount')->formatFlat(
$row['currency_symbol'],
$row['currency_decimal_places'],
$perDay,
false
),
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
];
}
unset($leftToSpend);
if (0 === count($return)) {
$days = (int) $start->diffInDays($end, true) + 1;
// a small trick to get every expense in this period, regardless of budget.
$spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection());
foreach ($spent as $row) {
@@ -563,14 +554,9 @@ class BasicController extends Controller
'currency_code' => $row['currency_code'],
'currency_symbol' => $row['currency_symbol'],
'currency_decimal_places' => $row['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
'local_icon' => 'money',
'sub_title' => app('amount')->formatFlat(
$row['currency_symbol'],
$row['currency_decimal_places'],
$perDay,
false
),
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
];
}
@@ -587,9 +573,9 @@ class BasicController extends Controller
// 'currency_code' => $currency->code,
// 'currency_symbol' => $currency->symbol,
// 'currency_decimal_places' => $currency->decimal_places,
// 'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
// 'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
// 'local_icon' => 'money',
// 'sub_title' => app('amount')->formatFlat(
// 'sub_title' => Amount::formatFlat(
// $currency->symbol,
// $currency->decimal_places,
// '0',
@@ -642,7 +628,7 @@ class BasicController extends Controller
'currency_code' => $data['currency_code'],
'currency_symbol' => $data['currency_symbol'],
'currency_decimal_places' => $data['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
'value_parsed' => Amount::formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
'local_icon' => 'line-chart',
'sub_title' => '',
];
@@ -656,7 +642,7 @@ class BasicController extends Controller
'currency_code' => $this->primaryCurrency->code,
'currency_symbol' => $this->primaryCurrency->symbol,
'currency_decimal_places' => $this->primaryCurrency->decimal_places,
'value_parsed' => app('amount')->formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
'value_parsed' => Amount::formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
'local_icon' => 'line-chart',
'sub_title' => '',
];

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

@@ -40,6 +40,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Console\Command;
@@ -72,6 +73,8 @@ class CorrectsPrimaryCurrencyAmounts extends Command
/** @var UserGroupRepositoryInterface $repository */
$repository = app(UserGroupRepositoryInterface::class);
Preferences::mark();
/** @var UserGroup $userGroup */
foreach ($repository->getAll() as $userGroup) {
$this->recalculateForGroup($userGroup);
@@ -87,8 +90,7 @@ class CorrectsPrimaryCurrencyAmounts extends Command
$this->recalculateAccounts($userGroup);
// do a check with the group's currency so we can skip some stuff.
Preferences::mark();
$currency = app('amount')->getPrimaryCurrencyByUserGroup($userGroup);
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);

View File

@@ -35,6 +35,6 @@ class UserGroupChangedPrimaryCurrency extends Event
public function __construct(public UserGroup $userGroup)
{
Log::debug('User group changed default currency.');
Log::debug('User group changed primary currency.');
}
}

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

@@ -73,6 +73,7 @@ class PreferencesEventHandler
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$piggyBanks = $repository->getPiggyBanks();
Log::debug(sprintf('Resetting %d piggy bank(s).', $piggyBanks->count()));
/** @var PiggyBank $piggyBank */
foreach ($piggyBanks as $piggyBank) {
@@ -104,6 +105,8 @@ class PreferencesEventHandler
$repository->setUserGroup($userGroup);
$set = $repository->getBudgets();
Log::debug(sprintf('Resetting %d budget(s).', $set->count()));
/** @var Budget $budget */
foreach ($set as $budget) {
foreach ($budget->autoBudgets as $autoBudget) {

View File

@@ -117,7 +117,6 @@ class ShowController extends Controller
Log::debug('Start period overview');
Timer::start('period-overview');
$periods = $this->getAccountPeriodOverview($account, $firstTransaction, $end);
Log::debug('End period overview');
@@ -136,6 +135,7 @@ class ShowController extends Controller
->setAccounts(new Collection([$account]))
->setLimit($pageSize)
->setPage($page)
->withAttachmentInformation()
->withAPIInformation()
->setRange($start, $end)
;

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

@@ -35,6 +35,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\ChartGeneration;
@@ -78,6 +79,7 @@ class AccountController extends Controller
/**
* Shows the balances for all the user's expense accounts (on the front page).
* 2025-08-06 validated for multi (primary) currency
*
* This chart is (multi) currency aware.
*/
@@ -114,8 +116,8 @@ class AccountController extends Controller
// grab all balances
Log::debug(sprintf('expenseAccounts: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('expenseAccounts: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::finalAccountsBalance($accounts, $start);
$endBalances = Steam::finalAccountsBalance($accounts, $end);
$startBalances = Steam::finalAccountsBalance($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
$endBalances = Steam::finalAccountsBalance($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
@@ -157,7 +159,7 @@ class AccountController extends Controller
$tempData[] = [
'name' => $accountNames[$account->id],
'difference' => $diff,
'diff_float' => (float) $diff, // intentional float
'diff_float' => (float)$diff, // intentional float
'currency_id' => $currencies[$searchCode]->id,
];
}
@@ -182,7 +184,7 @@ class AccountController extends Controller
foreach ($currencies as $currencyId => $currency) {
$dataSet
= [
'label' => (string) trans('firefly.spent'),
'label' => (string)trans('firefly.spent'),
'type' => 'bar',
'currency_symbol' => $currency->symbol,
'currency_code' => $currency->code,
@@ -195,7 +197,7 @@ class AccountController extends Controller
foreach ($tempData as $entry) {
$currencyId = $entry['currency_id'];
$name = $entry['name'];
$chartData[$currencyId]['entries'][$name] = (float) $entry['difference'];
$chartData[$currencyId]['entries'][$name] = (float)$entry['difference'];
}
$data = $this->generator->multiSet($chartData);
@@ -223,6 +225,7 @@ class AccountController extends Controller
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($this->convertToPrimary);
$cache->addProperty($end);
$cache->addProperty('chart.account.expense-budget');
if ($cache->has()) {
@@ -231,7 +234,10 @@ class AccountController extends Controller
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$collector->setAccounts(new Collection([$account]))
->setRange($start, $end)
->withBudgetInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
;
$journals = $collector->getExtractedJournals();
$chartData = [];
$result = [];
@@ -239,19 +245,37 @@ class AccountController extends Controller
/** @var array $journal */
foreach ($journals as $journal) {
$budgetId = (int) $journal['budget_id'];
$budgetId = (int)$journal['budget_id'];
$key = sprintf('%d-%d', $budgetId, $journal['currency_id']);
$budgetIds[] = $budgetId;
// currency info:
$currencyId = (int)$journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$field = 'amount';
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
$field = 'pc_amount';
$currencyName = $this->primaryCurrency->name;
$currencySymbol = $this->primaryCurrency->symbol;
$currencyCode = $this->primaryCurrency->code;
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
}
if (!array_key_exists($key, $result)) {
$result[$key] = [
'total' => '0',
'budget_id' => $budgetId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'total' => '0',
'budget_id' => $budgetId,
'currency_name' => $currencyName,
'currency_symbol' => $currencySymbol,
'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces,
];
}
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
}
$names = $this->getBudgetNames($budgetIds);
@@ -259,7 +283,7 @@ class AccountController extends Controller
foreach ($result as $row) {
$budgetId = $row['budget_id'];
$name = $names[$budgetId];
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
}
@@ -289,6 +313,7 @@ class AccountController extends Controller
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToPrimary);
$cache->addProperty('chart.account.expense-category');
if ($cache->has()) {
return response()->json($cache->get());
@@ -305,22 +330,39 @@ class AccountController extends Controller
foreach ($journals as $journal) {
$key = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']);
if (!array_key_exists($key, $result)) {
$result[$key] = [
'total' => '0',
'category_id' => (int) $journal['category_id'],
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
// currency info:
$currencyId = (int)$journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$field = 'amount';
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
$field = 'pc_amount';
$currencyName = $this->primaryCurrency->name;
$currencySymbol = $this->primaryCurrency->symbol;
$currencyCode = $this->primaryCurrency->code;
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
}
$result[$key] = [
'total' => '0',
'category_id' => (int)$journal['category_id'],
'currency_name' => $currencyName,
'currency_code' => $currencyCode,
'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $currencyDecimalPlaces,
];
}
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
}
$names = $this->getCategoryNames(array_keys($result));
foreach ($result as $row) {
$categoryId = $row['category_id'];
$name = $names[$categoryId] ?? '(unknown)';
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
}
@@ -341,11 +383,11 @@ class AccountController extends Controller
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
$defaultSet = $repository->getAccountsByType([AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
// Log::debug('Default set is ', $defaultSet);
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
$frontpage = Preferences::get('frontpageAccounts', $defaultSet);
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
Log::debug('Frontpage preference set is ', $frontpageArray);
if (0 === count($frontpageArray)) {
app('preferences')->set('frontpageAccounts', $defaultSet);
Preferences::set('frontpageAccounts', $defaultSet);
Log::debug('frontpage set is empty!');
}
$accounts = $repository->getAccountsById($frontpageArray);
@@ -375,6 +417,7 @@ class AccountController extends Controller
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($this->convertToPrimary);
$cache->addProperty($end);
$cache->addProperty('chart.account.income-category');
if ($cache->has()) {
@@ -394,22 +437,39 @@ class AccountController extends Controller
foreach ($journals as $journal) {
$key = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']);
if (!array_key_exists($key, $result)) {
$result[$key] = [
'total' => '0',
'category_id' => $journal['category_id'],
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
// currency info:
$currencyId = (int)$journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$field = 'amount';
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
$field = 'pc_amount';
$currencyName = $this->primaryCurrency->name;
$currencySymbol = $this->primaryCurrency->symbol;
$currencyCode = $this->primaryCurrency->code;
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
}
$result[$key] = [
'total' => '0',
'category_id' => $journal['category_id'],
'currency_name' => $currencyName,
'currency_code' => $currencyCode,
'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $currencyDecimalPlaces,
];
}
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
}
$names = $this->getCategoryNames(array_keys($result));
foreach ($result as $row) {
$categoryId = $row['category_id'];
$name = $names[$categoryId] ?? '(unknown)';
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
}
$data = $this->generator->multiCurrencyPieChart($chartData);
@@ -450,7 +510,7 @@ class AccountController extends Controller
// This period depends on the size of the chart
$current = clone $start;
$current = app('navigation')->endOfX($current, $step, null);
$format = (string) trans('config.month_and_day_js', [], $locale);
$format = (string)trans('config.month_and_day_js', [], $locale);
$accountCurrency = $this->accountRepository->getAccountCurrency($account);
$range = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToPrimary);
@@ -512,7 +572,7 @@ class AccountController extends Controller
foreach ($return as $key => $info) {
if ('balance' !== $key && 'pc_balance' !== $key) {
// assume it's a currency:
$setCurrency = $this->currencyRepository->findByCode((string) $key);
$setCurrency = $this->currencyRepository->findByCode((string)$key);
$info['currency_symbol'] = $setCurrency->symbol;
$info['currency_code'] = $setCurrency->code;
$info['label'] = sprintf('%s (%s)', $account->name, $setCurrency->symbol);
@@ -525,7 +585,7 @@ class AccountController extends Controller
if ('pc_balance' === $key) {
$info['currency_symbol'] = $this->primaryCurrency->symbol;
$info['currency_code'] = $this->primaryCurrency->code;
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string) trans('firefly.sum'), $this->primaryCurrency->symbol);
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string)trans('firefly.sum'), $this->primaryCurrency->symbol);
}
$chartData[] = $info;
}
@@ -640,7 +700,7 @@ class AccountController extends Controller
$tempData[] = [
'name' => $accountNames[$account->id],
'difference' => $diff,
'diff_float' => (float) $diff, // intentional float
'diff_float' => (float)$diff, // intentional float
'currency_id' => $currencies[$searchCode]->id,
];
}
@@ -667,7 +727,7 @@ class AccountController extends Controller
foreach ($currencies as $currencyId => $currency) {
$dataSet
= [
'label' => (string) trans('firefly.earned'),
'label' => (string)trans('firefly.earned'),
'type' => 'bar',
'currency_symbol' => $currency->symbol,
'currency_code' => $currency->code,

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 */
@@ -170,14 +179,6 @@ class IndexController extends Controller
$return[$accountId]['target'] = '0';
$return[$accountId]['to_save'] = '0';
}
// calculate new interesting fields:
// $return[$accountId]['left'] -= $array['current_amount'];
// $return[$accountId]['saved'] += $array['current_amount'];
// $return[$accountId]['target'] += $array['target_amount'];
// $return[$accountId]['to_save'] += ($array['target_amount'] - $array['current_amount']);
// $return['account_name'] = $account['name'];
}
}
@@ -193,7 +194,7 @@ class IndexController extends Controller
// loop all accounts in this piggy bank subtract the current amount from "left to save" in the $accounts array.
/** @var array $piggyAccount */
foreach ($piggyBank['accounts'] as $piggyAccount) {
$accountId = $piggyAccount['id'];
$accountId = $piggyAccount['account_id'];
if (array_key_exists($accountId, $accounts)) {
$accounts[$accountId]['left'] = bcsub((string) $accounts[$accountId]['left'], (string) $piggyAccount['current_amount']);
$accounts[$accountId]['saved'] = bcadd((string) $accounts[$accountId]['saved'], (string) $piggyAccount['current_amount']);

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

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use JsonException;
use Carbon\Carbon;
use FireflyIII\Enums\AccountTypeEnum;
@@ -44,7 +45,6 @@ use Illuminate\View\View;
use function Safe\json_decode;
use function Safe\file_get_contents;
use function Safe\strtotime;
/**
* Class PreferencesController.
@@ -270,17 +270,19 @@ class PreferencesController extends Controller
if ($convertToPrimary && !$this->convertToPrimary) {
// set to true!
Log::debug('User sets convertToPrimary to true.');
Preferences::set('convert_to_primary', $convertToPrimary);
Preferences::set('convert_to_primary', true);
$singleton = PreferencesSingleton::getInstance();
$singleton->resetPreferences();
event(new UserGroupChangedPrimaryCurrency(auth()->user()->userGroup));
}
Preferences::set('convert_to_primary', $convertToPrimary);
// 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

@@ -34,7 +34,10 @@ use FireflyIII\Models\RecurrenceRepetition;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Facades\ExpandedForm;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -92,13 +95,20 @@ class EditController extends Controller
throw new FireflyException('This recurring transaction has no meta-data. You will have to delete it and recreate it. Sorry!');
}
// 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(new ParameterBag());
$array = $transformer->transform($recurrence);
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
$bills = app('expandedform')->makeSelectListWithEmpty($this->billRepository->getActiveBills());
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
$bills = ExpandedForm::makeSelectListWithEmpty($this->billRepository->getActiveBills());
/** @var RecurrenceRepetition $repetition */
$repetition = $recurrence->recurrenceRepetitions()->first();

View File

@@ -29,7 +29,9 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@@ -85,6 +87,13 @@ class IndexController extends Controller
$total = $collection->count();
$recurrences = $collection->slice(($page - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrences = $enrichment->enrich($recurrences);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters(new ParameterBag());

View File

@@ -32,8 +32,10 @@ use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\ParameterBag;
@@ -80,6 +82,13 @@ class ShowController extends Controller
{
$repos = app(AttachmentRepositoryInterface::class);
// 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(new ParameterBag());

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Report;
use FireflyIII\Support\Facades\Navigation;
use Throwable;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
@@ -290,11 +291,12 @@ class BudgetController extends Controller
$cache->addProperty('budget-period-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
// return $cache->get();
}
$periods = app('navigation')->listOfPeriods($start, $end);
$keyFormat = app('navigation')->preferredCarbonFormat($start, $end);
$periods = Navigation::listOfPeriods($start, $end);
$keyFormat = Navigation::preferredCarbonFormat($start, $end);
// list expenses for budgets in account(s)
$expenses = $this->opsRepository->listExpenses($start, $end, $accounts);
@@ -303,6 +305,17 @@ class BudgetController extends Controller
foreach ($currency['budgets'] as $budget) {
$count = 0;
foreach ($budget['transaction_journals'] as $journal) {
// #10678
// skip transactions between two asset / liability accounts.
if (
in_array($journal['source_account_type'], config('firefly.valid_currency_account_types'), true)
&& in_array($journal['destination_account_type'], config('firefly.valid_currency_account_types'), true)
) {
continue;
}
++$count;
$key = sprintf('%d-%d', $budget['id'], $currency['currency_id']);
$dateKey = $journal['date']->format($keyFormat);

View File

@@ -270,6 +270,7 @@ class CreateController extends Controller
$data = $request->getRuleData();
$rule = $this->ruleRepos->store($data);
session()->flash('success_url', route('rules.select-transactions', [$rule->id]));
session()->flash('success', (string) trans('firefly.stored_new_rule', ['title' => $rule->title]));
app('preferences')->mark();

View File

@@ -252,6 +252,7 @@ class TagController extends Controller
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
->withAttachmentInformation()
;
$groups = $collector->getPaginatedGroups();
$groups->setPath($path);
@@ -283,6 +284,7 @@ class TagController extends Controller
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
->withAttachmentInformation()
;
$groups = $collector->getPaginatedGroups();
$groups->setPath($path);

View File

@@ -25,16 +25,20 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class ShowController
@@ -57,7 +61,7 @@ class ShowController extends Controller
$this->repository = app(TransactionGroupRepositoryInterface::class);
$this->aleRepository = app(ALERepositoryInterface::class);
app('view')->share('title', (string) trans('firefly.transactions'));
app('view')->share('title', (string)trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-exchange');
return $next($request);
@@ -80,38 +84,62 @@ class ShowController extends Controller
*/
public function show(TransactionGroup $transactionGroup)
{
/** @var User $admin */
$admin = auth()->user();
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($admin)->setTransactionGroup($transactionGroup)->withAPIInformation();
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new NotFoundHttpException();
}
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var null|TransactionJournal $first */
$first = $transactionGroup->transactionJournals()->first(['transaction_journals.*']);
$splits = $transactionGroup->transactionJournals()->count();
$splits = count($selectedGroup['transactions']);
$keys = array_keys($selectedGroup['transactions']);
$first = $selectedGroup['transactions'][array_shift($keys)];
unset($keys);
if (null === $first) {
throw new FireflyException('This transaction is broken :(.');
}
$type = (string) trans(sprintf('firefly.%s', $first->transactionType->type));
$title = 1 === $splits ? $first->description : $transactionGroup->title;
$type = (string)trans(sprintf('firefly.%s', $first['transaction_type_type']));
$title = 1 === $splits ? $first['description'] : $selectedGroup['title'];
$subTitle = sprintf('%s: "%s"', $type, $title);
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters(new ParameterBag());
$groupArray = $transformer->transformObject($transactionGroup);
// do some calculations:
$amounts = $this->getAmounts($groupArray);
$accounts = $this->getAccounts($groupArray);
$amounts = $this->getAmounts($selectedGroup);
$accounts = $this->getAccounts($selectedGroup);
foreach (array_keys($groupArray['transactions']) as $index) {
$groupArray['transactions'][$index]['tags'] = $this->repository->getTagObjects(
(int) $groupArray['transactions'][$index]['transaction_journal_id']
);
foreach (array_keys($selectedGroup['transactions']) as $index) {
$selectedGroup['transactions'][$index]['tags'] = $this->repository->getTagObjects((int)$selectedGroup['transactions'][$index]['transaction_journal_id']);
}
// get audit log entries:
$groupLogEntries = $this->aleRepository->getForObject($transactionGroup);
$logEntries = [];
foreach ($transactionGroup->transactionJournals as $journal) {
$logEntries[$journal->id] = $this->aleRepository->getForObject($journal);
foreach ($selectedGroup['transactions'] as $journal) {
$logEntries[$journal['transaction_journal_id']] = $this->aleRepository->getForId(TransactionJournal::class, $journal['transaction_journal_id']);
}
$events = $this->repository->getPiggyEvents($transactionGroup);
@@ -129,6 +157,7 @@ class ShowController extends Controller
'groupLogEntries',
'subTitle',
'splits',
'selectedGroup',
'groupArray',
'events',
'attachments',
@@ -142,34 +171,38 @@ class ShowController extends Controller
{
$amounts = [];
foreach ($group['transactions'] as $transaction) {
// add normal amount:
$symbol = $transaction['currency_symbol'];
if (!array_key_exists($symbol, $amounts)) {
$amounts[$symbol] = [
'amount' => '0',
'symbol' => $symbol,
'decimal_places' => $transaction['currency_decimal_places'],
];
}
$amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], (string) $transaction['amount']);
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount']
&& 0 !== bccomp(
'0',
(string) $transaction['foreign_amount']
)) {
$amounts[$symbol] ??= [
'amount' => '0',
'symbol' => $symbol,
'decimal_places' => $transaction['currency_decimal_places'],
];
$amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], (string)$transaction['amount']);
// add foreign amount:
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string)$transaction['foreign_amount'])) {
// same for foreign currency:
$foreignSymbol = $transaction['foreign_currency_symbol'];
if (!array_key_exists($foreignSymbol, $amounts)) {
$amounts[$foreignSymbol] = [
'amount' => '0',
'symbol' => $foreignSymbol,
'decimal_places' => $transaction['foreign_currency_decimal_places'],
];
}
$amounts[$foreignSymbol]['amount'] = bcadd(
$amounts[$foreignSymbol]['amount'],
(string) $transaction['foreign_amount']
);
$amounts[$foreignSymbol] ??= [
'amount' => '0',
'symbol' => $foreignSymbol,
'decimal_places' => $transaction['foreign_currency_decimal_places'],
];
$amounts[$foreignSymbol]['amount'] = bcadd($amounts[$foreignSymbol]['amount'], (string)$transaction['foreign_amount']);
}
// add primary currency amount
if (null !== $transaction['pc_amount'] && $transaction['currency_id'] !== $this->primaryCurrency->id) {
// same for foreign currency:
$primarySymbol = $this->primaryCurrency->symbol;
$amounts[$primarySymbol] ??= [
'amount' => '0',
'symbol' => $this->primaryCurrency->symbol,
'decimal_places' => $this->primaryCurrency->decimal_places,
];
$amounts[$primarySymbol]['amount'] = bcadd($amounts[$primarySymbol]['amount'], (string)$transaction['pc_amount']);
}
}
return $amounts;
@@ -184,16 +217,16 @@ class ShowController extends Controller
foreach ($group['transactions'] as $transaction) {
$accounts['source'][] = [
'type' => $transaction['source_type'],
'id' => $transaction['source_id'],
'name' => $transaction['source_name'],
'iban' => $transaction['source_iban'],
'type' => $transaction['source_account_type'],
'id' => $transaction['source_account_id'],
'name' => $transaction['source_account_name'],
'iban' => $transaction['source_account_iban'],
];
$accounts['destination'][] = [
'type' => $transaction['destination_type'],
'id' => $transaction['destination_id'],
'name' => $transaction['destination_name'],
'iban' => $transaction['destination_iban'],
'type' => $transaction['destination_account_type'],
'id' => $transaction['destination_account_id'],
'name' => $transaction['destination_account_name'],
'iban' => $transaction['destination_account_iban'],
];
}

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

@@ -562,7 +562,13 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
// fields
'transaction_journals.date', 'transaction_types.type', 'transaction_journals.transaction_currency_id', 'transactions.amount'])
'transaction_journals.date',
'transaction_types.type',
'transaction_journals.transaction_currency_id',
'transactions.amount',
'transactions.native_amount',
'transactions.foreign_amount',
])
->toArray()
;

View File

@@ -52,4 +52,10 @@ class ALERepository implements ALERepositoryInterface
return $auditLogEntry;
}
public function getForId(string $model, int $modelId): Collection
{
// all Models have an ID.
return AuditLogEntry::where('auditable_id', $modelId)->where('auditable_type', $model)->get();
}
}

View File

@@ -46,5 +46,7 @@ interface ALERepositoryInterface
{
public function getForObject(Model $model): Collection;
public function getForId(string $model, int $modelId): Collection;
public function store(array $data): AuditLogEntry;
}

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,13 +197,16 @@ 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'],
'destination_account_name' => $journal['destination_account_name'],
'destination_account_type' => $journal['destination_account_type'],
'currency_id' => $journalCurrencyId,
'source_account_id' => $journal['source_account_id'],
'source_account_name' => $journal['source_account_name'],
'source_account_type' => $journal['source_account_type'],
'category_name' => $journal['category_name'],
'description' => $journal['description'],
'transaction_group_id' => $journal['transaction_group_id'],
@@ -282,4 +286,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

@@ -29,9 +29,9 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use NumberFormatter;
/**
@@ -116,11 +116,28 @@ class Amount
public function convertToPrimary(?User $user = null): bool
{
$instance = PreferencesSingleton::getInstance();
if (!$user instanceof User) {
return true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
$pref = $instance->getPreference('convert_to_primary_no_user');
if (null === $pref) {
$res = true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
$instance->setPreference('convert_to_primary_no_user', $res);
return $res;
}
return $pref;
}
$key = sprintf('convert_to_primary_%d', $user->id);
$pref = $instance->getPreference($key);
if (null === $pref) {
$res = true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
$instance->setPreference($key, $res);
return $res;
}
return true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
return $pref;
}
public function getPrimaryCurrency(): TransactionCurrency

View File

@@ -67,7 +67,7 @@ trait ChartGeneration
/** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class);
$default = app('amount')->getPrimaryCurrency();
$primary = app('amount')->getPrimaryCurrency();
$chartData = [];
Log::debug(sprintf('Start of accountBalanceChart(list, %s, %s)', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
@@ -75,10 +75,10 @@ trait ChartGeneration
/** @var Account $account */
foreach ($accounts as $account) {
Log::debug(sprintf('Now at account #%d ("%s)', $account->id, $account->name));
$currency = $accountRepos->getAccountCurrency($account) ?? $default;
$usePrimary = $convertToPrimary && $default->id !== $currency->id;
$currency = $accountRepos->getAccountCurrency($account) ?? $primary;
$usePrimary = $convertToPrimary && $primary->id !== $currency->id;
$field = $convertToPrimary ? 'pc_balance' : 'balance';
$currency = $usePrimary ? $default : $currency;
$currency = $usePrimary ? $primary : $currency;
Log::debug(sprintf('Will use field %s', $field));
$currentSet = [
'label' => $account->name,

View File

@@ -35,6 +35,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Debug\Timer;
use FireflyIII\Support\Facades\Navigation;
use Illuminate\Support\Facades\Log;
/**
@@ -81,7 +82,7 @@ trait PeriodOverview
Log::debug('Now in getAccountPeriodOverview()');
Timer::start('account-period-total');
$this->accountRepository = app(AccountRepositoryInterface::class);
$range = app('navigation')->getViewRange(true);
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
// properties for cache
@@ -91,11 +92,11 @@ trait PeriodOverview
$cache->addProperty('account-show-period-entries');
$cache->addProperty($account->id);
if ($cache->has()) {
return $cache->get();
// return $cache->get();
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
$spent = [];
$earned = [];
@@ -104,29 +105,26 @@ trait PeriodOverview
// run a custom query because doing this with the collector is MEGA slow.
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
// loop dates
Log::debug(sprintf('Count of loops: %d', count($dates)));
$loops = 0;
// stop after 10 loops for memory reasons.
foreach ($dates as $currentDate) {
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
if ($loops < 10) {
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
}
$title = Navigation::periodShow($currentDate['start'], $currentDate['period']);
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
$entries[]
= [
'title' => $title,
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred_away' => $this->groupByCurrency($transferredAway),
'transferred_in' => $this->groupByCurrency($transferredIn),
];
'title' => $title,
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred_away' => $this->groupByCurrency($transferredAway),
'transferred_in' => $this->groupByCurrency($transferredIn),
];
++$loops;
}
$cache->store($entries);
@@ -138,7 +136,8 @@ trait PeriodOverview
private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array
{
$result = [];
$result = [];
$filtered = [];
/**
* @var int $index
@@ -146,18 +145,23 @@ trait PeriodOverview
*/
foreach ($transactions as $index => $item) {
$date = Carbon::parse($item['date']);
if ($item['type'] === $type->value && $date >= $start && $date <= $end) {
$fits = $item['type'] === $type->value && $date >= $start && $date <= $end;
if ($fits) {
$result[] = $item;
unset($transactions[$index]);
}
if (!$fits) {
$filtered[] = $item;
}
}
return [$transactions, $result];
return [$filtered, $result];
}
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
{
$result = [];
$result = [];
$filtered = [];
/**
* @var int $index
@@ -166,18 +170,21 @@ trait PeriodOverview
foreach ($transactions as $index => $item) {
$date = Carbon::parse($item['date']);
if ($date >= $start && $date <= $end) {
if ('away' === $direction && -1 === bccomp((string) $item['amount'], '0')) {
if ('away' === $direction && -1 === bccomp((string)$item['amount'], '0')) {
$result[] = $item;
unset($transactions[$index]);
continue;
}
if ('in' === $direction && 1 === bccomp((string) $item['amount'], '0')) {
if ('in' === $direction && 1 === bccomp((string)$item['amount'], '0')) {
$result[] = $item;
unset($transactions[$index]);
continue;
}
$filtered[] = $item;
}
}
return [$transactions, $result];
return [$filtered, $result];
}
private function groupByCurrency(array $journals): array
@@ -186,7 +193,7 @@ trait PeriodOverview
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$currencyId = (int)$journal['currency_id'];
$currencyCode = $journal['currency_code'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
@@ -203,7 +210,7 @@ trait PeriodOverview
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
}
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId === $this->primaryCurrency->id) {
$currencyId = (int) $foreignCurrencyId;
$currencyId = (int)$foreignCurrencyId;
$currencyCode = $journal['foreign_currency_code'];
$currencyName = $journal['foreign_currency_name'];
$currencySymbol = $journal['foreign_currency_symbol'];
@@ -235,7 +242,7 @@ trait PeriodOverview
*/
protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
{
$range = app('navigation')->getViewRange(true);
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
// properties for entries with their amounts.
@@ -251,7 +258,7 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
@@ -281,7 +288,7 @@ trait PeriodOverview
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'transactions' => 0,
@@ -327,7 +334,7 @@ trait PeriodOverview
*/
protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
{
$range = app('navigation')->getViewRange(true);
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
@@ -342,7 +349,7 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
// get all expenses without a budget.
@@ -353,7 +360,7 @@ trait PeriodOverview
foreach ($dates as $currentDate) {
$set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'title' => $title,
@@ -380,17 +387,17 @@ trait PeriodOverview
protected function getNoCategoryPeriodOverview(Carbon $theDate): array
{
app('log')->debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
$range = app('navigation')->getViewRange(true);
$range = Navigation::getViewRange(true);
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon() : $first->date;
$end = clone $theDate;
$end = app('navigation')->endOfPeriod($end, $range);
$end = Navigation::endOfPeriod($end, $range);
app('log')->debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
app('log')->debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
// properties for cache
$dates = app('navigation')->blockPeriods($start, $end, $range);
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
@@ -422,7 +429,7 @@ trait PeriodOverview
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'title' => $title,
@@ -445,7 +452,7 @@ trait PeriodOverview
*/
protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags.
{
$range = app('navigation')->getViewRange(true);
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
// properties for cache
@@ -459,7 +466,7 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
@@ -495,7 +502,7 @@ trait PeriodOverview
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'transactions' => 0,
@@ -540,7 +547,7 @@ trait PeriodOverview
*/
protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
{
$range = app('navigation')->getViewRange(true);
$range = Navigation::getViewRange(true);
$types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
@@ -555,7 +562,7 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
$spent = [];
$earned = [];
@@ -567,7 +574,7 @@ trait PeriodOverview
$loops = 0;
foreach ($dates as $currentDate) {
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
if ($loops < 10) {
// set to correct array
@@ -582,14 +589,14 @@ trait PeriodOverview
}
}
$entries[]
= [
'title' => $title,
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
= [
'title' => $title,
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
++$loops;
}
@@ -605,7 +612,7 @@ trait PeriodOverview
/** @var array $journal */
foreach ($journals as $journal) {
if ($account->id === (int) $journal['source_account_id']) {
if ($account->id === (int)$journal['source_account_id']) {
$return[] = $journal;
}
}
@@ -622,7 +629,7 @@ trait PeriodOverview
/** @var array $journal */
foreach ($journals as $journal) {
if ($account->id === (int) $journal['destination_account_id']) {
if ($account->id === (int)$journal['destination_account_id']) {
$return[] = $journal;
}
}

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;
@@ -32,12 +33,16 @@ use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Location;
use FireflyIII\Models\Note;
use FireflyIII\Models\ObjectGroup;
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;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Override;
@@ -48,35 +53,32 @@ use Override;
*/
class AccountEnrichment implements EnrichmentInterface
{
private array $accountIds;
private array $accountTypeIds;
private array $accountTypes;
private array $ids = [];
private array $accountTypeIds = [];
private array $accountTypes = [];
private Collection $collection;
private array $currencies;
private array $locations;
private array $meta;
private array $currencies = [];
private array $locations = [];
private array $meta = [];
private TransactionCurrency $primaryCurrency;
private array $notes;
private array $openingBalances;
private array $notes = [];
private array $openingBalances = [];
private User $user;
private UserGroup $userGroup;
private array $lastActivities;
private array $lastActivities = [];
private ?Carbon $date = null;
private bool $convertToPrimary;
private array $balances = [];
private array $objectGroups = [];
private array $mappedObjects = [];
/**
* 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->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
}
#[Override]
@@ -99,26 +101,28 @@ 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->collectObjectGroups();
$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 +132,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 +166,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 +207,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 +233,160 @@ 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,
],
'object_group_id' => null,
'object_group_order' => null,
'object_group_title' => 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;
}
}
// also add currency, if present.
if (array_key_exists('currency_id', $accountMeta)) {
$currencyId = (int) $accountMeta['currency_id'];
$accountMeta['currency'] = $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'];
// 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'];
}
// if location, add location:
if (array_key_exists($item->id, $locations)) {
$accountMeta['location'] = $locations[$item->id];
if (array_key_exists($id, $this->locations)) {
$meta['location'] = $this->locations[$id];
}
if (array_key_exists($item->id, $lastActivities)) {
$accountMeta['last_activity'] = $lastActivities[$item->id];
if (array_key_exists($id, $this->meta)) {
foreach ($this->meta[$id] as $name => $value) {
$meta[$name] = $value;
}
}
$item->meta = $accountMeta;
// also add currency, if present.
if (array_key_exists('currency_id', $meta)) {
$currencyId = (int)$meta['currency_id'];
$meta['currency'] = $this->currencies[$currencyId];
}
if (array_key_exists($id, $this->openingBalances)) {
$meta['opening_balance_date'] = $this->openingBalances[$id]['date'];
$meta['opening_balance_amount'] = $this->openingBalances[$id]['amount'];
}
// add balances
// get currencies:
$currency = $this->primaryCurrency; // assume primary currency
if (null !== $meta['currency']) {
$currency = $meta['currency'];
}
// 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);
}
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::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
}
private function collectObjectGroups(): void
{
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->ids)
->where('object_groupable_type', Account::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;
}
}
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;
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->spentInBudgets[$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,198 @@
<?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\ObjectGroup;
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\DB;
use Illuminate\Support\Facades\Log;
class BudgetEnrichment implements EnrichmentInterface
{
private Collection $collection;
private bool $convertToPrimary;
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 = [];
private array $objectGroups = [];
private array $mappedObjects = [];
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->collectObjectGroups();
$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 = [
'object_group_id' => null,
'object_group_order' => null,
'object_group_title' => null,
'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,
];
// 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'];
}
$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;
}
private function collectObjectGroups(): void
{
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->ids)
->where('object_groupable_type', Budget::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;
}
}
}

View File

@@ -0,0 +1,158 @@
<?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 array $currencyIds = [];
private array $currencies = [];
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->collectCurrencies();
$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) {
$id = (int)$limit->id;
$this->ids[] = $id;
if (0 !== (int)$limit->transaction_currency_id) {
$this->currencyIds[$id] = (int)$limit->transaction_currency_id;
}
}
$this->ids = array_unique($this->ids);
$this->currencyIds = array_unique($this->currencyIds);
}
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;
$currencyId = (int)$item->transaction_currency_id;
if (0 === $currencyId) {
$currencyId = $this->primaryCurrency->id;
}
$meta = [
'notes' => $this->notes[$id] ?? null,
'spent' => $this->expenses[$id] ?? [],
'pc_spent' => $this->pcExpenses[$id] ?? [],
'currency' => $this->currencies[$currencyId],
];
$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] ?? [];
}
}
}
private function collectCurrencies(): void
{
$this->currencies[$this->primaryCurrency->id] = $this->primaryCurrency;
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot('id', $this->primaryCurrency->id)->get();
foreach ($currencies as $currency) {
$this->currencies[(int)$currency->id] = $currency;
}
}
}

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,273 @@
<?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);
if (null !== $this->amounts[$id][$accountId]['pc_current_amount'] && null !== $item->native_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' => '0',
'pc_current_amount' => '0',
'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,132 @@
<?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.
$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'] && null !== $transaction['foreign_currency_id']) {
$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;
@@ -26,18 +27,24 @@ class SubscriptionEnrichment implements EnrichmentInterface
private User $user;
private UserGroup $userGroup;
private Collection $collection;
private bool $convertToPrimary = false;
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $subscriptionIds = [];
private array $objectGroups = [];
private array $mappedObjects = [];
private array $paidDates = [];
private array $notes = [];
private array $payDates = [];
private bool $convertToPrimary;
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $subscriptionIds = [];
private array $objectGroups = [];
private array $mappedObjects = [];
private array $paidDates = [];
private array $notes = [];
private array $payDates = [];
private TransactionCurrency $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

@@ -45,26 +45,20 @@ use Override;
class TransactionGroupEnrichment implements EnrichmentInterface
{
private array $attachmentCount;
private array $attachmentCount = [];
private Collection $collection;
private readonly array $dateFields;
private array $journalIds;
private array $locations;
private array $metaData; // @phpstan-ignore-line
private array $notes; // @phpstan-ignore-line
private array $tags;
private array $journalIds = [];
private array $locations = [];
private array $metaData = [];
private array $notes = [];
private array $tags = [];
private User $user;
private readonly TransactionCurrency $primaryCurrency;
private UserGroup $userGroup;
public function __construct()
{
$this->notes = [];
$this->journalIds = [];
$this->tags = [];
$this->metaData = [];
$this->locations = [];
$this->attachmentCount = [];
$this->dateFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
$this->primaryCurrency = Amount::getPrimaryCurrency();
}

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 @@ namespace FireflyIII\Support;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Preference;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use FireflyIII\User;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\EncryptException;
@@ -283,6 +284,12 @@ class Preferences
*/
public function lastActivity(): string
{
$instance = PreferencesSingleton::getInstance();
$pref = $instance->getPreference('last_activity');
if (null !== $pref) {
// Log::debug(sprintf('Found last activity in singleton: %s', $pref));
return $pref;
}
$lastActivity = microtime();
$preference = $this->get('lastActivity', microtime());
@@ -292,13 +299,17 @@ class Preferences
if (is_array($lastActivity)) {
$lastActivity = implode(',', $lastActivity);
}
$setting = hash('sha256', (string) $lastActivity);
$instance->setPreference('last_activity', $setting);
return hash('sha256', (string) $lastActivity);
return $setting;
}
public function mark(): void
{
$this->set('lastActivity', microtime());
$instance = PreferencesSingleton::getInstance();
$instance->setPreference('last_activity', microtime());
Session::forget('first');
}

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

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support\Singleton;
class PreferencesSingleton
{
private static ?PreferencesSingleton $instance = null;
private array $preferences = [];
private function __construct()
{
// Private constructor to prevent direct instantiation.
}
public static function getInstance(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function resetPreferences(): void
{
$this->preferences = [];
}
public function setPreference(string $key, mixed $value): void
{
$this->preferences[$key] = $value;
}
public function getPreference(string $key): mixed
{
return $this->preferences[$key] ?? null;
}
}

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 accountsBalancesOptimized(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,48 @@ class Steam
return $amount;
}
private function getCurrencies(Collection $accounts): array
{
$currencies = [];
$accountCurrencies = [];
$accountPreferences = [];
$primary = Amount::getPrimaryCurrency();
$currencies[$primary->id] = $primary;
$ids = $accounts->pluck('id')->toArray();
$result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($result as $item) {
$integer = (int)$item->data;
if (0 !== $integer) {
$accountPreferences[(int)$item->account_id] = $integer;
}
}
// collect those currencies, skip primary because we already have it.
$set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->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($accountId, $accountPreferences)) {
$accountCurrencies[$accountId] = $primary;
}
if (!$currencyPresent && array_key_exists($accountId, $accountPreferences)) {
$accountCurrencies[$accountId] = $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,57 @@ 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,
'object_group_id' => $account->meta['object_group_id'],
'object_group_order' => $account->meta['object_group_order'],
'object_group_title' => $account->meta['object_group_title'],
// 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 +170,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 +206,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 +214,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 +227,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

@@ -55,7 +55,6 @@ class AttachmentTransformer extends AbstractTransformer
'updated_at' => $attachment->updated_at->toAtomString(),
'attachable_id' => (string) $attachment->attachable_id,
'attachable_type' => str_replace('FireflyIII\Models\\', '', $attachment->attachable_type),
'md5' => $attachment->md5,
'hash' => $attachment->md5,
'filename' => $attachment->filename,
'download_url' => route('api.v1.attachments.download', [$attachment->id]),

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(),
@@ -81,16 +93,11 @@ class BillTransformer extends AbstractTransformer
'object_group_order' => $bill->meta['object_group_order'],
'object_group_title' => $bill->meta['object_group_title'],
'paid_dates' => $bill->meta['paid_dates'],
'pay_dates' => $bill->meta['pay_dates'],
'next_expected_match' => $bill->meta['nem']?->toAtomString(),
'next_expected_match_diff' => $bill->meta['nem_diff'],
// these fields need work:
// 'next_expected_match' => $nem,
// 'next_expected_match_diff' => $nemDiff,
// 'pay_dates' => $payDatesFormatted,
'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,16 @@ 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->meta['currency'];
$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 +81,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,54 @@ 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,
'object_group_id' => $budget->meta['object_group_id'],
'object_group_order' => $budget->meta['object_group_order'],
'object_group_title' => $budget->meta['object_group_title'],
'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,
// 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,
'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 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' => null === $budget->meta['spent'] ? null : $this->beautify($budget->meta['spent']),
'pc_spent' => null === $budget->meta['pc_spent'] ? null : $this->beautify($budget->meta['pc_spent']),
'links' => [
[
'rel' => 'self',
@@ -136,7 +121,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'],
'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;

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