Refactor rule creation.

This commit is contained in:
James Cole
2018-08-05 15:34:20 +02:00
parent 07a8c69ba8
commit 422e80530b
12 changed files with 337 additions and 337 deletions

View File

@@ -30,7 +30,6 @@ use FireflyIII\Models\Bill;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Controllers\RuleManagement; use FireflyIII\Support\Http\Controllers\RuleManagement;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -71,6 +70,8 @@ class CreateController extends Controller
/** /**
* Create a new rule. It will be stored under the given $ruleGroup. * Create a new rule. It will be stored under the given $ruleGroup.
* *
* TODO reinstate bill specific code.
*
* @param Request $request * @param Request $request
* @param RuleGroup $ruleGroup * @param RuleGroup $ruleGroup
* *
@@ -78,51 +79,33 @@ class CreateController extends Controller
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/ */
public function create(Request $request, RuleGroup $ruleGroup) public function create(Request $request, RuleGroup $ruleGroup = null)
{ {
$this->createDefaultRuleGroup(); $this->createDefaultRuleGroup();
$this->createDefaultRule(); $this->createDefaultRule();
$bill = null;
$billId = (int)$request->get('fromBill');
$preFilled = [ $preFilled = [
'strict' => true, 'strict' => true,
]; ];
$oldTriggers = []; $oldTriggers = [];
$oldActions = []; $oldActions = [];
$returnToBill = false;
if ('true' === $request->get('return')) { // restore actions and triggers from old input:
$returnToBill = true;
}
// has bill?
if ($billId > 0) {
$bill = $this->billRepos->find($billId);
}
// has old input?
if ($request->old()) { if ($request->old()) {
$oldTriggers = $this->getPreviousTriggers($request); $oldTriggers = $this->getPreviousTriggers($request);
$oldActions = $this->getPreviousActions($request); $oldActions = $this->getPreviousActions($request);
} }
// has existing bill refered to in URI?
if (null !== $bill && !$request->old()) {
// create some sensible defaults:
$preFilled['title'] = (string)trans('firefly.new_rule_for_bill_title', ['name' => $bill->name]);
$preFilled['description'] = (string)trans('firefly.new_rule_for_bill_description', ['name' => $bill->name]);
// get triggers and actions for bill:
$oldTriggers = $this->getTriggersForBill($bill);
$oldActions = $this->getActionsForBill($bill);
}
$triggerCount = \count($oldTriggers); $triggerCount = \count($oldTriggers);
$actionCount = \count($oldActions); $actionCount = \count($oldActions);
$subTitleIcon = 'fa-clone'; $subTitleIcon = 'fa-clone';
$subTitle = (string)trans('firefly.make_new_rule', ['title' => $ruleGroup->title]);
// title depends on whether or not there is a rule group:
$subTitle = (string)trans('firefly.make_new_rule_no_group');
if (null !== $ruleGroup) {
$subTitle = (string)trans('firefly.make_new_rule', ['title' => $ruleGroup->title]);
}
// flash old data
$request->session()->flash('preFilled', $preFilled); $request->session()->flash('preFilled', $preFilled);
// put previous url in session if not redirect from store (not "create another"). // put previous url in session if not redirect from store (not "create another").
@@ -132,11 +115,7 @@ class CreateController extends Controller
session()->forget('rules.create.fromStore'); session()->forget('rules.create.fromStore');
return view( return view(
'rules.rule.create', 'rules.rule.create', compact('subTitleIcon', 'oldTriggers', 'preFilled', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroup', 'subTitle')
compact(
'subTitleIcon', 'oldTriggers', 'returnToBill', 'preFilled', 'bill', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroup',
'subTitle'
)
); );
} }

View File

@@ -124,7 +124,6 @@ class SelectController extends Controller
return view('rules.rule.select-transactions', compact('first', 'today', 'rule', 'subTitle')); return view('rules.rule.select-transactions', compact('first', 'today', 'rule', 'subTitle'));
} }
/** /**
* This method allows the user to test a certain set of rule triggers. The rule triggers are passed along * This method allows the user to test a certain set of rule triggers. The rule triggers are passed along
* using the URL parameters (GET), and are usually put there using a Javascript thing. * using the URL parameters (GET), and are usually put there using a Javascript thing.
@@ -267,18 +266,13 @@ class SelectController extends Controller
private function getValidTriggerList(TestRuleFormRequest $request): array private function getValidTriggerList(TestRuleFormRequest $request): array
{ {
$triggers = []; $triggers = [];
$data = [ $data = $request->get('rule_triggers');
'rule-triggers' => $request->get('rule-trigger'), if (\is_array($data)) {
'rule-trigger-values' => $request->get('rule-trigger-value'), foreach ($data as $index => $triggerInfo) {
'rule-trigger-stop' => $request->get('rule-trigger-stop'),
];
if (\is_array($data['rule-triggers'])) {
foreach ($data['rule-triggers'] as $index => $triggerType) {
$data['rule-trigger-stop'][$index] = (int)($data['rule-trigger-stop'][$index] ?? 0.0);
$triggers[] = [ $triggers[] = [
'type' => $triggerType, 'type' => $triggerInfo['name'] ?? '',
'value' => $data['rule-trigger-values'][$index], 'value' => $triggerInfo['value'] ?? '',
'stopProcessing' => 1 === (int)$data['rule-trigger-stop'][$index], 'stop_processing' => 1 === (int)($triggerInfo['stop_processing'] ?? '0'),
]; ];
} }
} }

View File

@@ -56,39 +56,11 @@ class RuleFormRequest extends Request
'active' => $this->boolean('active'), 'active' => $this->boolean('active'),
'trigger' => $this->string('trigger'), 'trigger' => $this->string('trigger'),
'description' => $this->string('description'), 'description' => $this->string('description'),
'stop-processing' => $this->boolean('stop_processing'), 'stop_processing' => $this->boolean('stop_processing'),
'strict' => $this->boolean('strict'), 'strict' => $this->boolean('strict'),
'rule-triggers' => [], 'rule_triggers' => $this->getRuleTriggerData(),
'rule-actions' => [], 'rule_actions' => $this->getRuleActionData(),
]; ];
$triggers = $this->get('rule-trigger');
$triggerValues = $this->get('rule-trigger-value');
$triggerStop = $this->get('rule-trigger-stop');
$actions = $this->get('rule-action');
$actionValues = $this->get('rule-action-value');
$actionStop = $this->get('rule-action-stop');
if (\is_array($triggers)) {
foreach ($triggers as $index => $value) {
$data['rule-triggers'][] = [
'name' => $value,
'value' => $triggerValues[$index] ?? '',
'stop-processing' => 1 === (int)($triggerStop[$index] ?? 0),
];
}
}
if (\is_array($actions)) {
foreach ($actions as $index => $value) {
$data['rule-actions'][] = [
'name' => $value,
'value' => $actionValues[$index] ?? '',
'stop-processing' => 1 === (int)($actionStop[$index] ?? 0),
];
}
}
return $data; return $data;
} }
@@ -117,16 +89,60 @@ class RuleFormRequest extends Request
'stop_processing' => 'boolean', 'stop_processing' => 'boolean',
'rule_group_id' => 'required|belongsToUser:rule_groups', 'rule_group_id' => 'required|belongsToUser:rule_groups',
'trigger' => 'required|in:store-journal,update-journal', 'trigger' => 'required|in:store-journal,update-journal',
'rule-trigger.*' => 'required|in:' . implode(',', $validTriggers), 'rule_triggers.*.name' => 'required|in:' . implode(',', $validTriggers),
'rule-trigger-value.*' => 'required|min:1|ruleTriggerValue', 'rule_triggers.*.value' => 'required|min:1|ruleTriggerValue',
'rule-action.*' => 'required|in:' . implode(',', $validActions), 'rule-actions.*.name' => 'required|in:' . implode(',', $validActions),
'strict' => 'in:0,1', 'strict' => 'in:0,1',
]; ];
// since Laravel does not support this stuff yet, here's a trick. // since Laravel does not support this stuff yet, here's a trick.
for ($i = 0; $i < 10; ++$i) { for ($i = 0; $i < 10; ++$i) {
$rules['rule-action-value.' . $i] = 'required_if:rule-action.' . $i . ',' . $contextActions . '|ruleActionValue'; $key = sprintf('rule_actions.%d.value', $i);
$rule = sprintf('required-if:rule_actions.%d.name,%s|ruleActionValue', $i, $contextActions);
$rules[$key] = $rule;
} }
return $rules; return $rules;
} }
/**
* @return array
*/
private function getRuleActionData(): array
{
$return = [];
$actionData= $this->get('rule_actions');
if (\is_array($actionData)) {
foreach ($actionData as $action) {
$stopProcessing = $action['stop_processing'] ?? '0';
$return[] = [
'name' => $action['name'] ?? 'invalid',
'value' => $action['value'] ?? '',
'stop_processing' => 1 === (int)$stopProcessing,
];
}
}
return $return;
}
/**
* @return array
*/
private function getRuleTriggerData(): array
{
$return = [];
$triggerData = $this->get('rule_triggers');
if (\is_array($triggerData)) {
foreach ($triggerData as $trigger) {
$stopProcessing = $trigger['stop_processing'] ?? '0';
$return[] = [
'name' => $trigger['name'] ?? 'invalid',
'value' => $trigger['value'] ?? '',
'stop_processing' => 1 === (int)$stopProcessing,
];
}
}
return $return;
}
} }

