First "date is" trigger for #3049

This commit is contained in:
James Cole 2020-05-16 12:11:06 +02:00
parent 86f14885eb
commit c847621874
5 changed files with 289 additions and 1 deletions

View File

@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use Log;
/**
* Class ParseDateString
*/
class ParseDateString
{
private $keywords
= [
'today',
'yesterday',
'tomorrow',
'start of this week',
'end of this week',
'start of this month',
'end of this month',
'start of this quarter',
'end of this quarter',
'start of this year',
'end of this year',
];
/**
* @param string $date
*
* @return Carbon
*/
public function parseDate(string $date): Carbon
{
// parse keywords:
if (in_array($date, $this->keywords, true)) {
return $this->parseKeyword($date);
}
// if regex for YYYY-MM-DD:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][\d]|3[01])$/';
if (preg_match($pattern, $date)) {
return $this->parseDefaultDate($date);
}
// if + or -:
if (0 === strpos($date, '+') || 0 === strpos($date, '-')) {
return $this->parseRelativeDate($date);
}
throw new FireflyException('Not recognised.');
}
/**
* @param string $date
*
* @return Carbon
*/
private function parseDefaultDate(string $date): Carbon
{
return Carbon::createFromFormat('Y-m-d', $date);
}
/**
* @param string $keyword
*
* @return Carbon
*/
private function parseKeyword(string $keyword): Carbon
{
$today = Carbon::today()->startOfDay();
switch ($keyword) {
default:
case 'today':
return $today;
case 'yesterday':
return $today->subDay();
case 'tomorrow':
return $today->addDay();
case 'start of this week':
return $today->startOfWeek();
case 'end of this week':
return $today->endOfWeek();
case 'start of this month':
return $today->startOfMonth();
case 'end of this month':
return $today->endOfMonth();
case 'start of this quarter':
return $today->startOfQuarter();
case 'end of this quarter':
return $today->endOfQuarter();
case 'start of this year':
return $today->startOfYear();
case 'end of this year':
return $today->endOfYear();
}
}
/**
* @param string $date
*
* @return Carbon
*/
private function parseRelativeDate(string $date): Carbon
{
Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
$parts = explode(' ', $date);
$today = Carbon::today()->startOfDay();
$functions = [
[
'd' => 'subDays',
'w' => 'subWeeks',
'm' => 'subMonths',
'q' => 'subQuarters',
'y' => 'subYears',
], [
'd' => 'addDays',
'w' => 'addWeeks',
'm' => 'addMonths',
'q' => 'addQuarters',
'y' => 'addYears',
],
];
/** @var string $part */
foreach ($parts as $part) {
Log::debug(sprintf('Now parsing part "%s"', $part));
$part = trim($part);
// verify if correct
$pattern = '/[+-]\d+[wqmdy]/';
$res = preg_match($pattern, $part);
if (0 === $res || false === $res) {
Log::error(sprintf('Part "%s" does not match regular expression. Will be skipped.', $part));
continue;
}
$direction = 0 === strpos($part, '+') ? 1 : 0;
$period = $part[strlen($part) - 1];
$number = (int) substr($part, 1, -1);
if (!isset($functions[$direction][$period])) {
Log::error(sprintf('No method for direction %d and period "%s".', $direction, $period));
continue;
}
$func = $functions[$direction][$period];
Log::debug(sprintf('Will now do %s(%d) on %s', $func, $number, $today->format('Y-m-d')));
$today->$func($number);
Log::debug(sprintf('Resulting date is %s', $today->format('Y-m-d')));
}
return $today;
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* DateIs.php
* Copyright (c) 2019 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\TransactionRules\Triggers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\ParseDateString;
use Log;
/**
* Class DateIs.
*/
final class DateIs extends AbstractTrigger implements TriggerInterface
{
/**
* A trigger is said to "match anything", or match any given transaction,
* when the trigger value is very vague or has no restrictions. Easy examples
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
* has an amount of more than zero! Other examples are all the "Description"-triggers
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
*
* If the user tries to create such a trigger, this method MUST return true so Firefly III
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
* (even if it will still include 99.9% of the users transactions), this method MUST return
* false.
*
* @param mixed $value
*
* @return bool
*/
public static function willMatchEverything($value = null): bool
{
if (null !== $value) {
return false;
}
Log::error(sprintf('Cannot use %s with a null value.', self::class));
return true;
}
/**
* Returns true when category is X.
*
* @param TransactionJournal $journal
*
* @return bool
*/
public function triggered(TransactionJournal $journal): bool
{
/** @var Carbon $date */
$date = $journal->date;
Log::debug(sprintf('Found date on journal: %s', $date->format('Y-m-d')));
$dateParser = new ParseDateString();
try {
$ruleDate = $dateParser->parseDate($this->triggerValue);
} catch (FireflyException $e) {
Log::error('Cannot execute rule trigger.');
Log::error($e->getMessage());
return false;
}
if ($ruleDate->isSameDay($date)) {
Log::debug(
sprintf(
'%s is on the same day as %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return true;
}
Log::debug(
sprintf(
'%s is NOT on the same day as %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return false;
}
}

View File

@ -24,6 +24,7 @@ namespace FireflyIII\Validation;
use Config; use Config;
use DB; use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
@ -34,11 +35,13 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Services\Password\Verifier; use FireflyIII\Services\Password\Verifier;
use FireflyIII\Support\ParseDateString;
use FireflyIII\TransactionRules\Triggers\TriggerInterface; use FireflyIII\TransactionRules\Triggers\TriggerInterface;
use FireflyIII\User; use FireflyIII\User;
use Google2FA; use Google2FA;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Validation\Validator; use Illuminate\Validation\Validator;
use Log;
/** /**
* Class FireflyValidator. * Class FireflyValidator.
@ -333,6 +336,20 @@ class FireflyValidator extends Validator
return 1 === $count; return 1 === $count;
} }
// if the type is date, the simply try to parse it and throw error when it's bad.
if (in_array($triggerType, ['date_is'], true)) {
/** @var ParseDateString $parser */
$parser = app(ParseDateString::class);
try {
$parser->parseDate($value);
} catch (FireflyException $e) {
Log::error($e->getMessage());
return false;
}
}
// and finally a "will match everything check": // and finally a "will match everything check":
$classes = app('config')->get('firefly.rule-triggers'); $classes = app('config')->get('firefly.rule-triggers');
/** @var TriggerInterface $class */ /** @var TriggerInterface $class */

View File

@ -73,18 +73,19 @@ use FireflyIII\TransactionRules\Actions\PrependNotes;
use FireflyIII\TransactionRules\Actions\RemoveAllTags; use FireflyIII\TransactionRules\Actions\RemoveAllTags;
use FireflyIII\TransactionRules\Actions\RemoveTag; use FireflyIII\TransactionRules\Actions\RemoveTag;
use FireflyIII\TransactionRules\Actions\SetBudget; use FireflyIII\TransactionRules\Actions\SetBudget;
use FireflyIII\TransactionRules\Actions\UpdatePiggybank;
use FireflyIII\TransactionRules\Actions\SetCategory; use FireflyIII\TransactionRules\Actions\SetCategory;
use FireflyIII\TransactionRules\Actions\SetDescription; use FireflyIII\TransactionRules\Actions\SetDescription;
use FireflyIII\TransactionRules\Actions\SetDestinationAccount; use FireflyIII\TransactionRules\Actions\SetDestinationAccount;
use FireflyIII\TransactionRules\Actions\SetNotes; use FireflyIII\TransactionRules\Actions\SetNotes;
use FireflyIII\TransactionRules\Actions\SetSourceAccount; use FireflyIII\TransactionRules\Actions\SetSourceAccount;
use FireflyIII\TransactionRules\Actions\UpdatePiggybank;
use FireflyIII\TransactionRules\Triggers\AmountExactly; use FireflyIII\TransactionRules\Triggers\AmountExactly;
use FireflyIII\TransactionRules\Triggers\AmountLess; use FireflyIII\TransactionRules\Triggers\AmountLess;
use FireflyIII\TransactionRules\Triggers\AmountMore; use FireflyIII\TransactionRules\Triggers\AmountMore;
use FireflyIII\TransactionRules\Triggers\BudgetIs; use FireflyIII\TransactionRules\Triggers\BudgetIs;
use FireflyIII\TransactionRules\Triggers\CategoryIs; use FireflyIII\TransactionRules\Triggers\CategoryIs;
use FireflyIII\TransactionRules\Triggers\CurrencyIs; use FireflyIII\TransactionRules\Triggers\CurrencyIs;
use FireflyIII\TransactionRules\Triggers\DateIs;
use FireflyIII\TransactionRules\Triggers\DescriptionContains; use FireflyIII\TransactionRules\Triggers\DescriptionContains;
use FireflyIII\TransactionRules\Triggers\DescriptionEnds; use FireflyIII\TransactionRules\Triggers\DescriptionEnds;
use FireflyIII\TransactionRules\Triggers\DescriptionIs; use FireflyIII\TransactionRules\Triggers\DescriptionIs;
@ -468,6 +469,7 @@ return [
'description_ends' => DescriptionEnds::class, 'description_ends' => DescriptionEnds::class,
'description_contains' => DescriptionContains::class, 'description_contains' => DescriptionContains::class,
'description_is' => DescriptionIs::class, 'description_is' => DescriptionIs::class,
'date_is' => DateIs::class,
'transaction_type' => TransactionType::class, 'transaction_type' => TransactionType::class,
'category_is' => CategoryIs::class, 'category_is' => CategoryIs::class,
'budget_is' => BudgetIs::class, 'budget_is' => BudgetIs::class,
@ -554,6 +556,7 @@ return [
'notes_start', 'notes_start',
'notes_end', 'notes_end',
'notes_are', 'notes_are',
'date_is',
], ],
'test-triggers' => [ 'test-triggers' => [

View File

@ -421,6 +421,10 @@ return [
'rule_trigger_description_contains' => 'Description contains ":trigger_value"', 'rule_trigger_description_contains' => 'Description contains ":trigger_value"',
'rule_trigger_description_is_choice' => 'Description is..', 'rule_trigger_description_is_choice' => 'Description is..',
'rule_trigger_description_is' => 'Description is ":trigger_value"', 'rule_trigger_description_is' => 'Description is ":trigger_value"',
'rule_trigger_date_is_choice' => 'Transaction date is..',
'rule_trigger_date_is' => 'Transaction date is ":trigger_value"',
'rule_trigger_budget_is_choice' => 'Budget is..', 'rule_trigger_budget_is_choice' => 'Budget is..',
'rule_trigger_budget_is' => 'Budget is ":trigger_value"', 'rule_trigger_budget_is' => 'Budget is ":trigger_value"',
'rule_trigger_tag_is_choice' => '(A) tag is..', 'rule_trigger_tag_is_choice' => '(A) tag is..',