Expand search and add operators.

This commit is contained in:
James Cole 2022-09-28 07:35:57 +02:00
parent 6eaed9829b
commit 9b7285ea84
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
7 changed files with 865 additions and 118 deletions

View File

@ -62,6 +62,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameDoesNotContain(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = !str_contains(strtolower($attachment['filename']), strtolower($name)) && !str_contains(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* Has attachments
*
@ -135,6 +161,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameDoesNotEnd(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = !str_ends_with(strtolower($attachment['filename']), strtolower($name)) && !str_ends_with(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
@ -161,6 +213,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameIsNot(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = $attachment['filename'] !== $name && $attachment['title'] !== $name;
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
@ -187,6 +265,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameDoesNotStart(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = !str_starts_with(strtolower($attachment['filename']), strtolower($name)) && !str_starts_with(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
@ -213,6 +317,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesAreNot(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && $notes !== $value;
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
@ -239,6 +369,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesDoNotContain(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && !str_contains(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
@ -265,6 +421,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesDoNotEnd(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && !str_ends_with(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
@ -291,6 +473,32 @@ trait AttachmentCollection
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesDoNotStart(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && !str_starts_with(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* Has attachments
*

View File

@ -135,6 +135,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function externalIdDoesNotContain(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* Join table to get tag information.
*/
@ -160,6 +172,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function externalIdDoesNotEnd(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
@ -172,6 +196,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function externalIdDoesNotStart(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
@ -187,6 +223,23 @@ trait MetaCollection
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlDoesNotContain(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', trim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $url));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
@ -202,6 +255,23 @@ trait MetaCollection
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlDoesNotEnd(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', ltrim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s', $url));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
@ -219,6 +289,23 @@ trait MetaCollection
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlDoesNotStart(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', rtrim($url, '"'));
//var_dump($url);
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%s%%', $url));
return $this;
}
/**
* Where has no tags.
*
@ -275,6 +362,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceDoesNotContain(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* @inheritDoc
*/
@ -287,6 +386,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceDoesNotEnd(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
@ -299,6 +410,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceDoesNotStart(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* @param string $value
*
@ -605,6 +728,19 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function excludeExternalId(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', '!=', sprintf('%s', json_encode($externalId)));
return $this;
}
/**
* @inheritDoc
*/
@ -617,6 +753,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function excludeExternalUrl(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', '!=', json_encode($url));
return $this;
}
/**
* @inheritDoc
*/
@ -630,6 +778,19 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function excludeInternalReference(string $internalReference): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $internalReference));
return $this;
}
/**
* @inheritDoc
*/
@ -642,6 +803,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function excludeRecurrenceId(string $recurringId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'recurrence_id');
$this->query->where('journal_meta.data', '!=', sprintf('%s', json_encode($recurringId)));
return $this;
}
/**
* Limit results to a specific tag.
*

View File

@ -79,18 +79,42 @@ interface GroupCollectorInterface
*/
public function attachmentNameContains(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameDoesNotContain(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameEnds(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameDoesNotEnd(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameIs(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameIsNot(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameDoesNotStart(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
@ -103,12 +127,24 @@ interface GroupCollectorInterface
*/
public function attachmentNotesAre(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesAreNot(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesContains(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesDoNotContain(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
@ -121,6 +157,18 @@ interface GroupCollectorInterface
*/
public function attachmentNotesStarts(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesDoNotEnd(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesDoNotStart(string $value): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
@ -358,36 +406,76 @@ interface GroupCollectorInterface
*/
public function externalIdContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdDoesNotContain(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdDoesNotEnd(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdDoesNotStart(string $externalId): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlContains(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlDoesNotContain(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlEnds(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlDoesNotEnd(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlStarts(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlDoesNotStart(string $url): GroupCollectorInterface;
/**
* Ensure the search will find nothing at all, zero results.
*
@ -477,18 +565,36 @@ interface GroupCollectorInterface
*/
public function internalReferenceContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceDoesNotContain(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceDoesNotEnd(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceDoesNotStart(string $externalId): GroupCollectorInterface;
/**
* Only journals that are reconciled.
*
@ -876,12 +982,27 @@ interface GroupCollectorInterface
*/
public function setExternalId(string $externalId): GroupCollectorInterface;
/**
* Look for specific external ID's.
*
* @param string $externalId
*
* @return GroupCollectorInterface
*/
public function excludeExternalId(string $externalId): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function setExternalUrl(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function excludeExternalUrl(string $url): GroupCollectorInterface;
/**
* Limit results to a specific foreign currency.
*
@ -909,6 +1030,17 @@ interface GroupCollectorInterface
*/
public function setInternalReference(string $externalId): GroupCollectorInterface;
/**
* Look for specific external ID's.
*
* @param string $externalId
*
* @return GroupCollectorInterface
*/
public function excludeInternalReference(string $externalId): GroupCollectorInterface;
/**
* Limit the result to a set of specific transaction journals.
*
@ -1015,6 +1147,13 @@ interface GroupCollectorInterface
*/
public function setRecurrenceId(string $recurringId): GroupCollectorInterface;
/**
* @param string $recurringId
*
* @return GroupCollectorInterface
*/
public function excludeRecurrenceId(string $recurringId): GroupCollectorInterface;
/**
* Search for words in descriptions.
*

View File

@ -33,7 +33,7 @@ use Log;
*/
class ParseDateString
{
private $keywords
private array $keywords
= [
'today',
'yesterday',
@ -80,6 +80,7 @@ class ParseDateString
*/
public function parseDate(string $date): Carbon
{
Log::debug(sprintf('parseDate("%s")', $date));
$date = strtolower($date);
// parse keywords:
if (in_array($date, $this->keywords, true)) {
@ -118,7 +119,7 @@ class ParseDateString
return new Carbon(sprintf('%d-01-01', $date));
}
throw new FireflyException(sprintf('[d]Not a recognised date format: "%s"', $date));
throw new FireflyException(sprintf('[d] Not a recognised date format: "%s"', $date));
}
/**

View File

@ -190,7 +190,7 @@ class OperatorQuerySearch implements SearchInterface
case Subquery::class:
// loop all notes in subquery:
foreach ($searchNode->getNodes() as $subNode) { // @phpstan-ignore-line
$this->handleSearchNode($subNode); // let's hope it's not too recursive!
$this->handleSearchNode($subNode); // let's hope it's not too recursive
}
break;
case Word::class:
@ -254,8 +254,8 @@ class OperatorQuerySearch implements SearchInterface
private function updateCollector(string $operator, string $value, bool $prohibited): bool
{
if ($prohibited) {
Log::debug(sprintf('Operator "%s" is now "%s"', $operator, '!' . $operator));
$operator = sprintf('!%s', $operator);
Log::debug(sprintf('Operator "%s" is now "%s"', $operator, sprintf('-%s', $operator)));
$operator = sprintf('-%s', $operator);
}
Log::debug(sprintf('Now in updateCollector("%s", "%s")', $operator, $value));
@ -278,97 +278,97 @@ class OperatorQuerySearch implements SearchInterface
case 'account_is':
$this->searchAccount($value, 3, 4);
break;
case '!account_is':
case '-account_is':
$this->searchAccount($value, 3, 4, true);
break;
case 'account_contains':
$this->searchAccount($value, 3, 3);
break;
case '!account_contains':
case '-account_contains':
$this->searchAccount($value, 3, 3, true);
break;
case 'account_ends':
$this->searchAccount($value, 3, 2);
break;
case '!account_ends':
case '-account_ends':
$this->searchAccount($value, 3, 2, true);
break;
case 'account_starts':
$this->searchAccount($value, 3, 1);
break;
case '!account_starts':
case '-account_starts':
$this->searchAccount($value, 3, 1, true);
break;
case 'account_nr_is':
$this->searchAccountNr($value, 3, 4);
break;
case '!account_nr_is':
case '-account_nr_is':
$this->searchAccountNr($value, 3, 4, true);
break;
case 'account_nr_contains':
$this->searchAccountNr($value, 3, 3);
break;
case '!account_nr_contains':
case '-account_nr_contains':
$this->searchAccountNr($value, 3, 3, true);
break;
case 'account_nr_ends':
$this->searchAccountNr($value, 3, 2);
break;
case '!account_nr_ends':
case '-account_nr_ends':
$this->searchAccountNr($value, 3, 2, true);
break;
case 'account_nr_starts':
$this->searchAccountNr($value, 3, 1);
break;
case '!account_nr_starts':
case '-account_nr_starts':
$this->searchAccountNr($value, 3, 1, true);
break;
case 'source_account_starts':
$this->searchAccount($value, 1, 1);
break;
case '!source_account_starts':
case '-source_account_starts':
$this->searchAccount($value, 1, 1, true);
break;
case 'source_account_ends':
$this->searchAccount($value, 1, 2);
break;
case '!source_account_ends':
case '-source_account_ends':
$this->searchAccount($value, 1, 2, true);
break;
case 'source_account_is':
$this->searchAccount($value, 1, 4);
break;
case '!source_account_is':
case '-source_account_is':
$this->searchAccount($value, 1, 4, true);
break;
case 'source_account_nr_starts':
$this->searchAccountNr($value, 1, 1);
break;
case '!source_account_nr_starts':
case '-source_account_nr_starts':
$this->searchAccountNr($value, 1, 1, true);
break;
case 'source_account_nr_ends':
$this->searchAccountNr($value, 1, 2);
break;
case '!source_account_nr_ends':
case '-source_account_nr_ends':
$this->searchAccountNr($value, 1, 2, true);
break;
case 'source_account_nr_is':
$this->searchAccountNr($value, 1, 4);
break;
case '!source_account_nr_is':
case '-source_account_nr_is':
$this->searchAccountNr($value, 1, 4, true);
break;
case 'source_account_nr_contains':
$this->searchAccountNr($value, 1, 3);
break;
case '!source_account_nr_contains':
case '-source_account_nr_contains':
$this->searchAccountNr($value, 1, 3, true);
break;
case 'source_account_contains':
$this->searchAccount($value, 1, 3);
break;
case '!source_account_contains':
case '-source_account_contains':
$this->searchAccount($value, 1, 3, true);
break;
case 'source_account_id':
@ -381,7 +381,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!source_account_id':
case '-source_account_id':
$account = $this->accountRepository->find((int) $value);
if (null !== $account) {
$this->collector->excludeSourceAccounts(new Collection([$account]));
@ -395,7 +395,7 @@ class OperatorQuerySearch implements SearchInterface
$parts = explode(',', $value);
$this->collector->setJournalIds($parts);
break;
case '!journal_id':
case '-journal_id':
$parts = explode(',', $value);
$this->collector->excludeJournalIds($parts);
break;
@ -403,56 +403,56 @@ class OperatorQuerySearch implements SearchInterface
$parts = explode(',', $value);
$this->collector->setIds($parts);
break;
case '!id':
case '-id':
$parts = explode(',', $value);
$this->collector->excludeIds($parts);
break;
case 'destination_account_starts':
$this->searchAccount($value, 2, 1);
break;
case '!destination_account_starts':
case '-destination_account_starts':
$this->searchAccount($value, 2, 1, true);
break;
case 'destination_account_ends':
$this->searchAccount($value, 2, 2);
break;
case '!destination_account_ends':
case '-destination_account_ends':
$this->searchAccount($value, 2, 2, true);
break;
case 'destination_account_nr_starts':
$this->searchAccountNr($value, 2, 1);
break;
case '!destination_account_nr_starts':
case '-destination_account_nr_starts':
$this->searchAccountNr($value, 2, 1, true);
break;
case 'destination_account_nr_ends':
$this->searchAccountNr($value, 2, 2);
break;
case '!destination_account_nr_ends':
case '-destination_account_nr_ends':
$this->searchAccountNr($value, 2, 2, true);
break;
case 'destination_account_nr_is':
$this->searchAccountNr($value, 2, 4);
break;
case '!destination_account_nr_is':
case '-destination_account_nr_is':
$this->searchAccountNr($value, 2, 4, true);
break;
case 'destination_account_is':
$this->searchAccount($value, 2, 4);
break;
case '!destination_account_is':
case '-destination_account_is':
$this->searchAccount($value, 2, 4, true);
break;
case 'destination_account_nr_contains':
$this->searchAccountNr($value, 2, 3);
break;
case '!destination_account_nr_contains':
case '-destination_account_nr_contains':
$this->searchAccountNr($value, 2, 3, true);
break;
case 'destination_account_contains':
$this->searchAccount($value, 2, 3);
break;
case '!destination_account_contains':
case '-destination_account_contains':
$this->searchAccount($value, 2, 3, true);
break;
case 'destination_account_id':
@ -464,7 +464,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!destination_account_id':
case '-destination_account_id':
$account = $this->accountRepository->find((int) $value);
if (null !== $account) {
$this->collector->excludeDestinationAccounts(new Collection([$account]));
@ -489,7 +489,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!account_id':
case '-account_id':
$parts = explode(',', $value);
$collection = new Collection;
foreach ($parts as $accountId) {
@ -512,7 +512,7 @@ class OperatorQuerySearch implements SearchInterface
$account = $this->getCashAccount();
$this->collector->setSourceAccounts(new Collection([$account]));
break;
case '!source_is_cash':
case '-source_is_cash':
$account = $this->getCashAccount();
$this->collector->excludeSourceAccounts(new Collection([$account]));
break;
@ -520,7 +520,7 @@ class OperatorQuerySearch implements SearchInterface
$account = $this->getCashAccount();
$this->collector->setDestinationAccounts(new Collection([$account]));
break;
case '!destination_is_cash':
case '-destination_is_cash':
$account = $this->getCashAccount();
$this->collector->excludeDestinationAccounts(new Collection([$account]));
break;
@ -528,7 +528,7 @@ class OperatorQuerySearch implements SearchInterface
$account = $this->getCashAccount();
$this->collector->setAccounts(new Collection([$account]));
break;
case '!account_is_cash':
case '-account_is_cash':
$account = $this->getCashAccount();
$this->collector->excludeAccounts(new Collection([$account]));
break;
@ -538,27 +538,27 @@ class OperatorQuerySearch implements SearchInterface
case 'description_starts':
$this->collector->descriptionStarts([$value]);
break;
case '!description_starts':
case '-description_starts':
$this->collector->descriptionDoesNotStart([$value]);
break;
case 'description_ends':
$this->collector->descriptionEnds([$value]);
break;
case '!description_ends':
case '-description_ends':
$this->collector->descriptionDoesNotEnd([$value]);
break;
case 'description_contains':
$this->words[] = $value;
return false;
case '!description_contains':
case '-description_contains':
$this->prohibitedWords[] = $value;
break;
case 'description_is':
$this->collector->descriptionIs($value);
break;
case '!description_is':
case '-description_is':
$this->collector->descriptionIsNot($value);
break;
//
@ -573,7 +573,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!currency_is':
case '-currency_is':
$currency = $this->findCurrency($value);
if (null !== $currency) {
$this->collector->excludeCurrency($currency);
@ -591,7 +591,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!foreign_currency_is':
case '-foreign_currency_is':
$currency = $this->findCurrency($value);
if (null !== $currency) {
$this->collector->excludeForeignCurrency($currency);
@ -604,22 +604,22 @@ class OperatorQuerySearch implements SearchInterface
// attachments
//
case 'has_attachments':
case '!has_no_attachments':
case '-has_no_attachments':
Log::debug('Set collector to filter on attachments.');
$this->collector->hasAttachments();
break;
case 'has_no_attachments':
case '!has_attachments':
case '-has_attachments':
Log::debug('Set collector to filter on NO attachments.');
$this->collector->hasNoAttachments();
break;
//
// categories
case '!has_any_category':
case '-has_any_category':
case 'has_no_category':
$this->collector->withoutCategory();
break;
case '!has_no_category':
case '-has_no_category':
case 'has_any_category':
$this->collector->withCategory();
break;
@ -631,7 +631,7 @@ class OperatorQuerySearch implements SearchInterface
}
$this->collector->findNothing();
break;
case '!category_is':
case '-category_is':
$category = $this->categoryRepository->findByName($value);
if (null !== $category) {
$this->collector->excludeCategory($category);
@ -647,7 +647,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!category_ends':
case '-category_ends':
$result = $this->categoryRepository->categoryEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeCategories($result);
@ -665,7 +665,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!category_starts':
case '-category_starts':
$result = $this->categoryRepository->categoryStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeCategories($result);
@ -683,7 +683,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!category_contains':
case '-category_contains':
$result = $this->categoryRepository->searchCategory($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeCategories($result);
@ -695,12 +695,12 @@ class OperatorQuerySearch implements SearchInterface
//
// budgets
//
case '!has_any_budget':
case '-has_any_budget':
case 'has_no_budget':
$this->collector->withoutBudget();
break;
case 'has_any_budget':
case '!has_no_budget':
case '-has_no_budget':
$this->collector->withBudget();
break;
case 'budget_contains':
@ -712,7 +712,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!budget_contains':
case '-budget_contains':
$result = $this->budgetRepository->searchBudget($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBudgets($result);
@ -729,7 +729,7 @@ class OperatorQuerySearch implements SearchInterface
}
$this->collector->findNothing();
break;
case '!budget_is':
case '-budget_is':
$budget = $this->budgetRepository->findByName($value);
if (null !== $budget) {
$this->collector->excludeBudget($budget);
@ -746,7 +746,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!budget_ends':
case '-budget_ends':
$result = $this->budgetRepository->budgetEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBudgets($result);
@ -764,7 +764,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!budget_starts':
case '-budget_starts':
$result = $this->budgetRepository->budgetStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBudgets($result);
@ -776,11 +776,11 @@ class OperatorQuerySearch implements SearchInterface
//
// bill
//
case '!has_any_bill':
case '-has_any_bill':
case 'has_no_bill':
$this->collector->withoutBill();
break;
case '!has_no_bill':
case '-has_no_bill':
case 'has_any_bill':
$this->collector->withBill();
break;
@ -792,7 +792,7 @@ class OperatorQuerySearch implements SearchInterface
}
$this->collector->findNothing();
break;
case '!bill_contains':
case '-bill_contains':
$result = $this->billRepository->searchBill($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBills($result);
@ -808,7 +808,7 @@ class OperatorQuerySearch implements SearchInterface
}
$this->collector->findNothing();
break;
case '!bill_is':
case '-bill_is':
$bill = $this->billRepository->findByName($value);
if (null !== $bill) {
$this->collector->excludeBills(new Collection([$bill]));
@ -825,7 +825,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!bill_ends':
case '-bill_ends':
$result = $this->billRepository->billEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBills($result);
@ -843,7 +843,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!bill_starts':
case '-bill_starts':
$result = $this->billRepository->billStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBills($result);
@ -855,15 +855,15 @@ class OperatorQuerySearch implements SearchInterface
//
// tags
//
case '!has_any_tag':
case '-has_any_tag':
case 'has_no_tag':
$this->collector->withoutTags();
break;
case '!has_no_tag':
case '-has_no_tag':
case 'has_any_tag':
$this->collector->hasAnyTag();
break;
case '!tag_is_not':
case '-tag_is_not':
case 'tag_is':
$result = $this->tagRepository->searchTag($value);
if ($result->count() > 0) {
@ -875,7 +875,7 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing();
}
break;
case '!tag_is':
case '-tag_is':
case 'tag_is_not':
$result = $this->tagRepository->searchTag($value);
if ($result->count() > 0) {
@ -888,39 +888,39 @@ class OperatorQuerySearch implements SearchInterface
case 'notes_contains':
$this->collector->notesContain($value);
break;
case '!notes_contains':
case '-notes_contains':
$this->collector->notesDoNotContain($value);
break;
case 'notes_starts':
$this->collector->notesStartWith($value);
break;
case '!notes_starts':
case '-notes_starts':
$this->collector->notesDontStartWith($value);
break;
case 'notes_ends':
$this->collector->notesEndWith($value);
break;
case '!notes_ends':
case '-notes_ends':
$this->collector->notesDontEndWith($value);
break;
case 'notes_is':
$this->collector->notesExactly($value);
break;
case '!notes_is':
case '-notes_is':
$this->collector->notesExactlyNot($value);
break;
case '!any_notes':
case '-any_notes':
case 'no_notes':
$this->collector->withoutNotes();
break;
case 'any_notes':
case '!no_notes':
case '-no_notes':
$this->collector->withAnyNotes();
break;
case 'reconciled':
$this->collector->isReconciled();
break;
case '!reconciled':
case '-reconciled':
$this->collector->isNotReconciled();
break;
//
@ -934,7 +934,7 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountIs($amount);
break;
case '!amount_is':
case '-amount_is':
// strip comma's, make dots.
Log::debug(sprintf('Original value "%s"', $value));
$value = str_replace(',', '.', (string) $value);
@ -951,7 +951,7 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->foreignAmountIs($amount);
break;
case '!foreign_amount_is':
case '-foreign_amount_is':
// strip comma's, make dots.
$value = str_replace(',', '.', (string) $value);
@ -960,7 +960,7 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->foreignAmountIsNot($amount);
break;
case '!amount_more':
case '-amount_more':
case 'amount_less':
// strip comma's, make dots.
$value = str_replace(',', '.', (string) $value);
@ -969,7 +969,7 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountLess($amount);
break;
case '!foreign_amount_more':
case '-foreign_amount_more':
case 'foreign_amount_less':
// strip comma's, make dots.
$value = str_replace(',', '.', (string) $value);
@ -978,7 +978,7 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->foreignAmountLess($amount);
break;
case '!amount_less':
case '-amount_less':
case 'amount_more':
Log::debug(sprintf('Now handling operator "%s"', $operator));
// strip comma's, make dots.
@ -987,7 +987,7 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountMore($amount);
break;
case '!foreign_amount_less':
case '-foreign_amount_less':
case 'foreign_amount_more':
Log::debug(sprintf('Now handling operator "%s"', $operator));
// strip comma's, make dots.
@ -1003,158 +1003,158 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->setTypes([ucfirst($value)]);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
break;
case '!transaction_type':
case '-transaction_type':
$this->collector->excludeTypes([ucfirst($value)]);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
break;
//
// dates
//
case '!date_on':
case '-date_on':
case 'date_on':
$range = $this->parseDateRange($value);
$this->setExactDateParams($range, $prohibited);
return false;
case 'date_before':
case '!date_after':
case '-date_after':
$range = $this->parseDateRange($value);
$this->setDateBeforeParams($range);
return false;
case 'date_after':
case '!date_before':
case '-date_before':
$range = $this->parseDateRange($value);
$this->setDateAfterParams($range);
return false;
case 'interest_date_on':
case '!interest_date_on':
case '-interest_date_on':
$range = $this->parseDateRange($value);
$this->setExactMetaDateParams('interest_date', $range, $prohibited);
return false;
case 'interest_date_before':
case '!interest_date_after':
case '-interest_date_after':
$range = $this->parseDateRange($value);
$this->setMetaDateBeforeParams('interest_date', $range);
return false;
case 'interest_date_after':
case '!interest_date_before':
case '-interest_date_before':
$range = $this->parseDateRange($value);
$this->setMetaDateAfterParams('interest_date', $range);
return false;
case 'book_date_on':
case '!book_date_on':
case '-book_date_on':
$range = $this->parseDateRange($value);
$this->setExactMetaDateParams('book_date', $range, $prohibited);
return false;
case 'book_date_before':
case '!book_date_after':
case '-book_date_after':
$range = $this->parseDateRange($value);
$this->setMetaDateBeforeParams('book_date', $range);
return false;
case 'book_date_after':
case '!book_date_before':
case '-book_date_before':
$range = $this->parseDateRange($value);
$this->setMetaDateAfterParams('book_date', $range);
return false;
case 'process_date_on':
case '!process_date_on':
case '-process_date_on':
$range = $this->parseDateRange($value);
$this->setExactMetaDateParams('process_date', $range, $prohibited);
return false;
case 'process_date_before':
case '!process_date_after':
case '-process_date_after':
$range = $this->parseDateRange($value);
$this->setMetaDateBeforeParams('process_date', $range);
return false;
case 'process_date_after':
case '!process_date_before':
case '-process_date_before':
$range = $this->parseDateRange($value);
$this->setMetaDateAfterParams('process_date', $range);
return false;
case 'due_date_on':
case '!due_date_on':
case '-due_date_on':
$range = $this->parseDateRange($value);
$this->setExactMetaDateParams('due_date', $range, $prohibited);
return false;
case 'due_date_before':
case '!due_date_after':
case '-due_date_after':
$range = $this->parseDateRange($value);
$this->setMetaDateBeforeParams('due_date', $range);
return false;
case 'due_date_after':
case '!due_date_before':
case '-due_date_before':
$range = $this->parseDateRange($value);
$this->setMetaDateAfterParams('due_date', $range);
return false;
case 'payment_date_on':
case '!payment_date_on':
case '-payment_date_on':
$range = $this->parseDateRange($value);
$this->setExactMetaDateParams('payment_date', $range, $prohibited);
return false;
case 'payment_date_before':
case '!payment_date_after':
case '-payment_date_after':
$range = $this->parseDateRange($value);
$this->setMetaDateBeforeParams('payment_date', $range);
return false;
case 'payment_date_after':
case '!payment_date_before':
case '-payment_date_before':
$range = $this->parseDateRange($value);
$this->setMetaDateAfterParams('payment_date', $range);
return false;
case 'invoice_date_on':
case '!invoice_date_on':
case '-invoice_date_on':
$range = $this->parseDateRange($value);
$this->setExactMetaDateParams('invoice_date', $range, $prohibited);
return false;
case 'invoice_date_before':
case '!invoice_date_after':
case '-invoice_date_after':
$range = $this->parseDateRange($value);
$this->setMetaDateBeforeParams('invoice_date', $range);
return false;
case 'invoice_date_after':
case '!invoice_date_before':
case '-invoice_date_before':
$range = $this->parseDateRange($value);
$this->setMetaDateAfterParams('invoice_date', $range);
return false;
case 'created_at_on':
case '!created_at_on':
case '-created_at_on':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
$range = $this->parseDateRange($value);
$this->setExactObjectDateParams('created_at', $range, $prohibited);
return false;
case 'created_at_before':
case '!created_at_after':
case '-created_at_after':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
$range = $this->parseDateRange($value);
$this->setObjectDateBeforeParams('created_at', $range);
return false;
case 'created_at_after':
case '!created_at_before':
case '-created_at_before':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
$range = $this->parseDateRange($value);
$this->setObjectDateAfterParams('created_at', $range);
return false;
case 'updated_at_on':
case '!updated_at_on':
case '-updated_at_on':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
$range = $this->parseDateRange($value);
$this->setExactObjectDateParams('updated_at', $range, $prohibited);
return false;
case 'updated_at_before':
case '!updated_at_after':
case '-updated_at_after':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
$range = $this->parseDateRange($value);
$this->setObjectDateBeforeParams('updated_at', $range);
return false;
case 'updated_at_after':
case '!updated_at_before':
case '-updated_at_before':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
$range = $this->parseDateRange($value);
$this->setObjectDateAfterParams('updated_at', $range);
@ -1162,87 +1162,154 @@ class OperatorQuerySearch implements SearchInterface
//
// external URL
//
case '!any_external_url':
case '-any_external_url':
case 'no_external_url':
$this->collector->withoutExternalUrl();
break;
case '!no_external_url':
case '-no_external_url':
case 'any_external_url':
$this->collector->withExternalUrl();
break;
case 'external_url_is':
$this->collector->setExternalUrl($value);
break;
case '-external_url_is':
$this->collector->excludeExternalUrl($value);
break;
case 'external_url_contains':
$this->collector->externalUrlContains($value);
break;
case '-external_url_contains':
$this->collector->externalUrlDoesNotContain($value);
break;
case 'external_url_starts':
$this->collector->externalUrlStarts($value);
break;
case '-external_url_starts':
$this->collector->externalUrlDoesNotStart($value);
break;
case 'external_url_ends':
$this->collector->externalUrlEnds($value);
break;
case '-external_url_ends':
$this->collector->externalUrlDoesNotEnd($value);
break;
//
// other fields
//
case 'external_id_is':
$this->collector->setExternalId($value);
break;
case '-external_id_is':
$this->collector->excludeExternalId($value);
break;
case 'recurrence_id':
$this->collector->setRecurrenceId($value);
break;
case '-recurrence_id':
$this->collector->excludeRecurrenceId($value);
break;
case 'external_id_contains':
$this->collector->externalIdContains($value);
break;
case '-external_id_contains':
$this->collector->externalIdDoesNotContain($value);
break;
case 'external_id_starts':
$this->collector->externalIdStarts($value);
break;
case '-external_id_starts':
$this->collector->externalIdDoesNotStart($value);
break;
case 'external_id_ends':
$this->collector->externalIdEnds($value);
break;
case '-external_id_ends':
$this->collector->externalIdDoesNotEnd($value);
break;
case 'internal_reference_is':
$this->collector->setInternalReference($value);
break;
case '-internal_reference_is':
$this->collector->excludeInternalReference($value);
break;
case 'internal_reference_contains':
$this->collector->internalReferenceContains($value);
break;
case '-internal_reference_contains':
$this->collector->internalReferenceDoesNotContain($value);
break;
case 'internal_reference_starts':
$this->collector->internalReferenceStarts($value);
break;
case '-internal_reference_starts':
$this->collector->internalReferenceDoesNotStart($value);
break;
case 'internal_reference_ends':
$this->collector->internalReferenceEnds($value);
break;
case '-internal_reference_ends':
$this->collector->internalReferenceDoesNotEnd($value);
break;
case 'attachment_name_is':
$this->collector->attachmentNameIs($value);
break;
case '-attachment_name_is':
$this->collector->attachmentNameIsNot($value);
break;
case 'attachment_name_contains':
$this->collector->attachmentNameContains($value);
break;
case '-attachment_name_contains':
$this->collector->attachmentNameDoesNotContain($value);
break;
case 'attachment_name_starts':
$this->collector->attachmentNameStarts($value);
break;
case '-attachment_name_starts':
$this->collector->attachmentNameDoesNotStart($value);
break;
case 'attachment_name_ends':
$this->collector->attachmentNameEnds($value);
break;
case '-attachment_name_ends':
$this->collector->attachmentNameDoesNotEnd($value);
break;
case 'attachment_notes_are':
$this->collector->attachmentNotesAre($value);
break;
case '-attachment_notes_are':
$this->collector->attachmentNotesAreNot($value);
break;
case 'attachment_notes_contains':
$this->collector->attachmentNotesContains($value);
break;
case '-attachment_notes_contains':
$this->collector->attachmentNotesDoNotContain($value);
break;
case 'attachment_notes_starts':
$this->collector->attachmentNotesStarts($value);
break;
case '-attachment_notes_starts':
$this->collector->attachmentNotesDoNotStart($value);
break;
case 'attachment_notes_ends':
$this->collector->attachmentNotesEnds($value);
break;
case '-attachment_notes_ends':
$this->collector->attachmentNotesDoNotEnd($value);
break;
case 'exists':
$this->collector->exists();
break;
case '-exists':
$this->collector->findNothing();
break;
}
return true;
@ -1257,9 +1324,9 @@ class OperatorQuerySearch implements SearchInterface
public static function getRootOperator(string $operator): string
{
$original = $operator;
// if the string starts with "!" (not), we can remove it and recycle
// if the string starts with "-" (not), we can remove it and recycle
// the configuration from the original operator.
if (str_starts_with($operator, '!')) {
if (str_starts_with($operator, '-')) {
$operator = substr($operator, 1);
}
@ -1269,8 +1336,8 @@ class OperatorQuerySearch implements SearchInterface
}
if (true === $config['alias']) {
$return = $config['alias_for'];
if (str_starts_with($original, '!')) {
$return = sprintf('!%s', $config['alias_for']);
if (str_starts_with($original, '-')) {
$return = sprintf('-%s', $config['alias_for']);
}
Log::debug(sprintf('"%s" is an alias for "%s", so return that instead.', $original, $return));
@ -1423,7 +1490,7 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count()));
$filtered = $accounts->filter(
function (Account $account) use ($value, $stringMethod) {
// either IBAN or account number!
// either IBAN or account number
$ibanMatch = $stringMethod(strtolower((string) $account->iban), strtolower((string) $value));
$accountNrMatch = false;
/** @var AccountMeta $meta */

View File

@ -421,7 +421,8 @@ class General extends AbstractExtension
return new TwigFunction(
'getRootSearchOperator',
static function (string $operator): string {
return OperatorQuerySearch::getRootOperator($operator);
$result = OperatorQuerySearch::getRootOperator($operator);
return str_replace('-', 'not_', $result);
}
);
}

View File

@ -327,14 +327,17 @@ return [
'search_modifier_reconciled' => 'Transaction is reconciled',
'search_modifier_not_reconciled' => 'Transaction is not reconciled',
'search_modifier_id' => 'Transaction ID is ":value"',
'search_modifier_not_id' => 'Transaction ID is not ":value"',
'search_modifier_date_before' => 'Transaction date is before or on ":value"',
'search_modifier_date_after' => 'Transaction date is after or on ":value"',
'search_modifier_external_id_is' => 'External ID is ":value"',
'search_modifier_not_external_id_is' => 'External ID is not ":value"',
'search_modifier_no_external_url' => 'The transaction has no external URL',
'search_modifier_not_any_external_url' => 'The transaction has no external URL',
'search_modifier_any_external_url' => 'The transaction must have a (any) external URL',
'search_modifier_not_no_external_url' => 'The transaction must have a (any) external URL',
'search_modifier_internal_reference_is' => 'Internal reference is ":value"',
'search_modifier_not_internal_reference_is' => 'Internal reference is not ":value"',
'search_modifier_description_starts' => 'Description starts with ":value"',
'search_modifier_not_description_starts' => 'Description does not start with ":value"',
'search_modifier_description_ends' => 'Description ends on ":value"',
@ -395,6 +398,7 @@ return [
'search_modifier_source_account_nr_is' => 'Source account number (IBAN) is ":value"',
'search_modifier_not_source_account_nr_is' => 'Source account number (IBAN) is not ":value"',
'search_modifier_source_account_nr_contains' => 'Source account number (IBAN) contains ":value"',
'search_modifier_not_source_account_nr_contains' => 'Source account number (IBAN) does not contain ":value"',
'search_modifier_source_account_nr_starts' => 'Source account number (IBAN) starts with ":value"',
'search_modifier_not_source_account_nr_starts' => 'Source account number (IBAN) does not start with ":value"',
'search_modifier_source_account_nr_ends' => 'Source account number (IBAN) ends on ":value"',
@ -414,6 +418,7 @@ return [
'search_modifier_source_is_cash' => 'Source account is the "(cash)" account',
'search_modifier_not_source_is_cash' => 'Source account is not the "(cash)" account',
'search_modifier_destination_account_nr_is' => 'Destination account number (IBAN) is ":value"',
'search_modifier_not_destination_account_nr_is' => 'Destination account number (IBAN) is ":value"',
'search_modifier_destination_account_nr_contains' => 'Destination account number (IBAN) contains ":value"',
'search_modifier_not_destination_account_nr_contains' => 'Destination account number (IBAN) does not contain ":value"',
'search_modifier_destination_account_nr_starts' => 'Destination account number (IBAN) starts with ":value"',
@ -429,6 +434,7 @@ return [
'search_modifier_bill_is' => 'Bill is ":value"',
'search_modifier_not_bill_is' => 'Bill is not ":value"',
'search_modifier_transaction_type' => 'Transaction type is ":value"',
'search_modifier_not_transaction_type' => 'Transaction type is not ":value"',
'search_modifier_tag_is' => 'Tag is ":value"',
'search_modifier_not_tag_is' => 'No tag is ":value"',
'search_modifier_date_on_year' => 'Transaction is in year ":value"',
@ -469,6 +475,7 @@ return [
'search_modifier_category_ends' => 'Category ends on ":value"',
'search_modifier_not_category_ends' => 'Category does not end on ":value"',
'search_modifier_category_starts' => 'Category starts with ":value"',
'search_modifier_not_category_starts' => 'Category does not start with ":value"',
'search_modifier_budget_contains' => 'Budget contains ":value"',
'search_modifier_not_budget_contains' => 'Budget does not contain ":value"',
'search_modifier_budget_ends' => 'Budget ends with ":value"',
@ -482,15 +489,25 @@ return [
'search_modifier_bill_starts' => 'Bill starts with ":value"',
'search_modifier_not_bill_starts' => 'Bill does not start with ":value"',
'search_modifier_external_id_contains' => 'External ID contains ":value"',
'search_modifier_not_external_id_contains' => 'External ID does not contain ":value"',
'search_modifier_external_id_ends' => 'External ID ends with ":value"',
'search_modifier_not_external_id_ends' => 'External ID does not end with ":value"',
'search_modifier_external_id_starts' => 'External ID starts with ":value"',
'search_modifier_not_external_id_starts' => 'External ID does not start with ":value"',
'search_modifier_internal_reference_contains' => 'Internal reference contains ":value"',
'search_modifier_not_internal_reference_contains' => 'Internal reference does not contain ":value"',
'search_modifier_internal_reference_ends' => 'Internal reference ends with ":value"',
'search_modifier_internal_reference_starts' => 'Internal reference starts with ":value"',
'search_modifier_not_internal_reference_ends' => 'Internal reference does not end with ":value"',
'search_modifier_not_internal_reference_starts' => 'Internal reference does not start with ":value"',
'search_modifier_external_url_is' => 'External URL is ":value"',
'search_modifier_not_external_url_is' => 'External URL is not ":value"',
'search_modifier_external_url_contains' => 'External URL contains ":value"',
'search_modifier_not_external_url_contains' => 'External URL does not ":value"',
'search_modifier_external_url_ends' => 'External URL ends with ":value"',
'search_modifier_not_external_url_ends' => 'External URL does not end with ":value"',
'search_modifier_external_url_starts' => 'External URL starts with ":value"',
'search_modifier_not_external_url_starts' => 'External URL does not start with ":value"',
'search_modifier_has_no_attachments' => 'Transaction has no attachments',
'search_modifier_not_has_no_attachments' => 'Transaction has attachments',
'search_modifier_not_has_attachments' => 'Transaction has no attachments',
@ -499,12 +516,15 @@ return [
'search_modifier_journal_id' => 'The journal ID is ":value"',
'search_modifier_not_journal_id' => 'The journal ID is not ":value"',
'search_modifier_recurrence_id' => 'The recurring transaction ID is ":value"',
'search_modifier_not_recurrence_id' => 'The recurring transaction ID is not ":value"',
'search_modifier_foreign_amount_is' => 'The foreign amount is ":value"',
'search_modifier_not_foreign_amount_is' => 'The foreign amount is not ":value"',
'search_modifier_foreign_amount_less' => 'The foreign amount is less than ":value"',
'search_modifier_not_foreign_amount_more' => 'The foreign amount is less than ":value"',
'search_modifier_not_foreign_amount_less' => 'The foreign amount is more than ":value"',
'search_modifier_foreign_amount_more' => 'The foreign amount is more than ":value"',
'search_modifier_exists' => 'Transaction exists (any transaction)',
'search_modifier_not_exists' => 'Transaction does not exist (no transaction)',
// date fields
'search_modifier_interest_date_on' => 'Transaction interest date is ":value"',
@ -644,7 +664,15 @@ return [
'search_modifier_attachment_notes_are' => 'Any attachment\'s notes are ":value"',
'search_modifier_attachment_notes_contains' => 'Any attachment\'s notes contain ":value"',
'search_modifier_attachment_notes_starts' => 'Any attachment\'s notes start with ":value"',
'search_modifier_attachment_notes_ends' => 'Any attachment\'s notes end is ":value"',
'search_modifier_attachment_notes_ends' => 'Any attachment\'s notes end with ":value"',
'search_modifier_not_attachment_name_is' => 'Any attachment\'s name is not ":value"',
'search_modifier_not_attachment_name_contains' => 'Any attachment\'s name does not contain ":value"',
'search_modifier_not_attachment_name_starts' => 'Any attachment\'s name does not start with ":value"',
'search_modifier_not_attachment_name_ends' => 'Any attachment\'s name does not end with ":value"',
'search_modifier_not_attachment_notes_are' => 'Any attachment\'s notes are not ":value"',
'search_modifier_not_attachment_notes_contains' => 'Any attachment\'s notes do not contain ":value"',
'search_modifier_not_attachment_notes_starts' => 'Any attachment\'s notes start with ":value"',
'search_modifier_not_attachment_notes_ends' => 'Any attachment\'s notes do not end with ":value"',
'update_rule_from_query' => 'Update rule ":rule" from search query',
'create_rule_from_query' => 'Create new rule from search query',
'rule_from_search_words' => 'The rule engine has a hard time handling ":string". The suggested rule that fits your search query may give different results. Please verify the rule triggers carefully.',
@ -1001,6 +1029,136 @@ return [
'rule_trigger_exists_choice' => 'Any transaction matches(!)',
'rule_trigger_exists' => 'Any transaction matches',
// more values for new types:
'rule_trigger_not_account_id' => 'Account ID is not ":trigger_value"',
'rule_trigger_not_source_account_id' => 'Source account ID is not ":trigger_value"',
'rule_trigger_not_destination_account_id' => 'Destination account ID is not ":trigger_value"',
'rule_trigger_not_transaction_type' => 'Transaction type is not ":trigger_value"',
'rule_trigger_not_tag_is' => 'Tag is not ":trigger_value"',
'rule_trigger_not_tag_is_not' => 'Tag is ":trigger_value"',
'rule_trigger_not_description_is' => 'Description is not ":trigger_value"',
'rule_trigger_not_description_contains' => 'Description does not contain',
'rule_trigger_not_description_ends' => 'Description does not end with ":trigger_value"',
'rule_trigger_not_description_starts' => 'Description does not start with ":trigger_value"',
'rule_trigger_not_notes_is' => 'Notes are not ":trigger_value"',
'rule_trigger_not_notes_contains' => 'Notes do not contain ":trigger_value"',
'rule_trigger_not_notes_ends' => 'Notes do not end on ":trigger_value"',
'rule_trigger_not_notes_starts' => 'Notes do not start with ":trigger_value"',
'rule_trigger_not_source_account_is' => 'Source account is not ":trigger_value"',
'rule_trigger_not_source_account_contains' => 'Source account does not contain ":trigger_value"',
'rule_trigger_not_source_account_ends' => 'Source account does not end on ":trigger_value"',
'rule_trigger_not_source_account_starts' => 'Source account does not start with ":trigger_value"',
'rule_trigger_not_source_account_nr_is' => 'Source account number / IBAN is not ":trigger_value"',
'rule_trigger_not_source_account_nr_contains' => 'Source account number / IBAN does not contain ":trigger_value"',
'rule_trigger_not_source_account_nr_ends' => 'Source account number / IBAN does not end on ":trigger_value"',
'rule_trigger_not_source_account_nr_starts' => 'Source account number / IBAN does not start with ":trigger_value"',
'rule_trigger_not_destination_account_is' => 'Destination account is not ":trigger_value"',
'rule_trigger_not_destination_account_contains' => 'Destination account does not contain ":trigger_value"',
'rule_trigger_not_destination_account_ends' => 'Destination account does not end on ":trigger_value"',
'rule_trigger_not_destination_account_starts' => 'Destination account does not start with ":trigger_value"',
'rule_trigger_not_destination_account_nr_is' => 'Destination account number / IBAN is not ":trigger_value"',
'rule_trigger_not_destination_account_nr_contains' => 'Destination account number / IBAN does not contain ":trigger_value"',
'rule_trigger_not_destination_account_nr_ends' => 'Destination account number / IBAN does not end on ":trigger_value"',
'rule_trigger_not_destination_account_nr_starts' => 'Destination account number / IBAN does not start with ":trigger_value"',
'rule_trigger_not_account_is' => 'Neither account is ":trigger_value"',
'rule_trigger_not_account_contains' => 'Neither account contains ":trigger_value"',
'rule_trigger_not_account_ends' => 'Neither account ends on ":trigger_value"',
'rule_trigger_not_account_starts' => 'Neither account starts with ":trigger_value"',
'rule_trigger_not_account_nr_is' => 'Neither account number / IBAN is ":trigger_value"',
'rule_trigger_not_account_nr_contains' => 'Neither account number / IBAN contains ":trigger_value"',
'rule_trigger_not_account_nr_ends' => 'Neither account number / IBAN ends on ":trigger_value"',
'rule_trigger_not_account_nr_starts' => 'Neither account number / IBAN starts with ":trigger_value"',
'rule_trigger_not_category_is' => 'Neither category is ":trigger_value"',
'rule_trigger_not_category_contains' => 'Neither category contains ":trigger_value"',
'rule_trigger_not_category_ends' => 'Neither category ends on ":trigger_value"',
'rule_trigger_not_category_starts' => 'Neither category starts with ":trigger_value"',
'rule_trigger_not_budget_is' => 'Neither budget is ":trigger_value"',
'rule_trigger_not_budget_contains' => 'Neither budget contains ":trigger_value"',
'rule_trigger_not_budget_ends' => 'Neither budget ends on ":trigger_value"',
'rule_trigger_not_budget_starts' => 'Neither budget starts with ":trigger_value"',
'rule_trigger_not_bill_is' => 'Neither bill is ":trigger_value"',
'rule_trigger_not_bill_contains' => 'Neither bill contains ":trigger_value"',
'rule_trigger_not_bill_ends' => 'Neither bill ends on ":trigger_value"',
'rule_trigger_not_bill_starts' => 'Neither bill starts with ":trigger_value"',
'rule_trigger_not_external_id_is' => 'External ID is not ":trigger_value"',
'rule_trigger_not_external_id_contains' => 'External ID does not contain ":trigger_value"',
'rule_trigger_not_external_id_ends' => 'External ID does not end on ":trigger_value"',
'rule_trigger_not_external_id_starts' => 'External ID does not start with ":trigger_value"',
'rule_trigger_not_internal_reference_is' => 'Internal reference is not ":trigger_value"',
'rule_trigger_not_internal_reference_contains' => 'Internal reference does not contain ":trigger_value"',
'rule_trigger_not_internal_reference_ends' => 'Internal reference does not end on ":trigger_value"',
'rule_trigger_not_internal_reference_starts' => 'Internal reference does not start with ":trigger_value"',
'rule_trigger_not_external_url_is' => 'External URL is not ":trigger_value"',
'rule_trigger_not_external_url_contains' => 'External URL does not contain ":trigger_value"',
'rule_trigger_not_external_url_ends' => 'External URL does not end on ":trigger_value"',
'rule_trigger_not_external_url_starts' => 'External URL does not start with ":trigger_value"',
'rule_trigger_not_currency_is' => 'Currency is not ":trigger_value"',
'rule_trigger_not_foreign_currency_is' => 'Foreign currency is not ":trigger_value"',
'rule_trigger_not_id' => 'Transaction ID is not ":trigger_value"',
'rule_trigger_not_journal_id' => 'Transaction journal ID is not ":trigger_value"',
'rule_trigger_not_recurrence_id' => 'Recurrence ID is not ":trigger_value"',
'rule_trigger_not_date_on' => 'Date is not on ":trigger_value"',
'rule_trigger_not_date_before' => 'Date is not before ":trigger_value"',
'rule_trigger_not_date_after' => 'Date is not after ":trigger_value"',
'rule_trigger_not_interest_date_on' => 'Interest date is not on ":trigger_value"',
'rule_trigger_not_interest_date_before' => 'Interest date is not before ":trigger_value"',
'rule_trigger_not_interest_date_after' => 'Interest date is not after ":trigger_value"',
'rule_trigger_not_book_date_on' => 'Book date is not on ":trigger_value"',
'rule_trigger_not_book_date_before' => 'Book date is not before ":trigger_value"',
'rule_trigger_not_book_date_after' => 'Book date is not after ":trigger_value"',
'rule_trigger_not_process_date_on' => 'Process date is not on ":trigger_value"',
'rule_trigger_not_process_date_before' => 'Process date is not before ":trigger_value"',
'rule_trigger_not_process_date_after' => 'Process date is not after ":trigger_value"',
'rule_trigger_not_due_date_on' => 'Due date is not on ":trigger_value"',
'rule_trigger_not_due_date_before' => 'Due date is not before ":trigger_value"',
'rule_trigger_not_due_date_after' => 'Due date is not after ":trigger_value"',
'rule_trigger_not_payment_date_on' => 'Payment date is not on ":trigger_value"',
'rule_trigger_not_payment_date_before' => 'Payment date is not before ":trigger_value"',
'rule_trigger_not_payment_date_after' => 'Payment date is not after ":trigger_value"',
'rule_trigger_not_invoice_date_on' => 'Invoice date is not on ":trigger_value"',
'rule_trigger_not_invoice_date_before' => 'Invoice date is not before ":trigger_value"',
'rule_trigger_not_invoice_date_after' => 'Invoice date is not after ":trigger_value"',
'rule_trigger_not_created_at_on' => 'Transaction is not created on ":trigger_value"',
'rule_trigger_not_created_at_before' => 'Transaction is not created before ":trigger_value"',
'rule_trigger_not_created_at_after' => 'Transaction is not created after ":trigger_value"',
'rule_trigger_not_updated_at_on' => 'Transaction is not updated on ":trigger_value"',
'rule_trigger_not_updated_at_before' => 'Transaction is not updated before ":trigger_value"',
'rule_trigger_not_updated_at_after' => 'Transaction is not updated after ":trigger_value"',
'rule_trigger_not_amount_is' => 'Transaction amount is not ":trigger_value"',
'rule_trigger_not_amount_less' => 'Transaction amount is more than ":trigger_value"',
'rule_trigger_not_amount_more' => 'Transaction amount is less than ":trigger_value"',
'rule_trigger_not_foreign_amount_is' => 'Foreign transaction amount is not ":trigger_value"',
'rule_trigger_not_foreign_amount_less' => 'Foreign transaction amount is more than ":trigger_value"',
'rule_trigger_not_foreign_amount_more' => 'Foreign transaction amount is less than ":trigger_value"',
'rule_trigger_not_attachment_name_is' => 'No attachment is named ":trigger_value"',
'rule_trigger_not_attachment_name_contains' => 'No attachment name contains ":trigger_value"',
'rule_trigger_not_attachment_name_starts' => 'No attachment name starts with ":trigger_value"',
'rule_trigger_not_attachment_name_ends' => 'No attachment name ends on ":trigger_value"',
'rule_trigger_not_attachment_notes_are' => 'No attachment notes are ":trigger_value"',
'rule_trigger_not_attachment_notes_contains' => 'No attachment notes contain ":trigger_value"',
'rule_trigger_not_attachment_notes_starts' => 'No attachment notes start with ":trigger_value"',
'rule_trigger_not_attachment_notes_ends' => 'No attachment notes end on ":trigger_value"',
'rule_trigger_not_reconciled' => 'Transaction is not reconciled',
'rule_trigger_not_exists' => 'Transaction does not exist',
'rule_trigger_not_has_attachments' => 'Transaction has no attachments',
'rule_trigger_not_has_any_category' => 'Transaction has no category',
'rule_trigger_not_has_any_budget' => 'Transaction has no category',
'rule_trigger_not_has_any_bill' => 'Transaction has no bill',
'rule_trigger_not_has_any_tag' => 'Transaction has no tags',
'rule_trigger_not_any_notes' => 'Transaction has no notes',
'rule_trigger_not_any_external_url' => 'Transaction has no external URL',
'rule_trigger_not_has_no_attachments' => 'Transaction has a (any) attachment(s)',
'rule_trigger_not_has_no_category' => 'Transaction has a (any) category',
'rule_trigger_not_has_no_budget' => 'Transaction has a (any) budget',
'rule_trigger_not_has_no_bill' => 'Transaction has a (any) bill',
'rule_trigger_not_has_no_tag' => 'Transaction has a (any) tag',
'rule_trigger_not_no_notes' => 'Transaction has any notes',
'rule_trigger_not_no_external_url' => 'Transaction has an external URL',
'rule_trigger_not_source_is_cash' => 'Source account is a not a cash account',
'rule_trigger_not_destination_is_cash' => 'Destination account is a not a cash account',
'rule_trigger_not_account_is_cash' => 'Neither account is a cash account',
// actions
'rule_action_delete_transaction_choice' => 'DELETE transaction(!)',
'rule_action_delete_transaction' => 'DELETE transaction(!)',