View File

@@ -292,7 +292,7 @@ class RuleRepository implements RuleRepositoryInterface
$rule->order = ($order + 1); $rule->order = ($order + 1);
$rule->active = true; $rule->active = true;
$rule->strict = $data['strict'] ?? false; $rule->strict = $data['strict'] ?? false;
$rule->stop_processing = 1 === (int)$data['stop-processing']; $rule->stop_processing = 1 === (int)$data['stop_processing'];
$rule->title = $data['title']; $rule->title = $data['title'];
$rule->description = \strlen($data['description']) > 0 ? $data['description'] : null; $rule->description = \strlen($data['description']) > 0 ? $data['description'] : null;
@@ -319,7 +319,7 @@ class RuleRepository implements RuleRepositoryInterface
$ruleAction->rule()->associate($rule); $ruleAction->rule()->associate($rule);
$ruleAction->order = $values['order']; $ruleAction->order = $values['order'];
$ruleAction->active = true; $ruleAction->active = true;
$ruleAction->stop_processing = $values['stopProcessing']; $ruleAction->stop_processing = $values['stop_processing'];
$ruleAction->action_type = $values['action']; $ruleAction->action_type = $values['action'];
$ruleAction->action_value = $values['value'] ?? ''; $ruleAction->action_value = $values['value'] ?? '';
$ruleAction->save(); $ruleAction->save();
@@ -339,7 +339,7 @@ class RuleRepository implements RuleRepositoryInterface
$ruleTrigger->rule()->associate($rule); $ruleTrigger->rule()->associate($rule);
$ruleTrigger->order = $values['order']; $ruleTrigger->order = $values['order'];
$ruleTrigger->active = true; $ruleTrigger->active = true;
$ruleTrigger->stop_processing = $values['stopProcessing']; $ruleTrigger->stop_processing = $values['stop_processing'];
$ruleTrigger->trigger_type = $values['action']; $ruleTrigger->trigger_type = $values['action'];
$ruleTrigger->trigger_value = $values['value'] ?? ''; $ruleTrigger->trigger_value = $values['value'] ?? '';
$ruleTrigger->save(); $ruleTrigger->save();
@@ -358,7 +358,7 @@ class RuleRepository implements RuleRepositoryInterface
// update rule: // update rule:
$rule->rule_group_id = $data['rule_group_id']; $rule->rule_group_id = $data['rule_group_id'];
$rule->active = $data['active']; $rule->active = $data['active'];
$rule->stop_processing = $data['stop-processing']; $rule->stop_processing = $data['stop_processing'];
$rule->title = $data['title']; $rule->title = $data['title'];
$rule->strict = $data['strict'] ?? false; $rule->strict = $data['strict'] ?? false;
$rule->description = $data['description']; $rule->description = $data['description'];
@@ -388,14 +388,14 @@ class RuleRepository implements RuleRepositoryInterface
private function storeActions(Rule $rule, array $data): bool private function storeActions(Rule $rule, array $data): bool
{ {
$order = 1; $order = 1;
foreach ($data['rule-actions'] as $action) { foreach ($data['rule_actions'] as $action) {
$value = $action['value'] ?? ''; $value = $action['value'] ?? '';
$stopProcessing = $action['stop-processing'] ?? false; $stopProcessing = $action['stop_processing'] ?? false;
$actionValues = [ $actionValues = [
'action' => $action['name'], 'action' => $action['name'],
'value' => $value, 'value' => $value,
'stopProcessing' => $stopProcessing, 'stop_processing' => $stopProcessing,
'order' => $order, 'order' => $order,
]; ];
@@ -419,19 +419,19 @@ class RuleRepository implements RuleRepositoryInterface
$triggerValues = [ $triggerValues = [
'action' => 'user_action', 'action' => 'user_action',
'value' => $data['trigger'], 'value' => $data['trigger'],
'stopProcessing' => $stopProcessing, 'stop_processing' => $stopProcessing,
'order' => $order, 'order' => $order,
]; ];
$this->storeTrigger($rule, $triggerValues); $this->storeTrigger($rule, $triggerValues);
foreach ($data['rule-triggers'] as $trigger) { foreach ($data['rule_triggers'] as $trigger) {
$value = $trigger['value'] ?? ''; $value = $trigger['value'] ?? '';
$stopProcessing = $trigger['stop-processing'] ?? false; $stopProcessing = $trigger['stop_processing'] ?? false;
$triggerValues = [ $triggerValues = [
'action' => $trigger['name'], 'action' => $trigger['name'],
'value' => $value, 'value' => $value,
'stopProcessing' => $stopProcessing, 'stop_processing' => $stopProcessing,
'order' => $order, 'order' => $order,
]; ];

View File

@@ -93,33 +93,30 @@ trait RuleManagement
*/ */
protected function getPreviousActions(Request $request): array protected function getPreviousActions(Request $request): array
{ {
$newIndex = 0; $index = 0;
$actions = []; $triggers = [];
/** @var array $oldActions */ $oldInput = $request->old('rule_actions');
$oldActions = \is_array($request->old('rule-action')) ? $request->old('rule-action') : []; if (\is_array($oldInput)) {
foreach ($oldActions as $index => $entry) { foreach ($oldInput as $oldAction) {
$count = ($newIndex + 1);
$checked = isset($request->old('rule-action-stop')[$index]) ? true : false;
try { try {
$actions[] = view( $triggers[] = view(
'rules.partials.action', 'rules.partials.action',
[ [
'oldAction' => $entry, 'oldAction' => $oldAction['name'],
'oldValue' => $request->old('rule-action-value')[$index], 'oldValue' => $oldAction['value'],
'oldChecked' => $checked, 'oldChecked' => 1 === (int)($oldAction['stop_processing'] ?? '0'),
'count' => $count, 'count' => $index + 1,
] ]
)->render(); )->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getPreviousActions(): %s', $e->getMessage())); Log::debug(sprintf('Throwable was thrown in getPreviousActions(): %s', $e->getMessage()));
Log::error($e->getTraceAsString()); Log::error($e->getTraceAsString());
} }
// @codeCoverageIgnoreEnd $index++;
++$newIndex; }
} }
return $actions; return $triggers;
} }
/** /**
@@ -129,30 +126,27 @@ trait RuleManagement
*/ */
protected function getPreviousTriggers(Request $request): array protected function getPreviousTriggers(Request $request): array
{ {
$newIndex = 0; $index = 0;
$triggers = []; $triggers = [];
/** @var array $oldTriggers */ $oldInput = $request->old('rule_triggers');
$oldTriggers = \is_array($request->old('rule-trigger')) ? $request->old('rule-trigger') : []; if (\is_array($oldInput)) {
foreach ($oldTriggers as $index => $entry) { foreach ($oldInput as $oldTrigger) {
$count = ($newIndex + 1);
$oldChecked = isset($request->old('rule-trigger-stop')[$index]) ? true : false;
try { try {
$triggers[] = view( $triggers[] = view(
'rules.partials.trigger', 'rules.partials.trigger',
[ [
'oldTrigger' => $entry, 'oldTrigger' => $oldTrigger['name'],
'oldValue' => $request->old('rule-trigger-value')[$index], 'oldValue' => $oldTrigger['value'],
'oldChecked' => $oldChecked, 'oldChecked' => 1 === (int)($oldTrigger['stop_processing'] ?? '0'),
'count' => $count, 'count' => $index + 1,
] ]
)->render(); )->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getPreviousTriggers(): %s', $e->getMessage())); Log::debug(sprintf('Throwable was thrown in getPreviousTriggers(): %s', $e->getMessage()));
Log::error($e->getTraceAsString()); Log::error($e->getTraceAsString());
} }
// @codeCoverageIgnoreEnd $index++;
++$newIndex; }
} }
return $triggers; return $triggers;

View File

@@ -89,7 +89,7 @@ class TriggerFactory
/** @var AbstractTrigger $class */ /** @var AbstractTrigger $class */
$class = self::getTriggerClass($triggerType); $class = self::getTriggerClass($triggerType);
$obj = $class::makeFromStrings($triggerValue, $stopProcessing); $obj = $class::makeFromStrings($triggerValue, $stopProcessing);
Log::debug('Created trigger from string', ['type' => $triggerType, 'value' => $triggerValue, 'stopProcessing' => $stopProcessing, 'class' => $class]); Log::debug('Created trigger from string', ['type' => $triggerType, 'value' => $triggerValue, 'stop_processing' => $stopProcessing, 'class' => $class]);
return $obj; return $obj;
} }

View File

@@ -122,7 +122,7 @@ final class Processor
* *
* The given triggers must be in the following format: * The given triggers must be in the following format:
* *
* [type => xx, value => yy, stopProcessing => bool], [type => xx, value => yy, stopProcessing => bool], * [type => xx, value => yy, stop_processing => bool], [type => xx, value => yy, stop_processing => bool],
* *
* @param array $triggers * @param array $triggers
* *
@@ -135,7 +135,7 @@ final class Processor
$self = new self; $self = new self;
foreach ($triggers as $entry) { foreach ($triggers as $entry) {
$entry['value'] = $entry['value'] ?? ''; $entry['value'] = $entry['value'] ?? '';
$trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stopProcessing']); $trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stop_processing']);
$self->triggers->push($trigger); $self->triggers->push($trigger);
} }

View File

@@ -247,37 +247,22 @@ class FireflyValidator extends Validator
* *
* @return bool * @return bool
*/ */
public function validateRuleActionValue($attribute): bool public function validateRuleActionValue(string $attribute, string $value): bool
{ {
// get the index from a string like "rule-action-value.2". // first, get the index from this string:
$parts = explode('.', $attribute); $parts = explode('.', $attribute);
$index = $parts[\count($parts) - 1]; $index = (int)($parts[1] ?? '0');
if ($index === 'value') {
// user is coming from API.
$index = $parts[\count($parts) - 2];
}
$index = (int)$index;
// get actions from $this->data // get the name of the trigger from the data array:
$actions = []; $actionType = $this->data['rule_actions'][$index]['name'] ?? 'invalid';
if (isset($this->data['rule-action']) && \is_array($this->data['rule-action'])) {
$actions = $this->data['rule-action']; // if it's "invalid" return false.
} if ('invalid' === $actionType) {
if (isset($this->data['rule-actions']) && \is_array($this->data['rule-actions'])) { return false;
$actions = $this->data['rule-actions'];
} }
// if it's set_budget, verify the budget name:
// loop all rule-actions. if ('set_budget' === $actionType) {
// check if rule-action-value matches the thing.
if (\is_array($actions)) {
$name = $this->getRuleActionName($index);
$value = $this->getRuleActionValue($index);
switch ($name) {
default:
return true;
case 'set_budget':
/** @var BudgetRepositoryInterface $repository */ /** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class); $repository = app(BudgetRepositoryInterface::class);
$budgets = $repository->getBudgets(); $budgets = $repository->getBudgets();
@@ -289,108 +274,74 @@ class FireflyValidator extends Validator
)->count(); )->count();
return 1 === $count; return 1 === $count;
case 'link_to_bill': }
// if it's link to bill, verify the name of the bill.
if ('link_to_bill' === $actionType) {
/** @var BillRepositoryInterface $repository */ /** @var BillRepositoryInterface $repository */
$repository = app(BillRepositoryInterface::class); $repository = app(BillRepositoryInterface::class);
$bill = $repository->findByName($value); $bill = $repository->findByName($value);
return null !== $bill; return null !== $bill;
case 'invalid':
return false;
}
} }
return false; // return true for the rest.
return true;
} }
/** /**
* @param $attribute * $attribute has the format rule_triggers.%d.value.
*
* @param string $attribute
* @param string $value
* *
* @return bool * @return bool
*/ */
public function validateRuleTriggerValue($attribute): bool public function validateRuleTriggerValue(string $attribute, string $value): bool
{ {
// get the index from a string like "rule-trigger-value.2". //
// first, get the index from this string:
$parts = explode('.', $attribute); $parts = explode('.', $attribute);
$index = $parts[\count($parts) - 1]; $index = (int)($parts[1] ?? '0');
// if the index is not a number, then we might be dealing with an API $attribute
// which is formatted "rule-triggers.0.value"
if ($index === 'value') {
$index = $parts[\count($parts) - 2];
}
$index = (int)$index;
// get triggers from $this->data // get the name of the trigger from the data array:
$triggers = []; $triggerType = $this->data['rule_triggers'][$index]['name'] ?? 'invalid';
if (isset($this->data['rule-trigger']) && \is_array($this->data['rule-trigger'])) {
$triggers = $this->data['rule-trigger'];
}
if (isset($this->data['rule-triggers']) && \is_array($this->data['rule-triggers'])) {
$triggers = $this->data['rule-triggers'];
}
// loop all rule-triggers. // invalid always returns false:
// check if rule-value matches the thing. if ('invalid' === $triggerType) {
if (\is_array($triggers)) {
$name = $this->getRuleTriggerName($index);
$value = $this->getRuleTriggerValue($index);
// break on some easy checks:
switch ($name) {
case 'amount_less':
case 'amount_more':
case 'amount_exactly':
$result = is_numeric($value);
if (false === $result) {
return false; return false;
} }
break;
case 'from_account_starts':
case 'from_account_ends':
case 'from_account_is':
case 'from_account_contains':
case 'to_account_starts':
case 'to_account_ends':
case 'to_account_is':
case 'to_account_contains':
case 'description_starts':
case 'description_ends':
case 'description_contains':
case 'description_is':
case 'category_is':
case 'budget_is':
case 'tag_is':
case 'currency_is':
case 'notes_contain':
case 'notes_start':
case 'notes_end':
case 'notes_are':
return \strlen($value) > 0;
break; // these trigger types need a numerical check:
case 'transaction_type': $numerical = ['amount_less', 'amount_more', 'amount_exactly'];
if (\in_array($triggerType, $numerical, true)) {
return is_numeric($value);
}
// these trigger types need a simple strlen check:
$length = ['from_account_starts', 'from_account_ends', 'from_account_is', 'from_account_contains', 'to_account_starts', 'to_account_ends',
'to_account_is', 'to_account_contains', 'description_starts', 'description_ends', 'description_contains', 'description_is', 'category_is',
'budget_is', 'tag_is', 'currency_is', 'notes_contain', 'notes_start', 'notes_end', 'notes_are',];
if (\in_array($triggerType, $length, true)) {
return '' !== $value;
}
// check transaction type.
if ('transaction_type' === $triggerType) {
$count = TransactionType::where('type', $value)->count(); $count = TransactionType::where('type', $value)->count();
if (!(1 === $count)) {
return false; return 1 !== $count;
} }
break;
case 'invalid': // and finally a "will match everything check":
return false; $classes = app('config')->get('firefly.rule-triggers');
}
// still a special case where the trigger is
// triggered in such a way that it would trigger ANYTHING. We can check for such things
// with function willmatcheverything
// we know which class it is so dont bother checking that.
$classes = Config::get('firefly.rule-triggers');
/** @var TriggerInterface $class */ /** @var TriggerInterface $class */
$class = $classes[$name]; $class = $classes[$triggerType];
return !$class::willMatchEverything($value); return !$class::willMatchEverything($value);
} }
return false;
}
/** /**
* @param $attribute * @param $attribute
* @param $value * @param $value

View File

@@ -22,20 +22,26 @@
$(function () { $(function () {
"use strict"; "use strict";
if (triggerCount === 0) {
addNewTrigger();
}
if (actionCount === 0) {
addNewAction();
}
if (triggerCount > 0) { if (triggerCount > 0) {
console.log('trigger count is larger than zero, call onAddNewTrigger.');
onAddNewTrigger(); onAddNewTrigger();
} }
if (actionCount > 0) { if (actionCount > 0) {
console.log('action count is larger than zero, call onAddNewAction.');
onAddNewAction(); onAddNewAction();
} }
if (triggerCount === 0) {
console.log('trigger count is zero, add trigger.');
addNewTrigger();
}
if (actionCount === 0) {
console.log('action count is zero, add action.');
addNewAction();
}
$('.add_rule_trigger').click(addNewTrigger); $('.add_rule_trigger').click(addNewTrigger);
$('.add_rule_action').click(addNewAction); $('.add_rule_action').click(addNewAction);
$('.test_rule_triggers').click(testRuleTriggers); $('.test_rule_triggers').click(testRuleTriggers);
@@ -49,7 +55,7 @@ $(function () {
function addNewTrigger() { function addNewTrigger() {
"use strict"; "use strict";
triggerCount++; triggerCount++;
console.log('In addNewTrigger(), count is now ' + triggerCount);
// disable the button // disable the button
$('.add_rule_trigger').attr('disabled', 'disabled'); $('.add_rule_trigger').attr('disabled', 'disabled');
@@ -81,7 +87,7 @@ function addNewTrigger() {
function addNewAction() { function addNewAction() {
"use strict"; "use strict";
actionCount++; actionCount++;
console.log('In addNewAction(), count is now ' + actionCount);
// disable the button // disable the button
$('.add_rule_action').attr('disabled', 'disabled'); $('.add_rule_action').attr('disabled', 'disabled');
@@ -154,18 +160,24 @@ function removeAction(e) {
*/ */
function onAddNewAction() { function onAddNewAction() {
"use strict"; "use strict";
console.log('Now in onAddNewAction()');
var selectQuery = 'select[name^="rule_actions["][name$="][name]"]';
var selectResult = $(selectQuery);
console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length);
// update all "select action type" dropdown buttons so they will respond correctly // update all "select action type" dropdown buttons so they will respond correctly
$('select[name^="rule-action["]').unbind('change').change(function (e) { selectResult.unbind('change').change(function (e) {
var target = $(e.target); var target = $(e.target);
updateActionInput(target) updateActionInput(target)
}); });
$.each($('.rule-action-holder'), function (i, v) { // $.each($('.rule-action-holder'), function (i, v) {
var holder = $(v); // var holder = $(v);
var select = holder.find('select'); // var select = holder.find('select');
updateActionInput(select); // updateActionInput(select);
}); // });
} }
/** /**
@@ -173,18 +185,24 @@ function onAddNewAction() {
*/ */
function onAddNewTrigger() { function onAddNewTrigger() {
"use strict"; "use strict";
console.log('Now in onAddNewTrigger()');
// update all "select trigger type" dropdown buttons so they will respond correctly var selectQuery = 'select[name^="rule_triggers["][name$="][name]"]';
$('select[name^="rule-trigger["]').unbind('change').change(function (e) { var selectResult = $(selectQuery);
console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length);
// trigger when user changes the trigger type.
selectResult.unbind('change').change(function (e) {
var target = $(e.target); var target = $(e.target);
updateTriggerInput(target) updateTriggerInput(target)
}); });
$.each($('.rule-trigger-holder'), function (i, v) { // $.each($('.rule-trigger-holder'), function (i, v) {
var holder = $(v); // var holder = $(v);
var select = holder.find('select'); // var select = holder.find('select');
updateTriggerInput(select); // updateTriggerInput(select);
}); // });
} }
/** /**
@@ -193,42 +211,56 @@ function onAddNewTrigger() {
* @param selectList * @param selectList
*/ */
function updateActionInput(selectList) { function updateActionInput(selectList) {
console.log('Now in updateActionInput() for a select list, currently with value "' + selectList.val() + '".');
// the actual row this select list is in: // the actual row this select list is in:
var parent = selectList.parent().parent(); var parent = selectList.parent().parent();
// the text input we're looking for: // the text input we're looking for:
var input = parent.find('input[name^="rule-action-value["]'); var inputQuery = 'input[name^="rule_actions["][name$="][value]"]';
input.removeAttr('disabled'); var inputResult = parent.find(inputQuery);
console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.');
inputResult.removeAttr('disabled');
switch (selectList.val()) { switch (selectList.val()) {
case 'set_category': case 'set_category':
createAutoComplete(input, 'json/categories'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/categories');
break; break;
case 'clear_category': case 'clear_category':
case 'clear_budget': case 'clear_budget':
case 'clear_notes': case 'clear_notes':
case 'remove_all_tags': case 'remove_all_tags':
input.attr('disabled', 'disabled'); console.log('Select list value is ' + selectList.val() +', so input needs to be disabled.');
inputResult.attr('disabled', 'disabled');
break; break;
case 'set_budget': case 'set_budget':
createAutoComplete(input, 'json/budgets'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/budgets');
break; break;
case 'add_tag': case 'add_tag':
case 'remove_tag': case 'remove_tag':
createAutoComplete(input, 'json/tags'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/tags');
break; break;
case 'set_description': case 'set_description':
createAutoComplete(input, 'json/transaction-journals/all'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/transaction-journals/all');
break; break;
case 'set_source_account': case 'set_source_account':
createAutoComplete(input, 'json/all-accounts'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/all-accounts');
break; break;
case 'set_destination_account': case 'set_destination_account':
createAutoComplete(input, 'json/all-accounts'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/all-accounts');
break; break;
case 'link_to_bill': case 'link_to_bill':
createAutoComplete(input, 'json/bills'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/bills');
break; break;
default: default:
input.typeahead('destroy'); console.log('Select list value is ' + selectList.val() +', destroy auto complete, do nothing else.');
inputResult.typeahead('destroy');
break; break;
} }
} }
@@ -239,11 +271,15 @@ function updateActionInput(selectList) {
* @param selectList * @param selectList
*/ */
function updateTriggerInput(selectList) { function updateTriggerInput(selectList) {
console.log('Now in updateTriggerInput() for a select list, currently with value "' + selectList.val() + '".');
// the actual row this select list is in: // the actual row this select list is in:
var parent = selectList.parent().parent(); var parent = selectList.parent().parent();
// the text input we're looking for: // the text input we're looking for:
var input = parent.find('input[name^="rule-trigger-value["]'); var inputQuery = 'input[name^="rule_triggers["][name$="][value]"]';
input.prop('disabled', false); var inputResult = parent.find(inputQuery);
console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.');
inputResult.prop('disabled', false);
switch (selectList.val()) { switch (selectList.val()) {
case 'from_account_starts': case 'from_account_starts':
case 'from_account_ends': case 'from_account_ends':
@@ -253,26 +289,31 @@ function updateTriggerInput(selectList) {
case 'to_account_ends': case 'to_account_ends':
case 'to_account_is': case 'to_account_is':
case 'to_account_contains': case 'to_account_contains':
createAutoComplete(input, 'json/all-accounts'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/all-accounts');
break; break;
case 'tag_is': case 'tag_is':
// also make tag thing? console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(input, 'json/tags'); createAutoComplete(inputResult, 'json/tags');
break; break;
case 'budget_is': case 'budget_is':
createAutoComplete(input, 'json/budgets'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/budgets');
break; break;
case 'category_is': case 'category_is':
createAutoComplete(input, 'json/categories'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/categories');
break; break;
case 'transaction_type': case 'transaction_type':
createAutoComplete(input, 'json/transaction-types'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/transaction-types');
break; break;
case 'description_starts': case 'description_starts':
case 'description_ends': case 'description_ends':
case 'description_contains': case 'description_contains':
case 'description_is': case 'description_is':
createAutoComplete(input, 'json/transaction-journals/all'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/transaction-journals/all');
break; break;
case 'has_no_category': case 'has_no_category':
case 'has_any_category': case 'has_any_category':
@@ -282,14 +323,17 @@ function updateTriggerInput(selectList) {
case 'no_notes': case 'no_notes':
case 'any_notes': case 'any_notes':
case 'has_any_tag': case 'has_any_tag':
input.prop('disabled', true); console.log('Select list value is ' + selectList.val() +', so input needs to be disabled.');
input.typeahead('destroy'); inputResult.prop('disabled', true);
inputResult.typeahead('destroy');
break; break;
case 'currency_is': case 'currency_is':
createAutoComplete(input, 'json/currency-names'); console.log('Select list value is ' + selectList.val() +', so input needs auto complete.');
createAutoComplete(inputResult, 'json/currency-names');
break; break;
default: default:
input.typeahead('destroy'); console.log('Select list value is ' + selectList.val() +', destroy auto complete, do nothing else.');
inputResult.typeahead('destroy');
break; break;
} }
} }
@@ -300,9 +344,13 @@ function updateTriggerInput(selectList) {
* @param URI * @param URI
*/ */
function createAutoComplete(input, URI) { function createAutoComplete(input, URI) {
console.log('Now in createAutoComplete().')
input.typeahead('destroy'); input.typeahead('destroy');
$.getJSON(URI).done(function (data) { $.getJSON(URI).done(function (data) {
console.log('Input now has auto complete from URI ' + URI);
input.typeahead({source: data, autoSelect: false}); input.typeahead({source: data, autoSelect: false});
}).fail(function() {
console.log('Could not grab URI ' + URI + ' so autocomplete will not work');
}); });
} }
@@ -319,6 +367,7 @@ function testRuleTriggers() {
// Serialize all trigger data // Serialize all trigger data
var triggerData = $(".rule-trigger-tbody").find("input[type=text], input[type=checkbox], select").serializeArray(); var triggerData = $(".rule-trigger-tbody").find("input[type=text], input[type=checkbox], select").serializeArray();
console.log('Found the following trigger data: ' + triggerData);
// Find a list of existing transactions that match these triggers // Find a list of existing transactions that match these triggers
$.get('rules/test', triggerData).done(function (data) { $.get('rules/test', triggerData).done(function (data) {

View File

@@ -3,12 +3,14 @@
<a href="#" class="btn btn-danger btn-sm remove-action"><i class="fa fa-trash"></i></a> <a href="#" class="btn btn-danger btn-sm remove-action"><i class="fa fa-trash"></i></a>
</td> </td>
<td style="width:30%;"> <td style="width:30%;">
{#
{% if errors.has('rule-action.'~count) %} {% if errors.has('rule-action.'~count) %}
<span class="form-control-feedback"><i class="fa fa-fw fa-remove"></i></span> <span class="form-control-feedback"><i class="fa fa-fw fa-remove"></i></span>
<p class="text-danger">{{ errors.first('rule-action.'~count) }}</p> <p class="text-danger">{{ errors.first('rule-action.'~count) }}</p>
{% endif %} {% endif %}
#}
<select name="rule-action[{{ count }}]" class="form-control"> <select name="rule_actions[{{ count }}][name]" class="form-control">
{% for key,name in allRuleActions() %} {% for key,name in allRuleActions() %}
<option value="{{ key }}" label="{{ name }}" {% if key == oldAction %} selected{% endif %}>{{ name }}</option> <option value="{{ key }}" label="{{ name }}" {% if key == oldAction %} selected{% endif %}>{{ name }}</option>
{% endfor %} {% endfor %}
@@ -16,18 +18,20 @@
</td> </td>
<td> <td>
<input autocomplete="off" type="text" value="{{ oldValue }}" name="rule-action-value[{{ count }}]" <input autocomplete="off" type="text" value="{{ oldValue }}" name="rule_actions[{{ count }}][value]"
class="form-control"> class="form-control">
{#
{% if errors.has(('rule-action-value.'~count)) %} {% if errors.has(('rule-action-value.'~count)) %}
<p class="text-danger"> <p class="text-danger">
{{ errors.first('rule-action-value.'~count) }} {{ errors.first('rule-action-value.'~count) }}
</p> </p>
{% endif %} {% endif %}
#}
</td> </td>
<td style="width:20%;"> <td style="width:20%;">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="rule-action-stop[{{ count }}]" value="1" <input type="checkbox" name="rule_actions[{{ count }}][stop_processing]" value="1"
{% if oldChecked %}checked{% endif %} {% if oldChecked %}checked{% endif %}
/> />
</label> </label>

View File

@@ -3,12 +3,14 @@
<a href="#" class="btn btn-danger btn-sm remove-trigger"><i class="fa fa-trash"></i></a> <a href="#" class="btn btn-danger btn-sm remove-trigger"><i class="fa fa-trash"></i></a>
</td> </td>
<td style="width:30%;"> <td style="width:30%;">
{#
{% if errors.has('rule-trigger.'~count) %} {% if errors.has('rule-trigger.'~count) %}
<span class="form-control-feedback"><i class="fa fa-fw fa-remove"></i></span> <span class="form-control-feedback"><i class="fa fa-fw fa-remove"></i></span>
<p class="text-danger">{{ errors.first('rule-trigger.'~count) }}</p> <p class="text-danger">{{ errors.first('rule-trigger.'~count) }}</p>
{% endif %} {% endif %}
#}
<select name="rule-trigger[{{ count }}]" class="form-control"> <select name="rule_triggers[{{ count }}][name]" class="form-control">
{% for key,name in allRuleTriggers() %} {% for key,name in allRuleTriggers() %}
<option value="{{ key }}" label="{{ name }}" <option value="{{ key }}" label="{{ name }}"
{% if key == oldTrigger %} {% if key == oldTrigger %}
@@ -20,18 +22,19 @@
</td> </td>
<td style="position: relative;"> <td style="position: relative;">
<input autocomplete="off" type="text" value="{{ oldValue }}" name="rule-trigger-value[{{ count }}]" <input autocomplete="off" type="text" value="{{ oldValue }}" name="rule_triggers[{{ count }}][value]" class="form-control">
class="form-control"> {#
{% if errors.has(('rule-trigger-value.'~count)) %} {% if errors.has(('rule-trigger-value.'~count)) %}
<p class="text-danger"> <p class="text-danger">
{{ errors.first('rule-trigger-value.'~count) }} {{ errors.first('rule-trigger-value.'~count) }}
</p> </p>
{% endif %} {% endif %}
#}
</td> </td>
<td style="width:20%;"> <td style="width:20%;">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="rule-trigger-stop[{{ count }}]" value="1" <input type="checkbox" name="rule_triggers[{{ count }}][stop_processing]" value="1"
{% if oldChecked %}checked{% endif %} {% if oldChecked %}checked{% endif %}
/> />
</label> </label>

View File

@@ -155,7 +155,9 @@ try {
$breadcrumbs->parent('accounts.show', $account); $breadcrumbs->parent('accounts.show', $account);
$what = config('firefly.shortNamesByFullName.' . $account->accountType->type); $what = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => limitStringLength($account->name)]), route('accounts.edit', [$account->id])); $breadcrumbs->push(
trans('firefly.edit_' . $what . '_account', ['name' => limitStringLength($account->name)]), route('accounts.edit', [$account->id])
);
} }
); );
@@ -294,7 +296,9 @@ try {
$object = $attachment->attachable; $object = $attachment->attachable;
if ($object instanceof TransactionJournal) { if ($object instanceof TransactionJournal) {
$breadcrumbs->parent('transactions.show', $object); $breadcrumbs->parent('transactions.show', $object);
$breadcrumbs->push(trans('firefly.delete_attachment', ['name' => limitStringLength($attachment->filename)]), route('attachments.edit', [$attachment])); $breadcrumbs->push(
trans('firefly.delete_attachment', ['name' => limitStringLength($attachment->filename)]), route('attachments.edit', [$attachment])
);
} else { } else {
throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object));
} }
@@ -834,10 +838,16 @@ try {
Breadcrumbs::register( Breadcrumbs::register(
'rules.create', 'rules.create',
function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup = null) {
$breadcrumbs->parent('rules.index'); $breadcrumbs->parent('rules.index');
if (null === $ruleGroup) {
$breadcrumbs->push(trans('firefly.make_new_rule_no_group'), route('rules.create'));
}
if (null !== $ruleGroup) {
$breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.create', [$ruleGroup])); $breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.create', [$ruleGroup]));
} }
}
); );
Breadcrumbs::register( Breadcrumbs::register(
'rules.edit', 'rules.edit',