mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
First "date is" trigger for #3049
This commit is contained in:
parent
86f14885eb
commit
c847621874
157
app/Support/ParseDateString.php
Normal file
157
app/Support/ParseDateString.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
107
app/TransactionRules/Triggers/DateIs.php
Normal file
107
app/TransactionRules/Triggers/DateIs.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 */
|
||||||
|
@ -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' => [
|
||||||
|
@ -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..',
|
||||||
|
Loading…
Reference in New Issue
Block a user