Greatly reduce number of queries in transaction transformer.

This commit is contained in:
James Cole 2025-02-15 14:10:54 +01:00
parent a795755618
commit 9f94ec067a
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
24 changed files with 418 additions and 91 deletions

View File

@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\PiggyBankTransformer; use FireflyIII\Transformers\PiggyBankTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
@ -165,13 +166,17 @@ class ListController extends Controller
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.accounts.transactions', [$account->id]).$this->buildParams()); $paginator->setPath(route('api.v1.accounts.transactions', [$account->id]).$this->buildParams());
$groups = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters); $transformer->setParameters($this->parameters);
$resource = new FractalCollection($groups, $transformer, 'transactions'); $resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Bill; use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\RuleTransformer; use FireflyIII\Transformers\RuleTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
@ -176,7 +177,11 @@ class ListController extends Controller
// get paginator. // get paginator.
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.bills.transactions', [$bill->id]).$this->buildParams()); $paginator->setPath(route('api.v1.bills.transactions', [$bill->id]).$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -32,6 +32,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\BudgetLimitTransformer; use FireflyIII\Transformers\BudgetLimitTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
@ -172,7 +173,11 @@ class ListController extends Controller
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.budgets.transactions', [$budget->id]).$this->buildParams()); $paginator->setPath(route('api.v1.budgets.transactions', [$budget->id]).$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
@ -232,7 +237,11 @@ class ListController extends Controller
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.budgets.without-budget').$this->buildParams()); $paginator->setPath(route('api.v1.budgets.without-budget').$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -84,7 +85,11 @@ class ListController extends Controller
$collector->setTypes($types); $collector->setTypes($types);
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.budgets.limits.transactions', [$budget->id, $budgetLimit->id]).$this->buildParams()); $paginator->setPath(route('api.v1.budgets.limits.transactions', [$budget->id, $budgetLimit->id]).$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
@ -139,7 +140,11 @@ class ListController extends Controller
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.categories.transactions', [$category->id]).$this->buildParams()); $paginator->setPath(route('api.v1.categories.transactions', [$category->id]).$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Recurrence; use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -110,7 +111,11 @@ class ListController extends Controller
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.transactions.index').$this->buildParams()); $paginator->setPath(route('api.v1.transactions.index').$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -29,6 +29,7 @@ use FireflyIII\Api\V1\Requests\Models\Rule\TestRequest;
use FireflyIII\Api\V1\Requests\Models\Rule\TriggerRequest; use FireflyIII\Api\V1\Requests\Models\Rule\TriggerRequest;
use FireflyIII\Models\Rule; use FireflyIII\Models\Rule;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface; use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
@ -94,6 +95,11 @@ class TriggerController extends Controller
$transactions = $ruleEngine->find(); $transactions = $ruleEngine->find();
$count = $transactions->count(); $count = $transactions->count();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($rule->user);
$transactions = $enrichment->enrich($transactions);
$paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page')); $paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.rules.test', [$rule->id]).$this->buildParams()); $paginator->setPath(route('api.v1.rules.test', [$rule->id]).$this->buildParams());

View File

@ -30,6 +30,7 @@ use FireflyIII\Api\V1\Requests\Models\RuleGroup\TriggerRequest;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface; use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
@ -100,6 +101,11 @@ class TriggerController extends Controller
$transactions = $ruleEngine->find(); $transactions = $ruleEngine->find();
$count = $transactions->count(); $count = $transactions->count();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($group->user);
$transactions = $enrichment->enrich($transactions);
$paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page')); $paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.rule-groups.test', [$group->id]).$this->buildParams()); $paginator->setPath(route('api.v1.rule-groups.test', [$group->id]).$this->buildParams());

View File

@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
@ -141,7 +142,12 @@ class ListController extends Controller
} }
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.tags.transactions', [$tag->id]).$this->buildParams()); $paginator->setPath(route('api.v1.tags.transactions', [$tag->id]).$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -30,10 +30,12 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
@ -85,7 +87,11 @@ class ShowController extends Controller
} }
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.transactions.index').$this->buildParams()); $paginator->setPath(route('api.v1.transactions.index').$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
@ -137,6 +143,11 @@ class ShowController extends Controller
throw new NotFoundHttpException(); throw new NotFoundHttpException();
} }
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters); $transformer->setParameters($this->parameters);

