Expand query search.

This commit is contained in:
James Cole
2022-03-21 06:31:38 +01:00
parent ad0dcc2cf4
commit aca008c911
8 changed files with 802 additions and 694 deletions

View File

@@ -216,9 +216,9 @@ trait AccountCollection
$this->query->leftJoin('account_types as dest_account_type', 'dest_account_type.id', '=', 'dest_account.account_type_id');
// and add fields:
$this->fields[] = 'dest_account.name as destination_account_name';
$this->fields[] = 'dest_account.iban as destination_account_iban';
$this->fields[] = 'dest_account_type.type as destination_account_type';
$this->fields[] = 'dest_account.name as destination_account_name';
$this->fields[] = 'dest_account.iban as destination_account_iban';
$this->fields[] = 'dest_account_type.type as destination_account_type';
$this->hasAccountInfo = true;
}

View File

@@ -32,20 +32,20 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
*/
trait CollectorProperties
{
private array $fields;
private bool $hasAccountInfo;
private bool $hasBillInformation;
private bool $hasBudgetInformation;
private bool $hasCatInformation;
private bool $hasJoinedAttTables;
private bool $hasJoinedMetaTables;
private bool $hasJoinedTagTables;
private bool $hasNotesInformation;
private array $integerFields;
private ?int $limit;
private ?int $page;
private array $fields;
private bool $hasAccountInfo;
private bool $hasBillInformation;
private bool $hasBudgetInformation;
private bool $hasCatInformation;
private bool $hasJoinedAttTables;
private bool $hasJoinedMetaTables;
private bool $hasJoinedTagTables;
private bool $hasNotesInformation;
private array $integerFields;
private ?int $limit;
private ?int $page;
private array $postFilters;
private HasMany $query;
private int $total;
private ?User $user;
private array $postFilters;
private int $total;
private ?User $user;
}

View File

