diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index dfad898e8b..459f5ef792 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -55,9 +55,6 @@ class GroupCollector implements GroupCollectorInterface */ public function __construct() { - if ('testing' === config('app.env')) { - app('log')->warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); - } $this->hasAccountInfo = false; $this->hasCatInformation = false; $this->hasBudgetInformation = false; diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 42b231ba4b..74c42dcd5d 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -149,9 +149,6 @@ class Preferences */ public function getForUser(User $user, string $name, $default = null): ?Preference { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s("%s") should NOT be called in the TEST environment!', __METHOD__, $name)); - } $fullName = sprintf('preference%s%s', $user->id, $name); if (Cache::has($fullName)) { return Cache::get($fullName); diff --git a/app/Support/Search/BetterQuerySearch.php b/app/Support/Search/BetterQuerySearch.php index 64d59cea20..dbde544906 100644 --- a/app/Support/Search/BetterQuerySearch.php +++ b/app/Support/Search/BetterQuerySearch.php @@ -24,6 +24,7 @@ namespace FireflyIII\Support\Search; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; @@ -33,6 +34,7 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\User; use Gdbots\QueryParser\Node\Field; use Gdbots\QueryParser\Node\Node; +use Gdbots\QueryParser\Node\Phrase; use Gdbots\QueryParser\Node\Word; use Gdbots\QueryParser\ParsedQuery; use Gdbots\QueryParser\QueryParser; @@ -42,7 +44,6 @@ use Log; /** * Class BetterQuerySearch - * @package FireflyIII\Support\Search */ class BetterQuerySearch implements SearchInterface { @@ -60,12 +61,17 @@ class BetterQuerySearch implements SearchInterface private float $startTime; private Collection $modifiers; + /** + * BetterQuerySearch constructor. + * @codeCoverageIgnore + */ public function __construct() { + Log::debug('Constructed BetterQuerySearch'); $this->modifiers = new Collection; $this->page = 1; $this->words = []; - $this->validOperators = config('firefly.search_modifiers'); + $this->validOperators = array_keys(config('firefly.search.operators')); $this->startTime = microtime(true); $this->accountRepository = app(AccountRepositoryInterface::class); $this->categoryRepository = app(CategoryRepositoryInterface::class); @@ -76,6 +82,7 @@ class BetterQuerySearch implements SearchInterface /** * @inheritDoc + * @codeCoverageIgnore */ public function getModifiers(): Collection { @@ -84,6 +91,7 @@ class BetterQuerySearch implements SearchInterface /** * @inheritDoc + * @codeCoverageIgnore */ public function getWordsAsString(): string { @@ -92,6 +100,7 @@ class BetterQuerySearch implements SearchInterface /** * @inheritDoc + * @codeCoverageIgnore */ public function setPage(int $page): void { @@ -100,25 +109,31 @@ class BetterQuerySearch implements SearchInterface /** * @inheritDoc + * @codeCoverageIgnore */ public function hasModifiers(): bool { - // TODO: Implement hasModifiers() method. die(__METHOD__); } /** * @inheritDoc + * @throws FireflyException */ public function parseQuery(string $query) { + Log::debug(sprintf('Now in parseQuery(%s)', $query)); $parser = new QueryParser(); $this->query = $parser->parse($query); // get limit from preferences. - $pageSize = (int) app('preferences')->getForUser($this->user, 'listPageSize', 50)->data; + $pageSize = (int) app('preferences')->getForUser($this->user, 'listPageSize', 50)->data; $this->collector = app(GroupCollectorInterface::class); - $this->collector->setLimit($pageSize)->setPage($this->page)->withAccountInformation()->withCategoryInformation()->withBudgetInformation(); + $this->collector->setUser($this->user); + $this->collector->setLimit($pageSize)->setPage($this->page); + $this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation(); + + Log::debug(sprintf('Found %d node(s)', count($this->query->getNodes()))); foreach ($this->query->getNodes() as $searchNode) { $this->handleSearchNode($searchNode); @@ -130,6 +145,7 @@ class BetterQuerySearch implements SearchInterface /** * @inheritDoc + * @codeCoverageIgnore */ public function searchTime(): float { @@ -146,6 +162,7 @@ class BetterQuerySearch implements SearchInterface /** * @inheritDoc + * @codeCoverageIgnore */ public function setUser(User $user): void { @@ -165,11 +182,18 @@ class BetterQuerySearch implements SearchInterface $class = get_class($searchNode); switch ($class) { default: + Log::error(sprintf('Cannot handle node %s', $class)); throw new FireflyException(sprintf('Firefly III search cant handle "%s"-nodes', $class)); case Word::class: + Log::debug(sprintf('Now handle %s', $class)); + $this->words[] = $searchNode->getValue(); + break; + case Phrase::class: + Log::debug(sprintf('Now handle %s', $class)); $this->words[] = $searchNode->getValue(); break; case Field::class: + Log::debug(sprintf('Now handle %s', $class)); /** @var Field $searchNode */ // used to search for x:y $operator = $searchNode->getValue(); @@ -178,9 +202,9 @@ class BetterQuerySearch implements SearchInterface if (in_array($operator, $this->validOperators, true)) { $this->updateCollector($operator, $value); $this->modifiers->push([ - 'type' => $operator, - 'value' => $value, - ]); + 'type' => $operator, + 'value' => $value, + ]); } break; } @@ -190,13 +214,27 @@ class BetterQuerySearch implements SearchInterface /** * @param string $operator * @param string $value + * @throws FireflyException */ private function updateCollector(string $operator, string $value): void { + Log::debug(sprintf('updateCollector(%s, %s)', $operator, $value)); $allAccounts = new Collection; switch ($operator) { default: - die(sprintf('Unsupported search operator: "%s"', $operator)); + Log::error(sprintf('No such operator: %s', $operator)); + throw new FireflyException(sprintf('Unsupported search operator: "%s"', $operator)); + // some search operators are ignored, basically: + case 'user_action': + Log::info(sprintf('Ignore search operator "%s"', $operator)); + break; + case 'from_account_starts': + $this->fromAccountStarts($value); + break; + case 'from_account_ends': + $this->fromAccountEnds($value); + break; + case 'from_account_contains': case 'from': case 'source': // source can only be asset, liability or revenue account: @@ -299,4 +337,54 @@ class BetterQuerySearch implements SearchInterface break; } } + + /** + * @param string $value + */ + private function fromAccountStarts(string $value): void + { + Log::debug(sprintf('fromAccountStarts(%s)', $value)); + // source can only be asset, liability or revenue account: + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; + $accounts = $this->accountRepository->searchAccount($value, $searchTypes, 25); + if (0 === $accounts->count()) { + Log::debug('Found zero, return.'); + return; + } + Log::debug(sprintf('Found %d, filter.', $accounts->count())); + $filtered = $accounts->filter(function (Account $account) use ($value) { + return str_starts_with($account->name, $value); + }); + if (0 === $filtered->count()) { + Log::debug('Left with zero, return.'); + return; + } + Log::debug(sprintf('Left with %d, set.', $accounts->count())); + $this->collector->setSourceAccounts($filtered); + } + + /** + * @param string $value + */ + private function fromAccountEnds(string $value): void + { + Log::debug(sprintf('fromAccountEnds(%s)', $value)); + // source can only be asset, liability or revenue account: + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; + $accounts = $this->accountRepository->searchAccount($value, $searchTypes, 25); + if (0 === $accounts->count()) { + Log::debug('Found zero, return.'); + return; + } + Log::debug(sprintf('Found %d, filter.', $accounts->count())); + $filtered = $accounts->filter(function (Account $account) use ($value) { + return str_ends_with($account->name, $value); + }); + if (0 === $filtered->count()) { + Log::debug('Left with zero, return.'); + return; + } + Log::debug(sprintf('Left with %d, set.', $accounts->count())); + $this->collector->setSourceAccounts($filtered); + } } \ No newline at end of file diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php deleted file mode 100644 index 3a97896476..0000000000 --- a/app/Support/Search/Search.php +++ /dev/null @@ -1,318 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Search; - -use Carbon\Carbon; -use FireflyIII\Helpers\Collector\GroupCollectorInterface; -use FireflyIII\Models\AccountType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Bill\BillRepositoryInterface; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; -use FireflyIII\User; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; -use Log; - -/** - * Class Search. - */ -class Search implements SearchInterface -{ - private AccountRepositoryInterface $accountRepository; - private BillRepositoryInterface $billRepository; - private BudgetRepositoryInterface $budgetRepository; - private CategoryRepositoryInterface $categoryRepository; - private Collection $modifiers; - private string $originalQuery = ''; - private float $startTime; - private int $limit; - private TagRepositoryInterface $tagRepository; - private User $user; - private array $validModifiers; - private array $words = []; - private int $page; - - /** - * Search constructor. - */ - public function __construct() - { - $this->page = 1; - $this->modifiers = new Collection; - $this->validModifiers = (array) config('firefly.search_modifiers'); - $this->startTime = microtime(true); - $this->accountRepository = app(AccountRepositoryInterface::class); - $this->categoryRepository = app(CategoryRepositoryInterface::class); - $this->budgetRepository = app(BudgetRepositoryInterface::class); - $this->billRepository = app(BillRepositoryInterface::class); - $this->tagRepository = app(TagRepositoryInterface::class); - - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); - } - } - - /** - * @return Collection - */ - public function getModifiers(): Collection - { - return $this->modifiers; - } - - /** - * @return string - */ - public function getWordsAsString(): string - { - $string = implode(' ', $this->words); - if ('' === $string) { - return is_string($this->originalQuery) ? $this->originalQuery : ''; - } - - return $string; - } - - /** - * @return bool - */ - public function hasModifiers(): bool - { - return $this->modifiers->count() > 0; - } - - /** - * @param string $query - */ - public function parseQuery(string $query): void - { - $filteredQuery = app('steam')->cleanString($query); - $this->originalQuery = $filteredQuery; - $pattern = '/[[:alpha:]_]*:(".*"|[\P{Zs}_-]*)/ui'; - $matches = []; - preg_match_all($pattern, $filteredQuery, $matches); - - foreach ($matches[0] as $match) { - $this->extractModifier($match); - $filteredQuery = str_replace($match, '', $filteredQuery); - } - $filteredQuery = trim(str_replace(['"', "'"], '', $filteredQuery)); - - // str replace some stuff: - $search = ['%', '=', '/', '<', '>', '(', ')', ';']; - $filteredQuery = str_replace($search, ' ', $filteredQuery); - - if ('' !== $filteredQuery) { - $this->words = array_map('trim', explode(' ', $filteredQuery)); - } - } - - /** - * @return float - */ - public function searchTime(): float - { - return microtime(true) - $this->startTime; - } - - /** - * @return LengthAwarePaginator - */ - public function searchTransactions(): LengthAwarePaginator - { - Log::debug('Start of searchTransactions()'); - - // get limit from preferences. - $pageSize = (int) app('preferences')->get('listPageSize', 50)->data; - - /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); - - $collector->setLimit($pageSize)->setPage($this->page)->withAccountInformation(); - $collector->withCategoryInformation()->withBudgetInformation(); - $collector->setSearchWords($this->words); - - // Most modifiers can be applied to the collector directly. - $collector = $this->applyModifiers($collector); - return $collector->getPaginatedGroups(); - } - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - $this->accountRepository->setUser($user); - $this->billRepository->setUser($user); - $this->categoryRepository->setUser($user); - $this->budgetRepository->setUser($user); - } - - /** - * @param GroupCollectorInterface $collector - * - * @return GroupCollectorInterface - * - */ - private function applyModifiers(GroupCollectorInterface $collector): GroupCollectorInterface - { - $totalAccounts = new Collection; - - foreach ($this->modifiers as $modifier) { - switch ($modifier['type']) { - default: - die(sprintf('unsupported modifier: "%s"', $modifier['type'])); - case 'from': - case 'source': - // source can only be asset, liability or revenue account: - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; - $accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes, 25); - if ($accounts->count() > 0) { - $totalAccounts = $accounts->merge($totalAccounts); - } - $collector->setSourceAccounts($totalAccounts); - break; - case 'to': - case 'destination': - // source can only be asset, liability or expense account: - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; - $accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes, 25); - if ($accounts->count() > 0) { - $totalAccounts = $accounts->merge($totalAccounts); - } - $collector->setDestinationAccounts($totalAccounts); - break; - case 'category': - $result = $this->categoryRepository->searchCategory($modifier['value'], 25); - if ($result->count() > 0) { - $collector->setCategories($result); - } - break; - case 'bill': - $result = $this->billRepository->searchBill($modifier['value'], 25); - if ($result->count() > 0) { - $collector->setBills($result); - } - break; - case 'tag': - $result = $this->tagRepository->searchTag($modifier['value']); - if ($result->count() > 0) { - $collector->setTags($result); - } - break; - case 'budget': - $result = $this->budgetRepository->searchBudget($modifier['value'], 25); - if ($result->count() > 0) { - $collector->setBudgets($result); - } - break; - case 'amount_is': - case 'amount': - $amount = app('steam')->positive((string) $modifier['value']); - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $amount)); - $collector->amountIs($amount); - break; - case 'amount_max': - case 'amount_less': - $amount = app('steam')->positive((string) $modifier['value']); - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $amount)); - $collector->amountLess($amount); - break; - case 'amount_min': - case 'amount_more': - $amount = app('steam')->positive((string) $modifier['value']); - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $amount)); - $collector->amountMore($amount); - break; - case 'type': - $collector->setTypes([ucfirst($modifier['value'])]); - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value'])); - break; - case 'date': - case 'on': - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value'])); - $start = new Carbon($modifier['value']); - $collector->setRange($start, $start); - break; - case 'date_before': - case 'before': - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value'])); - $before = new Carbon($modifier['value']); - $collector->setBefore($before); - break; - case 'date_after': - case 'after': - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value'])); - $after = new Carbon($modifier['value']); - $collector->setAfter($after); - break; - case 'created_on': - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value'])); - $createdAt = new Carbon($modifier['value']); - $collector->setCreatedAt($createdAt); - break; - case 'updated_on': - Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value'])); - $updatedAt = new Carbon($modifier['value']); - $collector->setUpdatedAt($updatedAt); - break; - case 'external_id': - $collector->setExternalId($modifier['value']); - break; - case 'internal_reference': - $collector->setInternalReference($modifier['value']); - break; - } - } - - return $collector; - } - - /** - * @param string $string - */ - private function extractModifier(string $string): void - { - $parts = explode(':', $string); - if (2 === count($parts) && '' !== trim((string) $parts[1]) && '' !== trim((string) $parts[0])) { - $type = strtolower(trim((string) $parts[0])); - $value = trim((string) $parts[1]); - $value = trim(trim($value, '"\'')); - if (in_array($type, $this->validModifiers, true)) { - // filter for valid type - $this->modifiers->push(['type' => $type, 'value' => $value]); - } - } - } - - /** - * @param int $page - */ - public function setPage(int $page): void - { - $this->page = $page; - } -} diff --git a/app/Support/Search/TransactionSearch.php b/app/Support/Search/TransactionSearch.php deleted file mode 100644 index a1899d1927..0000000000 --- a/app/Support/Search/TransactionSearch.php +++ /dev/null @@ -1,42 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Support\Search; - -use Illuminate\Support\Collection; - -/** - * Class TransactionSearch - */ -class TransactionSearch implements GenericSearchInterface -{ - - /** - * @inheritDoc - */ - public function search(): Collection - { - // TODO: Implement search() method. - } -} diff --git a/app/Support/Search/TransferSearch.php b/app/Support/Search/TransferSearch.php deleted file mode 100644 index 5560bb096a..0000000000 --- a/app/Support/Search/TransferSearch.php +++ /dev/null @@ -1,150 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Support\Search; - - -use Carbon\Carbon; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\User; -use Illuminate\Database\Query\JoinClause; -use Illuminate\Support\Collection; -use InvalidArgumentException; -use Log; - -/** - * Class TransferSearch - */ -class TransferSearch implements GenericSearchInterface -{ - /** @var AccountRepositoryInterface */ - private $accountRepository; - /** @var string */ - private $amount; - /** @var Carbon */ - private $date; - /** @var string */ - private $description; - /** @var Account */ - private $destination; - /** @var Account */ - private $source; - /** @var array */ - private $types; - - public function __construct() - { - $this->accountRepository = app(AccountRepositoryInterface::class); - $this->types = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]; - } - - /** - * @return Collection - */ - public function search(): Collection - { - /** @var User $user */ - $user = auth()->user(); - - $query = $user->transactionJournals() - ->leftJoin( - 'transactions as source', static function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'source.transaction_journal_id'); - $join->where('source.amount', '<', '0'); - } - ) - ->leftJoin( - 'transactions as destination', static function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'destination.transaction_journal_id'); - $join->where('destination.amount', '>', '0'); - } - ) - ->where('source.account_id', $this->source->id) - ->where('destination.account_id', $this->destination->id) - ->where('transaction_journals.description', $this->description) - ->where('destination.amount', $this->amount) - ->where('transaction_journals.date', $this->date->format('Y-m-d 00:00:00')) - ; - - return $query->get(['transaction_journals.id', 'transaction_journals.transaction_group_id']); - } - - /** - * @param string $amount - */ - public function setAmount(string $amount): void - { - $this->amount = $amount; - } - - /** - * @param string $date - */ - public function setDate(string $date): void - { - try { - $carbon = Carbon::createFromFormat('Y-m-d', $date); - } catch (InvalidArgumentException $e) { - Log::error($e->getMessage()); - $carbon = Carbon::now(); - } - $this->date = $carbon; - } - - /** - * @param string $description - */ - public function setDescription(string $description): void - { - $this->description = $description; - } - - /** - * @param string $destination - */ - public function setDestination(string $destination): void - { - if (is_numeric($destination)) { - $this->destination = $this->accountRepository->findNull((int)$destination); - } - if (null === $this->destination) { - $this->destination = $this->accountRepository->findByName($destination, $this->types); - } - } - - /** - * @param string $source - */ - public function setSource(string $source): void - { - if (is_numeric($source)) { - $this->source = $this->accountRepository->findNull((int)$source); - } - if (null === $this->source) { - $this->source = $this->accountRepository->findByName($source, $this->types); - } - } -} diff --git a/config/firefly.php b/config/firefly.php index ce04f26158..0e2078ca95 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -492,6 +492,11 @@ return [ 'notes_are' => NotesAre::class, 'no_notes' => NotesEmpty::class, 'any_notes' => NotesAny::class, + 'bill_is' => BillIs::class, // TODO + 'created_on' => CreatedOn::class, // TODO + 'updated_on' => UpdatedOn::class,// TODO + 'external_id' => ExternalId::class,// TODO + 'internal_reference' => InternalReference::class, // TODO ], 'rule-actions' => [ 'set_category' => SetCategory::class, @@ -572,9 +577,106 @@ return [ 'default_currency' => 'EUR', 'default_language' => envNonEmpty('DEFAULT_LANGUAGE', 'en_US'), 'default_locale' => envNonEmpty('DEFAULT_LOCALE', 'equal'), - 'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category', - 'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after', 'from', 'to', 'tag', 'created_on', - 'updated_on', 'external_id', 'internal_reference',], + + 'search' => [ + 'operators' => [ + 'user_action' => ['alias' => false, 'trigger_class' => UserAction::class], + 'from_account_starts' => ['alias' => false, 'trigger_class' => FromAccountStarts::class], + 'from_account_ends' => ['alias' => false, 'trigger_class' => FromAccountEnds::class], + 'from_account_contains' => ['alias' => false, 'trigger_class' => FromAccountContains::class], + 'from_account_nr_starts' => ['alias' => false, 'trigger_class' => FromAccountNumberStarts::class], + 'from_account_nr_ends' => ['alias' => false, 'trigger_class' => FromAccountNumberEnds::class], + 'from_account_nr_is' => ['alias' => false, 'trigger_class' => FromAccountNumberIs::class], + 'from_account_nr_contains' => ['alias' => false, 'trigger_class' => FromAccountNumberContains::class], + 'to_account_starts' => ['alias' => false, 'trigger_class' => ToAccountStarts::class], + 'to_account_ends' => ['alias' => false, 'trigger_class' => ToAccountEnds::class], + 'to_account_contains' => ['alias' => false, 'trigger_class' => ToAccountContains::class], + 'to_account_nr_starts' => ['alias' => false, 'trigger_class' => ToAccountNumberStarts::class], + 'to_account_nr_ends' => ['alias' => false, 'trigger_class' => ToAccountNumberEnds::class], + 'to_account_nr_is' => ['alias' => false, 'trigger_class' => ToAccountNumberIs::class], + 'to_account_nr_contains' => ['alias' => false, 'trigger_class' => ToAccountNumberContains::class], + 'description_starts' => ['alias' => false, 'trigger_class' => DescriptionStarts::class], + 'description_ends' => ['alias' => false, 'trigger_class' => DescriptionEnds::class], + 'description_contains' => ['alias' => false, 'trigger_class' => DescriptionContains::class], + 'description_is' => ['alias' => false, 'trigger_class' => DescriptionIs::class], + 'currency_is' => ['alias' => false, 'trigger_class' => CurrencyIs::class], + 'foreign_currency_is' => ['alias' => false, 'trigger_class' => ForeignCurrencyIs::class], + 'has_attachments' => ['alias' => false, 'trigger_class' => HasAttachment::class], + 'has_no_category' => ['alias' => false, 'trigger_class' => HasNoCategory::class], + 'has_any_category' => ['alias' => false, 'trigger_class' => HasAnyCategory::class], + 'has_no_budget' => ['alias' => false, 'trigger_class' => HasNoBudget::class], + 'has_any_budget' => ['alias' => false, 'trigger_class' => HasAnyBudget::class], + 'has_no_tag' => ['alias' => false, 'trigger_class' => HasNoTag::class], + 'has_any_tag' => ['alias' => false, 'trigger_class' => HasAnyTag::class], + 'notes_contain' => ['alias' => false, 'trigger_class' => NotesContain::class], + 'notes_start' => ['alias' => false, 'trigger_class' => NotesStart::class], + 'notes_end' => ['alias' => false, 'trigger_class' => NotesEnd::class], + 'notes_are' => ['alias' => false, 'trigger_class' => NotesAre::class], + 'no_notes' => ['alias' => false, 'trigger_class' => NotesEmpty::class], + 'any_notes' => ['alias' => false, 'trigger_class' => NotesAny::class], + + // exact amount + 'amount_exactly' => ['alias' => false, 'trigger_class' => AmountExactly::class], + 'amount_is' => ['alias' => true, 'alias_for' => 'amount_exactly'], + 'amount' => ['alias' => true, 'alias_for' => 'amount_exactly'], + + // is less than + 'amount_less' => ['alias' => false, 'trigger_class' => AmountLess::class], + 'amount_max' => ['alias' => true, 'alias_for' => 'amount_less'], + + // is more than + 'amount_more' => ['alias' => false, 'trigger_class' => AmountMore::class], + 'amount_min' => ['alias' => true, 'alias_for' => 'amount_more'], + + // source account + 'from_account_is' => ['alias' => false, 'trigger_class' => FromAccountIs::class], + 'source' => ['alias' => true, 'alias_for' => 'from_account_is'], + 'from' => ['alias' => true, 'alias_for' => 'from_account_is'], + + // destination account + 'to_account_is' => ['alias' => false, 'trigger_class' => ToAccountIs::class], + 'destination' => ['alias' => true, 'alias_for' => 'to_account_is'], + 'to' => ['alias' => true, 'alias_for' => 'to_account_is'], + + // category + 'category_is' => ['alias' => false, 'trigger_class' => CategoryIs::class], + 'category' => ['alias' => true, 'alias_for' => 'category_is'], + + // budget + 'budget_is' => ['alias' => false, 'trigger_class' => BudgetIs::class], + 'budget' => ['alias' => true, 'alias_for' => 'budget_is'], + + // bill + 'bill_is' => ['alias' => false, 'trigger_class' => BillIs::class], // TODO + 'bill' => ['alias' => true, 'alias_for' => 'bill_is'], + + // type + 'transaction_type' => ['alias' => false, 'trigger_class' => TransactionType::class], + 'type' => ['alias' => true, 'alias_for' => 'transaction_type'], + + // date: + 'date_is' => ['alias' => false, 'trigger_class' => DateIs::class], + 'date' => ['alias' => true, 'alias_for' => 'date_is'], + 'on' => ['alias' => true, 'alias_for' => 'date_is'], + 'date_before' => ['alias' => false, 'trigger_class' => DateBefore::class], + 'before' => ['alias' => true, 'alias_for' => 'date_before'], + 'date_after' => ['alias' => false, 'trigger_class' => DateAfter::class], + 'after' => ['alias' => true, 'alias_for' => 'date_after'], + // other interesting fields + 'tag_is' => ['alias' => false, 'trigger_class' => TagIs::class], + 'tag' => ['alias' => true, 'alias_for' => 'tag'], + 'created_on' => ['alias' => false, 'trigger_class' => CreatedOn::class], // TODO + 'updated_on' => ['alias' => false, 'trigger_class' => UpdatedOn::class], // TODO + 'external_id' => ['alias' => false, 'trigger_class' => ExternalId::class], // TODO + 'internal_reference' => ['alias' => false, 'trigger_class' => InternalReference::class], // TODO + + ], + ], + + 'search_modifiers' => [ + 'amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category', + 'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after', 'from', 'to', 'tag', 'created_on', + 'updated_on', 'external_id', 'internal_reference',], // TODO notes has_attachments