View File

@ -34,6 +34,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Rules\IsDuplicateTransaction; use FireflyIII\Rules\IsDuplicateTransaction;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -133,6 +134,11 @@ class StoreController extends Controller
throw new FireflyException('200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.'); throw new FireflyException('200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
} }
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters); $transformer->setParameters($this->parameters);

View File

@ -30,6 +30,7 @@ use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -99,6 +100,11 @@ class UpdateController extends Controller
throw new NotFoundHttpException(); throw new NotFoundHttpException();
} }
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters); $transformer->setParameters($this->parameters);

View File

@ -42,6 +42,7 @@ use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AccountTransformer; use FireflyIII\Transformers\AccountTransformer;
use FireflyIII\Transformers\AvailableBudgetTransformer; use FireflyIII\Transformers\AvailableBudgetTransformer;
use FireflyIII\Transformers\BillTransformer; use FireflyIII\Transformers\BillTransformer;
@ -360,7 +361,11 @@ class ListController extends Controller
} }
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.currencies.transactions', [$currency->code]).$this->buildParams()); $paginator->setPath(route('api.v1.currencies.transactions', [$currency->code]).$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\LinkType; use FireflyIII\Models\LinkType;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -109,7 +110,11 @@ class ListController extends Controller
} }
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.transactions.index').$this->buildParams()); $paginator->setPath(route('api.v1.transactions.index').$this->buildParams());
$transactions = $paginator->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Search;
use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Support\Search\SearchInterface; use FireflyIII\Support\Search\SearchInterface;
use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\Transformers\TransactionGroupTransformer;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -57,7 +58,11 @@ class TransactionController extends Controller
$parameters = ['search' => $fullQuery]; $parameters = ['search' => $fullQuery];
$url = route('api.v1.search.transactions').'?'.http_build_query($parameters); $url = route('api.v1.search.transactions').'?'.http_build_query($parameters);
$groups->setPath($url); $groups->setPath($url);
$transactions = $groups->getCollection();
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser(auth()->user());
$transactions = $enrichment->enrich($groups->getCollection());
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);

View File

@ -65,7 +65,7 @@ class TestRequest extends FormRequest
private function getAccounts(): array private function getAccounts(): array
{ {
return $this->get('accounts'); return $this->get('accounts') ?? [];
} }
public function rules(): array public function rules(): array

View File

@ -59,7 +59,7 @@ class TestRequest extends FormRequest
private function getAccounts(): array private function getAccounts(): array
{ {
return $this->get('accounts'); return $this->get('accounts') ?? [];
} }
public function rules(): array public function rules(): array

View File

@ -76,7 +76,7 @@ class TagFactory
$longitude = 0.0 === (float) $data['longitude'] ? null : (float) $data['longitude']; // intentional float $longitude = 0.0 === (float) $data['longitude'] ? null : (float) $data['longitude']; // intentional float
$array = [ $array = [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id, 'user_group_id' => $this->userGroup->id,
'tag' => trim($data['tag']), 'tag' => trim($data['tag']),
'tagMode' => 'nothing', 'tagMode' => 'nothing',
'date' => $data['date'], 'date' => $data['date'],

View File

@ -79,7 +79,7 @@ class TransactionGroupFactory
$group = new TransactionGroup(); $group = new TransactionGroup();
$group->user()->associate($this->user); $group->user()->associate($this->user);
$group->userGroup()->associate($data['user_group']); $group->userGroup()->associate($this->userGroup);
$group->title = $title; $group->title = $title;
$group->save(); $group->save();

View File

@ -26,6 +26,7 @@ namespace FireflyIII\Repositories\Currency;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -67,4 +68,5 @@ interface CurrencyRepositoryInterface
public function setExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date, float $rate): CurrencyExchangeRate; public function setExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date, float $rate): CurrencyExchangeRate;
public function setUser(null|Authenticatable|User $user): void; public function setUser(null|Authenticatable|User $user): void;
public function setUserGroup(UserGroup $userGroup): void;
} }

View File