@@ -39,6 +39,50 @@ use Log;
*/
trait MetaCollection
{
/**
* @inheritDoc
*/
public function externalIdContains(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* Where has no tags.
@@ -53,6 +97,82 @@ trait MetaCollection
return $this;
}
/**
* @return GroupCollectorInterface
*/
public function withTagInformation(): GroupCollectorInterface
{
$this->fields[] = 'tags.id as tag_id';
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
$this->joinTagTables();
return $this;
}
/**
* Join table to get tag information.
*/
protected function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
}
}
/**
* @inheritDoc
*/
public function internalReferenceContains(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceEnds(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceStarts(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* @param string $value
*
@@ -66,6 +186,29 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function withNotes(): GroupCollectorInterface
{
if (false === $this->hasNotesInformation) {
// join bill table
$this->query->leftJoin(
'notes',
static function (JoinClause $join) {
$join->on('notes.noteable_id', '=', 'transaction_journals.id');
$join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal');
$join->whereNull('notes.deleted_at');
}
);
// add fields
$this->fields[] = 'notes.text as notes';
$this->hasNotesInformation = true;
}
return $this;
}
/**
* @param string $value
*
@@ -120,6 +263,25 @@ trait MetaCollection
return $this;
}
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/**
* Limit the search to a specific set of bills.
*
@@ -150,6 +312,27 @@ trait MetaCollection
return $this;
}
/**
* Will include budget ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withBudgetInformation(): GroupCollectorInterface
{
if (false === $this->hasBudgetInformation) {
// join link table
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id');
// add fields
$this->fields[] = 'budgets.id as budget_id';
$this->fields[] = 'budgets.name as budget_name';
$this->hasBudgetInformation = true;
}
return $this;
}
/**
* Limit the search to a specific set of budgets.
*
@@ -184,6 +367,27 @@ trait MetaCollection
return $this;
}
/**
* Will include category ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface
{
if (false === $this->hasCatInformation) {
// join link table
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id');
// add fields
$this->fields[] = 'categories.id as category_id';
$this->fields[] = 'categories.name as category_name';
$this->hasCatInformation = true;
}
return $this;
}
/**
* Limit the search to a specific category.
*
@@ -214,87 +418,6 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function externalIdContains(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function withoutExternalUrl(): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where(function (Builder $q1) {
$q1->where(function (Builder $q2) {
$q2->where('journal_meta.name', '=', 'external_url');
$q2->whereNull('journal_meta.data');
})->orWhere(function (Builder $q3) {
$q3->where('journal_meta.name', '!=', 'external_url');
});
});
return $this;
}
/**
* @inheritDoc
*/
public function withExternalUrl(): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->whereNotNull('journal_meta.data');
return $this;
}
/**
* @inheritDoc
*/
@@ -353,11 +476,11 @@ trait MetaCollection
$this->withTagInformation();
// this method adds a "postFilter" to the collector.
$list = $tags->pluck('tag')->toArray();
$filter = function (int $index, array $object) use ($list): bool {
foreach($object['transactions'] as $transaction) {
foreach($transaction['tags'] as $tag) {
if(in_array($tag['name'], $list)) {
$list = $tags->pluck('tag')->toArray();
$filter = function (int $index, array $object) use ($list): bool {
foreach ($object['transactions'] as $transaction) {
foreach ($transaction['tags'] as $tag) {
if (in_array($tag['name'], $list)) {
return false;
}
}
@@ -394,25 +517,6 @@ trait MetaCollection
return $this;
}
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/**
* Limit results to a transactions without a budget..
*
@@ -426,27 +530,6 @@ trait MetaCollection
return $this;
}
/**
* Will include budget ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withBudgetInformation(): GroupCollectorInterface
{
if (false === $this->hasBudgetInformation) {
// join link table
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id');
// add fields
$this->fields[] = 'budgets.id as budget_id';
$this->fields[] = 'budgets.name as budget_name';
$this->hasBudgetInformation = true;
}
return $this;
}
/**
* Limit results to a transactions without a category.
*
@@ -460,64 +543,17 @@ trait MetaCollection
return $this;
}
/**
* Will include category ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface
{
if (false === $this->hasCatInformation) {
// join link table
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id');
// add fields
$this->fields[] = 'categories.id as category_id';
$this->fields[] = 'categories.name as category_name';
$this->hasCatInformation = true;
}
return $this;
}
/**
* @inheritDoc
*/
public function withNotes(): GroupCollectorInterface
public function withExternalUrl(): GroupCollectorInterface
{
if (false === $this->hasNotesInformation) {
// join bill table
$this->query->leftJoin(
'notes',
static function (JoinClause $join) {
$join->on('notes.noteable_id', '=', 'transaction_journals.id');
$join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal');
$join->whereNull('notes.deleted_at');
}
);
// add fields
$this->fields[] = 'notes.text as notes';
$this->hasNotesInformation = true;
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
return $this;
}
/**
* @return GroupCollectorInterface
*/
public function withTagInformation(): GroupCollectorInterface
{
$this->fields[] = 'tags.id as tag_id';
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
$this->joinTagTables();
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->whereNotNull('journal_meta.data');
return $this;
}
@@ -560,6 +596,27 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function withoutExternalUrl(): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where(function (Builder $q1) {
$q1->where(function (Builder $q2) {
$q2->where('journal_meta.name', '=', 'external_url');
$q2->whereNull('journal_meta.data');
})->orWhere(function (Builder $q3) {
$q3->where('journal_meta.name', '!=', 'external_url');
});
});
return $this;
}
/**
* @return GroupCollectorInterface
*/
@@ -586,17 +643,4 @@ trait MetaCollection
return $this;
}
/**
* Join table to get tag information.
*/
protected function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
}
}
}

View File

@@ -33,6 +33,45 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
trait TimeCollection
{
public function dayAfter(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '>=', $day);
return $this;
}
public function dayBefore(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '<=', $day);
return $this;
}
public function dayIs(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '=', $day);
return $this;
}
public function monthAfter(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '>=', $month);
return $this;
}
public function monthBefore(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '<=', $month);
return $this;
}
public function monthIs(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '=', $month);
return $this;
}
/**
* Collect transactions after a specific date.
*
@@ -120,22 +159,9 @@ trait TimeCollection
return $this;
}
public function yearIs(string $year): GroupCollectorInterface
public function yearAfter(string $year): GroupCollectorInterface
{
$this->query->whereYear('transaction_journals.date', '=', $year);
return $this;
}
public function monthIs(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '=', $month);
return $this;
}
public function dayIs(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '=', $day);
$this->query->whereYear('transaction_journals.date', '>=', $year);
return $this;
}
@@ -145,35 +171,9 @@ trait TimeCollection
return $this;
}
public function monthBefore(string $month): GroupCollectorInterface
public function yearIs(string $year): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '<=', $month);
return $this;
}
public function dayBefore(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '<=', $day);
return $this;
}
public function yearAfter(string $year): GroupCollectorInterface
{
$this->query->whereYear('transaction_journals.date', '>=', $year);
return $this;
}
public function monthAfter(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '>=', $month);
return $this;
}
public function dayAfter(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '>=', $day);
$this->query->whereYear('transaction_journals.date', '=', $year);
return $this;
}
}

View File

@@ -203,6 +203,26 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
*
*/
public function dumpQuery(): void
{
echo $this->query->select($this->fields)->toSql();
echo '<pre>';
print_r($this->query->getBindings());
echo '</pre>';
}
/**
*
*/
public function dumpQueryInLogs(): void
{
Log::debug($this->query->select($this->fields)->toSql());
Log::debug('Bindings', $this->query->getBindings());
}
/**
* @inheritDoc
*/
@@ -263,388 +283,6 @@ class GroupCollector implements GroupCollectorInterface
return $collection;
}
/**
* Same as getGroups but everything is in a paginator.
*
* @return LengthAwarePaginator
*/
public function getPaginatedGroups(): LengthAwarePaginator
{
$set = $this->getGroups();
if (0 === $this->limit) {
$this->setLimit(50);
}
return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page);
}
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface
{
Log::debug('Add filter on attachment ID.');
$this->joinAttachmentTables();
$this->query->whereNotNull('attachments.attachable_id');
return $this;
}
/**
* Limit results to a specific currency, either foreign or normal one.
*
* @param TransactionCurrency $currency
*
* @return GroupCollectorInterface
*/
public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($currency) {
$q->where('source.transaction_currency_id', $currency->id);
$q->orWhere('source.foreign_currency_id', $currency->id);
}
);
return $this;
}
/**
* @inheritDoc
*/
public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface
{
$this->query->where('source.foreign_currency_id', $currency->id);
return $this;
}
/**
* Limit the result to a set of specific transaction groups.
*
* @param array $groupIds
*
* @return GroupCollectorInterface
*/
public function setIds(array $groupIds): GroupCollectorInterface
{
$this->query->whereIn('transaction_groups.id', $groupIds);
return $this;
}
/**
* Limit the result to a set of specific journals.
*
* @param array $journalIds
*
* @return GroupCollectorInterface
*/
public function setJournalIds(array $journalIds): GroupCollectorInterface
{
if (!empty($journalIds)) {
// make all integers.
$integerIDs = array_map('intval', $journalIds);
$this->query->whereIn('transaction_journals.id', $integerIDs);
}
return $this;
}
/**
* Limit the number of returned entries.
*
* @param int $limit
*
* @return GroupCollectorInterface
*/
public function setLimit(int $limit): GroupCollectorInterface
{
$this->limit = $limit;
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
return $this;
}
/**
* Set the page to get.
*
* @param int $page
*
* @return GroupCollectorInterface
*/
public function setPage(int $page): GroupCollectorInterface
{
$page = 0 === $page ? 1 : $page;
$this->page = $page;
app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
return $this;
}
/**
* Search for words in descriptions.
*
* @param array $array
*
* @return GroupCollectorInterface
*/
public function setSearchWords(array $array): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($array) {
$q->where(
static function (EloquentBuilder $q1) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
}
}
);
$q->orWhere(
static function (EloquentBuilder $q2) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
}
}
);
}
);
return $this;
}
/**
* Limit the search to one specific transaction group.
*
* @param TransactionGroup $transactionGroup
*
* @return GroupCollectorInterface
*/
public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
{
$this->query->where('transaction_groups.id', $transactionGroup->id);
return $this;
}
/**
* Limit the included transaction types.
*
* @param array $types
*
* @return GroupCollectorInterface
*/
public function setTypes(array $types): GroupCollectorInterface
{
$this->query->whereIn('transaction_types.type', $types);
return $this;
}
/**
* Set the user object and start the query.
*
* @param User $user
*
* @return GroupCollectorInterface
*/
public function setUser(User $user): GroupCollectorInterface
{
if (null === $this->user) {
$this->user = $user;
$this->startQuery();
}
return $this;
}
/**
* Automatically include all stuff required to make API calls work.
*
* @return GroupCollectorInterface
*/
public function withAPIInformation(): GroupCollectorInterface
{
// include source + destination account name and type.
$this->withAccountInformation()
// include category ID + name (if any)
->withCategoryInformation()
// include budget ID + name (if any)
->withBudgetInformation()
// include bill ID + name (if any)
->withBillInformation();
return $this;
}
/**
* @inheritDoc
*/
public function withAttachmentInformation(): GroupCollectorInterface
{
$this->fields[] = 'attachments.id as attachment_id';
$this->fields[] = 'attachments.uploaded as attachment_uploaded';
$this->joinAttachmentTables();
return $this;
}
/**
* Join table to get attachment information.
*/
private function joinAttachmentTables(): void
{
if (false === $this->hasJoinedAttTables) {
// join some extra tables:
$this->hasJoinedAttTables = true;
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
->where(
static function (EloquentBuilder $q1) {
$q1->where('attachments.attachable_type', TransactionJournal::class);
//$q1->where('attachments.uploaded', true);
$q1->orWhereNull('attachments.attachable_type');
}
);
}
}
/**
* Build the query.
*/
private function startQuery(): void
{
//app('log')->debug('GroupCollector::startQuery');
$this->query = $this->user
//->transactionGroups()
//->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
->transactionJournals()
->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
// join source transaction.
->leftJoin(
'transactions as source',
function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
->where('source.amount', '<', 0);
}
)
// join destination transaction
->leftJoin(
'transactions as destination',
function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
->where('destination.amount', '>', 0);
}
)
// left join transaction type.
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id')
->whereNull('transaction_groups.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereNull('source.deleted_at')
->whereNull('destination.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('source.amount', 'DESC');
}
/**
*
*/
public function dumpQuery(): void
{
echo $this->query->select($this->fields)->toSql();
echo '<pre>';
print_r($this->query->getBindings());
echo '</pre>';
}
/**
*
*/
public function dumpQueryInLogs(): void
{
Log::debug($this->query->select($this->fields)->toSql());
Log::debug('Bindings', $this->query->getBindings());
}
/**
* Convert a selected set of fields to arrays.
*
* @param array $array
*
* @return array
*/
private function convertToInteger(array $array): array
{
foreach ($this->integerFields as $field) {
$array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null;
}
return $array;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('attachment_id', $newArray)) {
$attachmentId = (int) $newJournal['attachment_id'];
$existingJournal['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
return $existingJournal;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well.
$tagId = (int) $newJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int) $newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
];
}
return $existingJournal;
}
/**
* @param Collection $collection
*
@@ -754,6 +392,72 @@ class GroupCollector implements GroupCollectorInterface
return $result;
}
/**
* Convert a selected set of fields to arrays.
*
* @param array $array
*
* @return array
*/
private function convertToInteger(array $array): array
{
foreach ($this->integerFields as $field) {
$array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null;
}
return $array;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well.
$tagId = (int) $newJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int) $newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
];
}
return $existingJournal;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('attachment_id', $newArray)) {
$attachmentId = (int) $newJournal['attachment_id'];
$existingJournal['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
return $existingJournal;
}
/**
* @param array $groups
*
@@ -824,4 +528,300 @@ class GroupCollector implements GroupCollectorInterface
}
return $newCollection;
}
/**
* Same as getGroups but everything is in a paginator.
*
* @return LengthAwarePaginator
*/
public function getPaginatedGroups(): LengthAwarePaginator
{
$set = $this->getGroups();
if (0 === $this->limit) {
$this->setLimit(50);
}
return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page);
}
/**
* Limit the number of returned entries.
*
* @param int $limit
*
* @return GroupCollectorInterface
*/
public function setLimit(int $limit): GroupCollectorInterface
{
$this->limit = $limit;
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
return $this;
}
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface
{
Log::debug('Add filter on attachment ID.');
$this->joinAttachmentTables();
$this->query->whereNotNull('attachments.attachable_id');
return $this;
}
/**
* Join table to get attachment information.
*/
private function joinAttachmentTables(): void
{
if (false === $this->hasJoinedAttTables) {
// join some extra tables:
$this->hasJoinedAttTables = true;
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
->where(
static function (EloquentBuilder $q1) {
$q1->where('attachments.attachable_type', TransactionJournal::class);
//$q1->where('attachments.uploaded', true);
$q1->orWhereNull('attachments.attachable_type');
}
);
}
}
/**
* Limit results to a specific currency, either foreign or normal one.
*
* @param TransactionCurrency $currency
*
* @return GroupCollectorInterface
*/
public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($currency) {
$q->where('source.transaction_currency_id', $currency->id);
$q->orWhere('source.foreign_currency_id', $currency->id);
}
);
return $this;
}
/**
* @inheritDoc
*/
public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface
{
$this->query->where('source.foreign_currency_id', $currency->id);
return $this;
}
/**
* Limit the result to a set of specific transaction groups.
*
* @param array $groupIds
*
* @return GroupCollectorInterface
*/
public function setIds(array $groupIds): GroupCollectorInterface
{
$this->query->whereIn('transaction_groups.id', $groupIds);
return $this;
}
/**
* Limit the result to a set of specific journals.
*
* @param array $journalIds
*
* @return GroupCollectorInterface
*/
public function setJournalIds(array $journalIds): GroupCollectorInterface
{
if (!empty($journalIds)) {
// make all integers.
$integerIDs = array_map('intval', $journalIds);
$this->query->whereIn('transaction_journals.id', $integerIDs);
}
return $this;
}
/**
* Set the page to get.
*
* @param int $page
*
* @return GroupCollectorInterface
*/
public function setPage(int $page): GroupCollectorInterface
{
$page = 0 === $page ? 1 : $page;
$this->page = $page;
app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
return $this;
}
/**
* Search for words in descriptions.
*
* @param array $array
*
* @return GroupCollectorInterface
*/
public function setSearchWords(array $array): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($array) {
$q->where(
static function (EloquentBuilder $q1) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
}
}
);
$q->orWhere(
static function (EloquentBuilder $q2) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
}
}
);
}
);
return $this;
}
/**
* Limit the search to one specific transaction group.
*
* @param TransactionGroup $transactionGroup
*
* @return GroupCollectorInterface
*/
public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
{
$this->query->where('transaction_groups.id', $transactionGroup->id);
return $this;
}
/**
* Limit the included transaction types.
*
* @param array $types
*
* @return GroupCollectorInterface
*/
public function setTypes(array $types): GroupCollectorInterface
{
$this->query->whereIn('transaction_types.type', $types);
return $this;
}
/**
* Set the user object and start the query.
*
* @param User $user
*
* @return GroupCollectorInterface
*/
public function setUser(User $user): GroupCollectorInterface
{
if (null === $this->user) {
$this->user = $user;
$this->startQuery();
}
return $this;
}
/**
* Build the query.
*/
private function startQuery(): void
{
//app('log')->debug('GroupCollector::startQuery');
$this->query = $this->user
//->transactionGroups()
//->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
->transactionJournals()
->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
// join source transaction.
->leftJoin(
'transactions as source',
function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
->where('source.amount', '<', 0);
}
)
// join destination transaction
->leftJoin(
'transactions as destination',
function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
->where('destination.amount', '>', 0);
}
)
// left join transaction type.
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id')
->whereNull('transaction_groups.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereNull('source.deleted_at')
->whereNull('destination.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('source.amount', 'DESC');
}
/**
* Automatically include all stuff required to make API calls work.
*
* @return GroupCollectorInterface
*/
public function withAPIInformation(): GroupCollectorInterface
{
// include source + destination account name and type.
$this->withAccountInformation()
// include category ID + name (if any)
->withCategoryInformation()
// include budget ID + name (if any)
->withBudgetInformation()
// include bill ID + name (if any)
->withBillInformation();
return $this;
}
/**
* @inheritDoc
*/
public function withAttachmentInformation(): GroupCollectorInterface
{
$this->fields[] = 'attachments.id as attachment_id';
$this->fields[] = 'attachments.uploaded as attachment_uploaded';
$this->joinAttachmentTables();
return $this;
}
}

