Refresh notes in various actions.

This commit is contained in:
James Cole
2024-03-10 08:11:58 +01:00
parent 0b45c1aa76
commit 3413b9b5b5
14 changed files with 54 additions and 53 deletions

View File

@@ -105,8 +105,8 @@ class StoreRequest extends FormRequest
*/ */
public function rules(): array public function rules(): array
{ {
$validTriggers = $this->getTriggers(); $validTriggers = $this->getTriggers();
$validActions = array_keys(config('firefly.rule-actions')); $validActions = array_keys(config('firefly.rule-actions'));
// some triggers and actions require text: // some triggers and actions require text:
$contextTriggers = implode(',', $this->getTriggersWithContext()); $contextTriggers = implode(',', $this->getTriggersWithContext());
@@ -118,11 +118,11 @@ class StoreRequest extends FormRequest
'rule_group_id' => 'belongsToUser:rule_groups|required_without:rule_group_title', 'rule_group_id' => 'belongsToUser:rule_groups|required_without:rule_group_title',
'rule_group_title' => 'nullable|min:1|max:255|required_without:rule_group_id|belongsToUser:rule_groups,title', 'rule_group_title' => 'nullable|min:1|max:255|required_without:rule_group_id|belongsToUser:rule_groups,title',
'trigger' => 'required|in:store-journal,update-journal', 'trigger' => 'required|in:store-journal,update-journal',
'triggers.*.type' => 'required|in:' . implode(',', $validTriggers), 'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
'triggers.*.value' => 'required_if:actions.*.type,' . $contextTriggers . '|min:1|ruleTriggerValue|max:1024', 'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024',
'triggers.*.stop_processing' => [new IsBoolean()], 'triggers.*.stop_processing' => [new IsBoolean()],
'triggers.*.active' => [new IsBoolean()], 'triggers.*.active' => [new IsBoolean()],
'actions.*.type' => 'required|in:' . implode(',', $validActions), 'actions.*.type' => 'required|in:'.implode(',', $validActions),
'actions.*.value' => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'], 'actions.*.value' => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
'actions.*.stop_processing' => [new IsBoolean()], 'actions.*.stop_processing' => [new IsBoolean()],
'actions.*.active' => [new IsBoolean()], 'actions.*.active' => [new IsBoolean()],
@@ -181,10 +181,10 @@ class StoreRequest extends FormRequest
*/ */
protected function atLeastOneActiveTrigger(Validator $validator): void protected function atLeastOneActiveTrigger(Validator $validator): void
{ {
$data = $validator->getData(); $data = $validator->getData();
/** @var null|array|int|string $triggers */ /** @var null|array|int|string $triggers */
$triggers = $data['triggers'] ?? []; $triggers = $data['triggers'] ?? [];
// need at least one trigger // need at least one trigger
if (!is_countable($triggers) || 0 === count($triggers)) { if (!is_countable($triggers) || 0 === count($triggers)) {
return; return;
@@ -210,10 +210,10 @@ class StoreRequest extends FormRequest
*/ */
protected function atLeastOneActiveAction(Validator $validator): void protected function atLeastOneActiveAction(Validator $validator): void
{ {
$data = $validator->getData(); $data = $validator->getData();
/** @var null|array|int|string $actions */ /** @var null|array|int|string $actions */
$actions = $data['actions'] ?? []; $actions = $data['actions'] ?? [];
// need at least one trigger // need at least one trigger
if (!is_countable($actions) || 0 === count($actions)) { if (!is_countable($actions) || 0 === count($actions)) {
return; return;

View File

@@ -141,7 +141,7 @@ class UpdateRequest extends FormRequest
'triggers.*.stop_processing' => [new IsBoolean()], 'triggers.*.stop_processing' => [new IsBoolean()],
'triggers.*.active' => [new IsBoolean()], 'triggers.*.active' => [new IsBoolean()],
'actions.*.type' => 'required|in:'.implode(',', $validActions), 'actions.*.type' => 'required|in:'.implode(',', $validActions),
'actions.*.value' => [sprintf('required_if:actions.*.type,%s',$contextActions), new IsValidActionExpression(), 'ruleActionValue'], 'actions.*.value' => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
'actions.*.stop_processing' => [new IsBoolean()], 'actions.*.stop_processing' => [new IsBoolean()],
'actions.*.active' => [new IsBoolean()], 'actions.*.active' => [new IsBoolean()],
'strict' => [new IsBoolean()], 'strict' => [new IsBoolean()],

View File

@@ -26,9 +26,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Rule;
use FireflyIII\Rules\IsValidActionExpression; use FireflyIII\Rules\IsValidActionExpression;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;
/** /**
* Class ValidateExpressionRequest * Class ValidateExpressionRequest
@@ -41,6 +39,4 @@ class ValidateExpressionRequest extends FormRequest
{ {
return ['expression' => ['required', new IsValidActionExpression()]]; return ['expression' => ['required', new IsValidActionExpression()]];
} }
} }

View File

@@ -31,7 +31,6 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Log;
/** /**
* FireflyIII\Models\RuleAction * FireflyIII\Models\RuleAction
@@ -67,7 +66,7 @@ class RuleAction extends Model
use ReturnsIntegerIdTrait; use ReturnsIntegerIdTrait;
protected $casts protected $casts
= [ = [
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
'active' => 'boolean', 'active' => 'boolean',
@@ -80,12 +79,14 @@ class RuleAction extends Model
public function getValue(array $journal): string public function getValue(array $journal): string
{ {
if (false === config('firefly.feature_flags.expression_engine')) { if (false === config('firefly.feature_flags.expression_engine')) {
Log::debug('Expression engine is disabled, returning action value as string.'); \Log::debug('Expression engine is disabled, returning action value as string.');
return (string)$this->action_value; return (string)$this->action_value;
} }
$expr = new ActionExpression($this->action_value); $expr = new ActionExpression($this->action_value);
$result = $expr->evaluate($journal); $result = $expr->evaluate($journal);
Log::debug(sprintf('Expression engine is enabled, result of expression "%s" is "%s".', $this->action_value, $result)); \Log::debug(sprintf('Expression engine is enabled, result of expression "%s" is "%s".', $this->action_value, $result));
return $result; return $result;
} }
@@ -97,14 +98,14 @@ class RuleAction extends Model
protected function order(): Attribute protected function order(): Attribute
{ {
return Attribute::make( return Attribute::make(
get: static fn($value) => (int)$value, get: static fn ($value) => (int)$value,
); );
} }
protected function ruleId(): Attribute protected function ruleId(): Attribute
{ {
return Attribute::make( return Attribute::make(
get: static fn($value) => (int)$value, get: static fn ($value) => (int)$value,
); );
} }
} }

View File

@@ -44,7 +44,7 @@ class IsValidActionExpression implements ValidationRule
return; return;
} }
$value ??= ''; $value ??= '';
$expr = new ActionExpression($value); $expr = new ActionExpression($value);
if (!$expr->isValid()) { if (!$expr->isValid()) {
$fail('validation.rule_action_expression')->translate( $fail('validation.rule_action_expression')->translate(

View File

@@ -34,8 +34,8 @@ use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
*/ */
class AppendDescription implements ActionInterface class AppendDescription implements ActionInterface
{ {
private RuleAction $action;
use RefreshNotesTrait; use RefreshNotesTrait;
private RuleAction $action;
/** /**
* TriggerInterface constructor. * TriggerInterface constructor.

View File

@@ -51,6 +51,7 @@ class AppendDescriptionToNotes implements ActionInterface
public function actOnArray(array $journal): bool public function actOnArray(array $journal): bool
{ {
$this->refreshNotes($journal); $this->refreshNotes($journal);
/** @var null|TransactionJournal $object */ /** @var null|TransactionJournal $object */
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']); $object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
if (null === $object) { if (null === $object) {

View File

@@ -27,6 +27,7 @@ use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Models\Note; use FireflyIII\Models\Note;
use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
/** /**
* Class AppendNotes. * Class AppendNotes.
@@ -34,6 +35,7 @@ use FireflyIII\Models\TransactionJournal;
*/ */
class AppendNotes implements ActionInterface class AppendNotes implements ActionInterface
{ {
use RefreshNotesTrait;
private RuleAction $action; private RuleAction $action;
/** /**
@@ -46,6 +48,7 @@ class AppendNotes implements ActionInterface
public function actOnArray(array $journal): bool public function actOnArray(array $journal): bool
{ {
$this->refreshNotes($journal);
$dbNote = Note::where('noteable_id', (int)$journal['transaction_journal_id']) $dbNote = Note::where('noteable_id', (int)$journal['transaction_journal_id'])
->where('noteable_type', TransactionJournal::class) ->where('noteable_type', TransactionJournal::class)
->first(['notes.*']) ->first(['notes.*'])

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\Note;
use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
/** /**
* Class AppendNotesToDescription * Class AppendNotesToDescription
@@ -38,7 +39,7 @@ use FireflyIII\Support\Request\ConvertsDataTypes;
class AppendNotesToDescription implements ActionInterface class AppendNotesToDescription implements ActionInterface
{ {
use ConvertsDataTypes; use ConvertsDataTypes;
use RefreshNotesTrait;
private RuleAction $action; private RuleAction $action;
/** /**
@@ -52,6 +53,7 @@ class AppendNotesToDescription implements ActionInterface
public function actOnArray(array $journal): bool public function actOnArray(array $journal): bool
{ {
app('log')->debug('Now in AppendNotesToDescription'); app('log')->debug('Now in AppendNotesToDescription');
$this->refreshNotes($journal);
/** @var null|TransactionJournal $object */ /** @var null|TransactionJournal $object */
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']); $object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);

View File

@@ -26,12 +26,14 @@ namespace FireflyIII\TransactionRules\Actions;
use FireflyIII\Events\TriggeredAuditLog; use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
/** /**
* Class SetDescription. * Class SetDescription.
*/ */
class SetDescription implements ActionInterface class SetDescription implements ActionInterface
{ {
use RefreshNotesTrait;
private RuleAction $action; private RuleAction $action;
/** /**
@@ -44,6 +46,8 @@ class SetDescription implements ActionInterface
public function actOnArray(array $journal): bool public function actOnArray(array $journal): bool
{ {
$this->refreshNotes($journal);
/** @var TransactionJournal $object */ /** @var TransactionJournal $object */
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']); $object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
$before = $object->description; $before = $object->description;

View File

@@ -53,10 +53,10 @@ class SearchRuleEngine implements RuleEngineInterface
public function __construct() public function __construct()
{ {
$this->rules = new Collection(); $this->rules = new Collection();
$this->groups = new Collection(); $this->groups = new Collection();
$this->operators = []; $this->operators = [];
$this->resultCount = []; $this->resultCount = [];
// always collect the triggers from the database, unless indicated otherwise. // always collect the triggers from the database, unless indicated otherwise.
$this->refreshTriggers = true; $this->refreshTriggers = true;
@@ -73,7 +73,7 @@ class SearchRuleEngine implements RuleEngineInterface
app('log')->debug('SearchRuleEngine::find()'); app('log')->debug('SearchRuleEngine::find()');
$collection = new Collection(); $collection = new Collection();
foreach ($this->rules as $rule) { foreach ($this->rules as $rule) {
$found = new Collection(); $found = new Collection();
if (true === $rule->strict) { if (true === $rule->strict) {
$found = $this->findStrictRule($rule); $found = $this->findStrictRule($rule);
} }
@@ -82,8 +82,9 @@ class SearchRuleEngine implements RuleEngineInterface
} }
$collection = $collection->merge($found); $collection = $collection->merge($found);
} }
$result = $collection->unique(); $result = $collection->unique();
app('log')->debug(sprintf('SearchRuleEngine::find() returns %d unique transactions.', $result->count())); app('log')->debug(sprintf('SearchRuleEngine::find() returns %d unique transactions.', $result->count()));
return $result; return $result;
} }
@@ -93,8 +94,8 @@ class SearchRuleEngine implements RuleEngineInterface
private function findStrictRule(Rule $rule): Collection private function findStrictRule(Rule $rule): Collection
{ {
app('log')->debug(sprintf('Now in findStrictRule(#%d)', $rule->id ?? 0)); app('log')->debug(sprintf('Now in findStrictRule(#%d)', $rule->id ?? 0));
$searchArray = []; $searchArray = [];
$triggers = []; $triggers = [];
if ($this->refreshTriggers) { if ($this->refreshTriggers) {
$triggers = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); $triggers = $rule->ruleTriggers()->orderBy('order', 'ASC')->get();
} }
@@ -125,7 +126,7 @@ class SearchRuleEngine implements RuleEngineInterface
app('log')->debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value'])); app('log')->debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value']));
$searchArray[$operator['type']][] = sprintf('"%s"', $operator['value']); $searchArray[$operator['type']][] = sprintf('"%s"', $operator['value']);
} }
$date = today(config('app.timezone')); $date = today(config('app.timezone'));
if ($this->hasSpecificJournalTrigger($searchArray)) { if ($this->hasSpecificJournalTrigger($searchArray)) {
$date = $this->setDateFromJournalTrigger($searchArray); $date = $this->setDateFromJournalTrigger($searchArray);
} }
@@ -145,7 +146,7 @@ class SearchRuleEngine implements RuleEngineInterface
} }
} }
$result = $searchEngine->searchTransactions(); $result = $searchEngine->searchTransactions();
return $result->getCollection(); return $result->getCollection();
} }
@@ -170,7 +171,7 @@ class SearchRuleEngine implements RuleEngineInterface
$dateTrigger = true; $dateTrigger = true;
} }
} }
$result = $journalTrigger && $dateTrigger; $result = $journalTrigger && $dateTrigger;
app('log')->debug(sprintf('Result of hasSpecificJournalTrigger is %s.', var_export($result, true))); app('log')->debug(sprintf('Result of hasSpecificJournalTrigger is %s.', var_export($result, true)));
return $result; return $result;
@@ -189,7 +190,7 @@ class SearchRuleEngine implements RuleEngineInterface
if (0 !== $journalId) { if (0 !== $journalId) {
$repository = app(JournalRepositoryInterface::class); $repository = app(JournalRepositoryInterface::class);
$repository->setUser($this->user); $repository->setUser($this->user);
$journal = $repository->find($journalId); $journal = $repository->find($journalId);
if (null !== $journal) { if (null !== $journal) {
$date = $journal->date; $date = $journal->date;
app('log')->debug(sprintf('Found journal #%d with date %s.', $journal->id, $journal->date->format('Y-m-d'))); app('log')->debug(sprintf('Found journal #%d with date %s.', $journal->id, $journal->date->format('Y-m-d')));
@@ -265,10 +266,10 @@ class SearchRuleEngine implements RuleEngineInterface
$searchEngine->parseQuery(sprintf('%s:%s', $type, $value)); $searchEngine->parseQuery(sprintf('%s:%s', $type, $value));
} }
$result = $searchEngine->searchTransactions(); $result = $searchEngine->searchTransactions();
$collection = $result->getCollection(); $collection = $result->getCollection();
app('log')->debug(sprintf('Found in this run, %d transactions', $collection->count())); app('log')->debug(sprintf('Found in this run, %d transactions', $collection->count()));
$total = $total->merge($collection); $total = $total->merge($collection);
app('log')->debug(sprintf('Total collection is now %d transactions', $total->count())); app('log')->debug(sprintf('Total collection is now %d transactions', $total->count()));
++$count; ++$count;
// if trigger says stop processing, do so. // if trigger says stop processing, do so.
@@ -282,7 +283,7 @@ class SearchRuleEngine implements RuleEngineInterface
app('log')->debug(sprintf('Done running %d trigger(s)', $count)); app('log')->debug(sprintf('Done running %d trigger(s)', $count));
// make collection unique // make collection unique
$unique = $total->unique( $unique = $total->unique(
static function (array $group) { static function (array $group) {
$str = ''; $str = '';
foreach ($group['transactions'] as $transaction) { foreach ($group['transactions'] as $transaction) {
@@ -373,7 +374,7 @@ class SearchRuleEngine implements RuleEngineInterface
$this->processResults($rule, $collection); $this->processResults($rule, $collection);
app('log')->debug(sprintf('SearchRuleEngine:: done processing strict rule #%d', $rule->id)); app('log')->debug(sprintf('SearchRuleEngine:: done processing strict rule #%d', $rule->id));
$result = $collection->count() > 0; $result = $collection->count() > 0;
if (true === $result) { if (true === $result) {
app('log')->debug(sprintf('SearchRuleEngine:: rule #%d was triggered (on %d transaction(s)).', $rule->id, $collection->count())); app('log')->debug(sprintf('SearchRuleEngine:: rule #%d was triggered (on %d transaction(s)).', $rule->id, $collection->count()));
@@ -546,6 +547,7 @@ class SearchRuleEngine implements RuleEngineInterface
$transaction['notes'] = $dbNote->text; $transaction['notes'] = $dbNote->text;
} }
Log::debug(sprintf('Notes of journal #%d filled in.', $transaction['transaction_journal_id'])); Log::debug(sprintf('Notes of journal #%d filled in.', $transaction['transaction_journal_id']));
return $transaction; return $transaction;
} }
} }

View File

@@ -31,26 +31,24 @@ class ActionExpressionLanguageProvider implements ExpressionFunctionProviderInte
{ {
public function getFunctions(): array public function getFunctions(): array
{ {
return [ return [
new ExpressionFunction('constant', function ($str): string { new ExpressionFunction('constant', function ($str): string {
return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str . '!'); return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str.'!');
}, function ($arguments, $str): string { }, function ($arguments, $str): string {
if (!is_string($str)) { if (!is_string($str)) {
return $str; return $str;
} }
return strtolower($str . '!'); return strtolower($str.'!');
}), }),
new ExpressionFunction('enum', function ($str): string { new ExpressionFunction('enum', function ($str): string {
return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str . '?'); return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str.'?');
}, function ($arguments, $str): string { }, function ($arguments, $str): string {
if (!is_string($str)) { if (!is_string($str)) {
return $str; return $str;
} }
return strtolower($str) . '?'; return strtolower($str).'?';
}), }),
ExpressionFunction::fromPhp('substr'), ExpressionFunction::fromPhp('substr'),

View File

@@ -29,11 +29,6 @@ use Illuminate\Support\Facades\Log;
trait RefreshNotesTrait trait RefreshNotesTrait
{ {
/**
* @param array $transaction
*
* @return array
*/
final protected function refreshNotes(array $transaction): array final protected function refreshNotes(array $transaction): array
{ {
$transaction['notes'] = ''; $transaction['notes'] = '';
@@ -42,7 +37,7 @@ trait RefreshNotesTrait
$transaction['notes'] = $dbNote->text; $transaction['notes'] = $dbNote->text;
} }
Log::debug(sprintf('Notes of journal #%d refreshed.', $transaction['transaction_journal_id'])); Log::debug(sprintf('Notes of journal #%d refreshed.', $transaction['transaction_journal_id']));
return $transaction; return $transaction;
} }
} }

View File

@@ -607,7 +607,6 @@ Route::group(
Route::put('{rule}', ['uses' => 'UpdateController@update', 'as' => 'update']); Route::put('{rule}', ['uses' => 'UpdateController@update', 'as' => 'update']);
Route::delete('{rule}', ['uses' => 'DestroyController@destroy', 'as' => 'delete']); Route::delete('{rule}', ['uses' => 'DestroyController@destroy', 'as' => 'delete']);
Route::get('{rule}/test', ['uses' => 'TriggerController@testRule', 'as' => 'test']); Route::get('{rule}/test', ['uses' => 'TriggerController@testRule', 'as' => 'test']);
// TODO give results back // TODO give results back
Route::post('{rule}/trigger', ['uses' => 'TriggerController@triggerRule', 'as' => 'trigger']); Route::post('{rule}/trigger', ['uses' => 'TriggerController@triggerRule', 'as' => 'trigger']);