@ -31,5 +31,5 @@ interface EnrichmentInterface
{ {
public function enrich(Collection $collection): Collection; public function enrich(Collection $collection): Collection;
public function enrichSingle(Model $model): Model; public function enrichSingle(Model|array $model): Model|array;
} }

View File

@ -0,0 +1,238 @@
<?php
/*
* TransactionGroupEnrichment.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\Exceptions\FireflyException;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Location;
use FireflyIII\Models\Note;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalMeta;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class TransactionGroupEnrichment implements EnrichmentInterface
{
private Collection $collection;
private array $notes;
private array $tags;
private array $locations;
private array $journalIds;
private User $user;
private UserGroup $userGroup;
private array $metaData;
private array $dateFields;
private array $attachmentCount;
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',];
}
#[\Override] public function enrich(Collection $collection): Collection
{
Log::debug(sprintf('Now doing account enrichment for %d transaction group(s)', $collection->count()));
// prep local fields
$this->collection = $collection;
$this->collectJournalIds();
// collect first, then enrich.
$this->collectNotes();
$this->collectTags();
$this->collectMetaData();
$this->collectLocations();
$this->collectAttachmentCount();
$this->appendCollectedData();
return $this->collection;
}
#[\Override] public function enrichSingle(Model|array $model): Model|array
{
Log::debug(__METHOD__);
if(is_array($model)) {
$collection = new Collection([$model]);
$collection = $this->enrich($collection);
return $collection->first();
}
throw new FireflyException('Cannot enrich single model.');
}
private function collectJournalIds(): void
{
/** @var array $group */
foreach ($this->collection as $group) {
foreach ($group['transactions'] as $journal) {
$this->journalIds[] = $journal['transaction_journal_id'];
}
}
$this->journalIds = array_unique($this->journalIds);
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
public function setUser(User $user): void
{
$this->user = $user;
$this->userGroup = $user->userGroup;
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->journalIds)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', TransactionJournal::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 collectTags(): void
{
$set = Tag::
leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
->whereIn('tag_transaction_journal.transaction_journal_id', $this->journalIds)
->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag'])->toArray();
foreach ($set as $item) {
$journalId = $item['transaction_journal_id'];
$this->tags[$journalId] ??= [];
$this->tags[$journalId][] = $item['tag'];
}
}
private function collectMetaData(): void
{
$set = TransactionJournalMeta::whereIn('transaction_journal_id', $this->journalIds)->get(['transaction_journal_id', 'name', 'data'])->toArray();
foreach ($set as $entry) {
$name = $entry['name'];
$data = (string) $entry['data'];
if ('' === $data) {
continue;
}
if (in_array($name, $this->dateFields, true)) {
$this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data);
continue;
}
$this->metaData[(int) $entry['transaction_journal_id']][$name] = $data;
}
}
private function collectLocations(): void
{
$locations = Location::query()->whereIn('locatable_id', $this->journalIds)
->where('locatable_type', TransactionJournal::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray();
foreach ($locations as $location) {
$this->locations[(int) $location['locatable_id']]
= [
'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)));
}
private function collectAttachmentCount(): void
{
// select count(id) as nr_of_attachments, attachable_id from attachments
//group by attachable_id
$attachments = Attachment::query()
->whereIn('attachable_id', $this->journalIds)
->where('attachable_type', TransactionJournal::class)
->groupBy('attachable_id')
->get(['attachable_id', DB::raw('COUNT(id) as nr_of_attachments')])->toArray();
foreach ($attachments as $row) {
$this->attachmentCount[(int) $row['attachable_id']] = (int) $row['nr_of_attachments'];
}
}
private function appendCollectedData(): void {
$notes = $this->notes;
$tags = $this->tags;
$metaData = $this->metaData;
$locations = $this->locations;
$attachmentCount = $this->attachmentCount;
$this->collection = $this->collection->map(function (array $item) use ($notes, $tags, $metaData, $locations, $attachmentCount) {
foreach ($item['transactions'] as $index => $transaction) {
$journalId = (int) $transaction['transaction_journal_id'];
// attach notes if they exist:
$item['transactions'][$index]['notes'] = array_key_exists($journalId, $notes) ? $notes[$journalId] : null;
// attach tags if they exist:
$item['transactions'][$index]['tags'] = array_key_exists($journalId, $tags) ? $tags[$journalId] : [];
// attachment count
$item['transactions'][$index]['attachment_count'] = array_key_exists($journalId, $attachmentCount) ? $attachmentCount[$journalId] : 0;
// default location data
$item['transactions'][$index]['location'] = [
'latitude' => null,
'longitude' => null,
'zoom_level' => null,
];
// append meta data
$item['transactions'][$index]['meta'] = [];
$item['transactions'][$index]['meta_date'] = [];
if (array_key_exists($journalId, $metaData)) {
// loop al meta data:
foreach ($metaData[$journalId] as $name => $value) {
if (in_array($name, $this->dateFields, true)) {
$item['transactions'][$index]['meta_date'][$name] = Carbon::parse($value);
continue;
}
$item['transactions'][$index]['meta'][$name] = $value;
}
}
// append location data
if (array_key_exists($journalId, $locations)) {
$item['transactions'][$index]['location'] = $locations[$journalId];
}
}
return $item;
});
}
}

View File

@ -65,6 +65,10 @@ class TransactionSummarizer
// prepare foreign currency info: // prepare foreign currency info:
$foreignCurrencyId = 0; $foreignCurrencyId = 0;
$foreignCurrencyName = null;
$foreignCurrencySymbol = null;
$foreignCurrencyCode = null;
$foreignCurrencyDecimalPlaces = null;
if ($this->convertToNative) { if ($this->convertToNative) {
// if convert to native, use the native amount yes or no? // if convert to native, use the native amount yes or no?

View File

@ -37,6 +37,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Support\NullArrayObject; use FireflyIII\Support\NullArrayObject;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class TransactionGroupTransformer * Class TransactionGroupTransformer
@ -46,12 +47,14 @@ class TransactionGroupTransformer extends AbstractTransformer
private TransactionGroupRepositoryInterface $groupRepos; private TransactionGroupRepositoryInterface $groupRepos;
private array $metaDateFields; private array $metaDateFields;
private array $metaFields; private array $metaFields;
private Collection $collection;
/** /**
* Constructor. * Constructor.
*/ */
public function __construct() public function __construct()
{ {
Log::debug('TransactionGroupTransformer constructor.');
$this->groupRepos = app(TransactionGroupRepositoryInterface::class); $this->groupRepos = app(TransactionGroupRepositoryInterface::class);
$this->metaFields = [ $this->metaFields = [
'sepa_cc', 'sepa_cc',
@ -85,6 +88,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'created_at' => $first['created_at']->toAtomString(), 'created_at' => $first['created_at']->toAtomString(),
'updated_at' => $first['updated_at']->toAtomString(), 'updated_at' => $first['updated_at']->toAtomString(),
'user' => (string) $data['user_id'], 'user' => (string) $data['user_id'],
'user_group' => (string) $data['user_group_id'],
'group_title' => $data['title'], 'group_title' => $data['title'],
'transactions' => $this->transformTransactions($data), 'transactions' => $this->transformTransactions($data),
'links' => [ 'links' => [
@ -112,107 +116,96 @@ class TransactionGroupTransformer extends AbstractTransformer
*/ */
private function transformTransaction(array $transaction): array private function transformTransaction(array $transaction): array
{ {
$row = new NullArrayObject($transaction);
// amount: // amount:
$amount = app('steam')->positive((string) ($row['amount'] ?? '0')); $amount = app('steam')->positive((string) ($transaction['amount'] ?? '0'));
$foreignAmount = null; $foreignAmount = null;
if (null !== $row['foreign_amount'] && '' !== $row['foreign_amount'] && 0 !== bccomp('0', $row['foreign_amount'])) { if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', $transaction['foreign_amount'])) {
$foreignAmount = app('steam')->positive($row['foreign_amount']); $foreignAmount = app('steam')->positive($transaction['foreign_amount']);
} }
$metaFieldData = $this->groupRepos->getMetaFields((int) $row['transaction_journal_id'], $this->metaFields);
$metaDateData = $this->groupRepos->getMetaDateFields((int) $row['transaction_journal_id'], $this->metaDateFields);
$type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value); $type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value);
$longitude = null; // must be 0 (int) or NULL
$latitude = null; $recurrenceTotal = $transaction['meta']['recurrence_total'] ?? null;
$zoomLevel = null; $recurrenceTotal = null !== $recurrenceTotal ? (int) $recurrenceTotal : null;
$location = $this->getLocationById((int) $row['transaction_journal_id']); $recurrenceCount = $transaction['meta']['recurrence_count'] ?? null;
if (null !== $location) { $recurrenceCount = null !== $recurrenceCount ? (int) $recurrenceCount : null;
$longitude = $location->longitude;
$latitude = $location->latitude;
$zoomLevel = $location->zoom_level;
}
return [ return [
'user' => (string) $row['user_id'], 'user' => (string) $transaction['user_id'],
'transaction_journal_id' => (string) $row['transaction_journal_id'], 'transaction_journal_id' => $transaction['transaction_journal_id'],
'type' => strtolower($type), 'type' => strtolower($type),
'date' => $row['date']->toAtomString(), 'date' => $transaction['date']->toAtomString(),
'order' => $row['order'], 'order' => $transaction['order'],
'currency_id' => (string) $row['currency_id'], 'currency_id' => (string) $transaction['currency_id'],
'currency_code' => $row['currency_code'], 'currency_code' => $transaction['currency_code'],
'currency_name' => $row['currency_name'], 'currency_name' => $transaction['currency_name'],
'currency_symbol' => $row['currency_symbol'], 'currency_symbol' => $transaction['currency_symbol'],
'currency_decimal_places' => (int) $row['currency_decimal_places'], 'currency_decimal_places' => (int) $transaction['currency_decimal_places'],
'foreign_currency_id' => $this->stringFromArray($transaction, 'foreign_currency_id', null), 'foreign_currency_id' => $this->stringFromArray($transaction, 'foreign_currency_id', null),
'foreign_currency_code' => $row['foreign_currency_code'], 'foreign_currency_code' => $transaction['foreign_currency_code'],
'foreign_currency_symbol' => $row['foreign_currency_symbol'], 'foreign_currency_symbol' => $transaction['foreign_currency_symbol'],
'foreign_currency_decimal_places' => $row['foreign_currency_decimal_places'], 'foreign_currency_decimal_places' => $transaction['foreign_currency_decimal_places'],
'amount' => $amount, 'amount' => $amount,
'foreign_amount' => $foreignAmount, 'foreign_amount' => $foreignAmount,
'description' => $row['description'], 'description' => $transaction['description'],
'source_id' => (string) $row['source_account_id'], 'source_id' => (string) $transaction['source_account_id'],
'source_name' => $row['source_account_name'], 'source_name' => $transaction['source_account_name'],
'source_iban' => $row['source_account_iban'], 'source_iban' => $transaction['source_account_iban'],
'source_type' => $row['source_account_type'], 'source_type' => $transaction['source_account_type'],
'destination_id' => (string) $row['destination_account_id'], 'destination_id' => (string) $transaction['destination_account_id'],
'destination_name' => $row['destination_account_name'], 'destination_name' => $transaction['destination_account_name'],
'destination_iban' => $row['destination_account_iban'], 'destination_iban' => $transaction['destination_account_iban'],
'destination_type' => $row['destination_account_type'], 'destination_type' => $transaction['destination_account_type'],
'budget_id' => $this->stringFromArray($transaction, 'budget_id', null), 'budget_id' => $this->stringFromArray($transaction, 'budget_id', null),
'budget_name' => $row['budget_name'], 'budget_name' => $transaction['budget_name'],
'category_id' => $this->stringFromArray($transaction, 'category_id', null), 'category_id' => $this->stringFromArray($transaction, 'category_id', null),
'category_name' => $row['category_name'], 'category_name' => $transaction['category_name'],
'bill_id' => $this->stringFromArray($transaction, 'bill_id', null), 'bill_id' => $this->stringFromArray($transaction, 'bill_id', null),
'bill_name' => $row['bill_name'], 'bill_name' => $transaction['bill_name'],
'reconciled' => $row['reconciled'], 'reconciled' => $transaction['reconciled'],
'notes' => $this->groupRepos->getNoteText((int) $row['transaction_journal_id']), 'notes' => $transaction['notes'],
'tags' => $this->groupRepos->getTags((int) $row['transaction_journal_id']), 'tags' => $transaction['tags'],
'internal_reference' => $metaFieldData['internal_reference'], 'internal_reference' => $transaction['meta']['internal_reference'] ?? null,
'external_id' => $metaFieldData['external_id'], 'external_id' => $transaction['meta']['external_id'] ?? null,
'original_source' => $metaFieldData['original_source'], 'original_source' => $transaction['meta']['original_source'] ?? null,
'recurrence_id' => $this->stringFromArray($metaFieldData->getArrayCopy(), 'recurrence_id', null), 'recurrence_id' => $transaction['meta']['recurrence_id'] ?? null,
'recurrence_total' => $this->integerFromArray($metaFieldData->getArrayCopy(), 'recurrence_total'), 'recurrence_total' => $recurrenceTotal,
'recurrence_count' => $this->integerFromArray($metaFieldData->getArrayCopy(), 'recurrence_count'), 'recurrence_count' => $recurrenceCount,
'bunq_payment_id' => $metaFieldData['bunq_payment_id'], 'bunq_payment_id' => $transaction['meta']['bunq_payment_id'] ?? null,
'external_url' => $metaFieldData['external_url'], 'external_url' => $transaction['meta']['external_url'] ?? null,
'import_hash_v2' => $metaFieldData['import_hash_v2'], 'import_hash_v2' => $transaction['meta']['import_hash_v2'] ?? null,
'sepa_cc' => $metaFieldData['sepa_cc'], 'sepa_cc' => $transaction['meta']['sepa_cc'] ?? null,
'sepa_ct_op' => $metaFieldData['sepa_ct_op'], 'sepa_ct_op' => $transaction['meta']['sepa_ct_op'] ?? null,
'sepa_ct_id' => $metaFieldData['sepa_ct_id'], 'sepa_ct_id' => $transaction['meta']['sepa_ct_id'] ?? null,
'sepa_db' => $metaFieldData['sepa_db'], 'sepa_db' => $transaction['meta']['sepa_db'] ?? null,
'sepa_country' => $metaFieldData['sepa_country'], 'sepa_country' => $transaction['meta']['sepa_country'] ?? null,
'sepa_ep' => $metaFieldData['sepa_ep'], 'sepa_ep' => $transaction['meta']['sepa_ep'] ?? null,
'sepa_ci' => $metaFieldData['sepa_ci'], 'sepa_ci' => $transaction['meta']['sepa_ci'] ?? null,
'sepa_batch_id' => $metaFieldData['sepa_batch_id'], 'sepa_batch_id' => $transaction['meta']['sepa_batch_id'] ?? null,
'interest_date' => $this->dateFromArray($metaDateData, 'interest_date'),
'book_date' => $this->dateFromArray($metaDateData, 'book_date'),
'process_date' => $this->dateFromArray($metaDateData, 'process_date'),
'due_date' => $this->dateFromArray($metaDateData, 'due_date'),
'payment_date' => $this->dateFromArray($metaDateData, 'payment_date'),
'invoice_date' => $this->dateFromArray($metaDateData, 'invoice_date'),
'interest_date' => $transaction['meta_date']['interest_date'] ?? null,
'book_date' => $transaction['meta_date']['book_date'] ?? null,
'process_date' => $transaction['meta_date']['process_date'] ?? null,
'due_date' => $transaction['meta_date']['due_date'] ?? null,
'payment_date' => $transaction['meta_date']['payment_date'] ?? null,
'invoice_date' => $transaction['meta_date']['invoice_date'] ?? null,
// location data // location data
'longitude' => $longitude, 'longitude' => $transaction['location']['longitude'],
'latitude' => $latitude, 'latitude' => $transaction['location']['latitude'],
'zoom_level' => $zoomLevel, 'zoom_level' => $transaction['location']['zoom_level'],
'has_attachments' => $transaction['attachment_count'] > 0,
'has_attachments' => $this->hasAttachments((int) $row['transaction_journal_id']),
]; ];
} }