View File

@@ -66,6 +66,24 @@ interface GroupCollectorInterface
*/
public function amountMore(string $amount): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayAfter(string $day): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayBefore(string $day): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayIs(string $day): GroupCollectorInterface;
/**
* End of the description must match:
*
@@ -111,6 +129,24 @@ interface GroupCollectorInterface
*/
public function excludeSourceAccounts(Collection $accounts): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface;
/**
* Ensure the search will find nothing at all, zero results.
*
@@ -151,6 +187,42 @@ interface GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthAfter(string $month): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthBefore(string $month): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthIs(string $month): GroupCollectorInterface;
/**
* @param string $value
*
@@ -305,38 +377,6 @@ interface GroupCollectorInterface
*/
public function setExternalId(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface;
/**
* Transactions without an external URL
*
* @return GroupCollectorInterface
*/
public function withoutExternalUrl(): GroupCollectorInterface;
/**
* Transactions with an external URL
*
* @return GroupCollectorInterface
*/
public function withExternalUrl(): GroupCollectorInterface;
/**
* Limit results to a specific foreign currency.
*
@@ -437,15 +477,6 @@ interface GroupCollectorInterface
*/
public function setTags(Collection $tags): GroupCollectorInterface;
/**
* Only when does not have these tags
*
* @param Collection $tags
*
* @return GroupCollectorInterface
*/
public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface;
/**
* Limit the search to one specific transaction group.
*
@@ -482,6 +513,15 @@ interface GroupCollectorInterface
*/
public function setUser(User $user): GroupCollectorInterface;
/**
* Only when does not have these tags
*
* @param Collection $tags
*
* @return GroupCollectorInterface
*/
public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface;
/**
* Either account can be set, but NOT both. This effectively excludes internal transfers.
*
@@ -561,6 +601,13 @@ interface GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface;
/**
* Transactions with an external URL
*
* @return GroupCollectorInterface
*/
public function withExternalUrl(): GroupCollectorInterface;
/**
* Will include notes.
*
@@ -596,6 +643,13 @@ interface GroupCollectorInterface
*/
public function withoutCategory(): GroupCollectorInterface;
/**
* Transactions without an external URL
*
* @return GroupCollectorInterface
*/
public function withoutExternalUrl(): GroupCollectorInterface;
/**
* @return GroupCollectorInterface
*/
@@ -606,15 +660,22 @@ interface GroupCollectorInterface
*/
public function withoutTags(): GroupCollectorInterface;
public function yearIs(string $year): GroupCollectorInterface;
public function monthIs(string $month): GroupCollectorInterface;
public function dayIs(string $day): GroupCollectorInterface;
public function yearBefore(string $year): GroupCollectorInterface;
public function monthBefore(string $month): GroupCollectorInterface;
public function dayBefore(string $day): GroupCollectorInterface;
/**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearAfter(string $year): GroupCollectorInterface;
public function monthAfter(string $month): GroupCollectorInterface;
public function dayAfter(string $day): GroupCollectorInterface;
/**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearBefore(string $year): GroupCollectorInterface;
/**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearIs(string $year): GroupCollectorInterface;
}