Add new action that can switch the source and destination account of a transfer

This commit is contained in:
James Cole 2023-08-11 06:16:40 +02:00
parent 2b829cb645
commit 939c636a74
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
4 changed files with 156 additions and 48 deletions

View File

@ -0,0 +1,97 @@
<?php
/**
* ConvertToWithdrawal.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\Actions;
use DB;
use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Facades\Log;
/**
*
* Class SwitchAccounts
*/
class SwitchAccounts implements ActionInterface
{
private RuleAction $action;
/**
* TriggerInterface constructor.
*
* @param RuleAction $action
*/
public function __construct(RuleAction $action)
{
$this->action = $action;
}
/**
* @inheritDoc
*/
public function actOnArray(array $journal): bool
{
// make object from array (so the data is fresh).
/** @var TransactionJournal|null $object */
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
if (null === $object) {
Log::error(sprintf('Cannot find journal #%d, cannot switch accounts.', $journal['transaction_journal_id']));
return false;
}
$groupCount = TransactionJournal::where('transaction_group_id', $journal['transaction_group_id'])->count();
if ($groupCount > 1) {
Log::error(sprintf('Group #%d has more than one transaction in it, cannot switch accounts.', $journal['transaction_group_id']));
return false;
}
$type = $object->transactionType->type;
if (TransactionType::TRANSFER !== $type) {
Log::error(sprintf('Journal #%d is NOT a transfer (rule #%d), cannot switch accounts.', $journal['transaction_journal_id'], $this->action->rule_id));
return false;
}
/** @var Transaction $sourceTransaction */
$sourceTransaction = $object->transactions()->where('amount', '<', 0)->first();
/** @var Transaction $destTransaction */
$destTransaction = $object->transactions()->where('amount', '>', 0)->first();
if (null === $sourceTransaction || null === $destTransaction) {
Log::error(sprintf('Journal #%d has no source or destination transaction (rule #%d), cannot switch accounts.', $journal['transaction_journal_id'], $this->action->rule_id));
return false;
}
$sourceAccountId = (int)$sourceTransaction->account_id;
$destinationAccountId = $destTransaction->account_id;
$sourceTransaction->account_id = $destinationAccountId;
$destTransaction->account_id = $sourceAccountId;
$sourceTransaction->save();
$destTransaction->save();
event(new TriggeredAuditLog($this->action->rule, $object, 'switch_accounts', $sourceAccountId, $destinationAccountId));
return true;
}
}

View File

@ -86,6 +86,7 @@ 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\SwitchAccounts;
use FireflyIII\TransactionRules\Actions\UpdatePiggybank; use FireflyIII\TransactionRules\Actions\UpdatePiggybank;
use FireflyIII\User; use FireflyIII\User;
@ -503,6 +504,7 @@ return [
'convert_withdrawal' => ConvertToWithdrawal::class, 'convert_withdrawal' => ConvertToWithdrawal::class,
'convert_deposit' => ConvertToDeposit::class, 'convert_deposit' => ConvertToDeposit::class,
'convert_transfer' => ConvertToTransfer::class, 'convert_transfer' => ConvertToTransfer::class,
'switch_accounts' => SwitchAccounts::class,
'update_piggy' => UpdatePiggybank::class, 'update_piggy' => UpdatePiggybank::class,
'delete_transaction' => DeleteTransaction::class, 'delete_transaction' => DeleteTransaction::class,
'append_descr_to_notes' => AppendDescriptionToNotes::class, 'append_descr_to_notes' => AppendDescriptionToNotes::class,

View File

@ -162,7 +162,7 @@ function onAddNewAction() {
"use strict"; "use strict";
console.log('Now in onAddNewAction()'); console.log('Now in onAddNewAction()');
var selectQuery = 'select[name^="actions["][name$="][type]"]'; var selectQuery = 'select[name^="actions["][name$="][type]"]';
var selectResult = $(selectQuery); var selectResult = $(selectQuery);
console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length); console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length);
@ -190,7 +190,7 @@ function onAddNewTrigger() {
"use strict"; "use strict";
console.log('Now in onAddNewTrigger()'); console.log('Now in onAddNewTrigger()');
var selectQuery = 'select[name^="triggers["][name$="][type]"]'; var selectQuery = 'select[name^="triggers["][name$="][type]"]';
var selectResult = $(selectQuery); var selectResult = $(selectQuery);
console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length); console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length);
@ -217,9 +217,9 @@ function onAddNewTrigger() {
function updateActionInput(selectList) { function updateActionInput(selectList) {
console.log('Now in updateActionInput() for a select list, currently with value "' + selectList.val() + '".'); 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 inputQuery = 'input[name^="actions["][name$="][value]"]'; var inputQuery = 'input[name^="actions["][name$="][value]"]';
var inputResult = parent.find(inputQuery); var inputResult = parent.find(inputQuery);
console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.'); console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.');
@ -234,6 +234,7 @@ function updateActionInput(selectList) {
case 'clear_budget': case 'clear_budget':
case 'append_descr_to_notes': case 'append_descr_to_notes':
case 'append_notes_to_descr': case 'append_notes_to_descr':
case 'switch_accounts':
case 'move_descr_to_notes': case 'move_descr_to_notes':
case 'move_notes_to_descr': case 'move_notes_to_descr':
case 'clear_notes': case 'clear_notes':
@ -296,9 +297,9 @@ function updateActionInput(selectList) {
function updateTriggerInput(selectList) { function updateTriggerInput(selectList) {
console.log('Now in updateTriggerInput() for a select list, currently with value "' + selectList.val() + '".'); 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 inputQuery = 'input[name^="triggers["][name$="][value]"]'; var inputQuery = 'input[name^="triggers["][name$="][value]"]';
var inputResult = parent.find(inputQuery); var inputResult = parent.find(inputQuery);
console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.'); console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.');
@ -391,50 +392,50 @@ function createAutoComplete(input, URL) {
input.typeahead('destroy'); input.typeahead('destroy');
// append URL: // append URL:
var lastChar = URL[URL.length - 1]; var lastChar = URL[URL.length - 1];
var urlParamSplit = '?'; var urlParamSplit = '?';
if ('&' === lastChar) { if ('&' === lastChar) {
urlParamSplit = ''; urlParamSplit = '';
} }
var source = new Bloodhound({ var source = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'), datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace, queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: { prefetch: {
url: URL + urlParamSplit + 'uid=' + uid, url: URL + urlParamSplit + 'uid=' + uid,
filter: function (list) { filter: function (list) {
return $.map(list, function (item) { return $.map(list, function (item) {
if (item.hasOwnProperty('active') && item.active === true) { if (item.hasOwnProperty('active') && item.active === true) {
return {name: item.name}; return {name: item.name};
} }
if (item.hasOwnProperty('active') && item.active === false) { if (item.hasOwnProperty('active') && item.active === false) {
return; return;
} }
if (item.hasOwnProperty('active')) { if (item.hasOwnProperty('active')) {
console.log(item.active); console.log(item.active);
} }
return {name: item.name}; return {name: item.name};
}); });
} }
}, },
remote: { remote: {
url: URL + urlParamSplit + 'query=%QUERY&uid=' + uid, url: URL + urlParamSplit + 'query=%QUERY&uid=' + uid,
wildcard: '%QUERY', wildcard: '%QUERY',
filter: function (list) { filter: function (list) {
return $.map(list, function (item) { return $.map(list, function (item) {
if (item.hasOwnProperty('active') && item.active === true) { if (item.hasOwnProperty('active') && item.active === true) {
return {name: item.name}; return {name: item.name};
} }
if (item.hasOwnProperty('active') && item.active === false) { if (item.hasOwnProperty('active') && item.active === false) {
return; return;
} }
if (item.hasOwnProperty('active')) { if (item.hasOwnProperty('active')) {
console.log(item.active); console.log(item.active);
} }
return {name: item.name}; return {name: item.name};
}); });
} }
} }
}); });
source.initialize(); source.initialize();
input.typeahead({hint: true, highlight: true,}, {source: source, displayKey: 'name', autoSelect: false}); input.typeahead({hint: true, highlight: true,}, {source: source, displayKey: 'name', autoSelect: false});
} }

View File

@ -904,10 +904,14 @@ return [
'rule_trigger_internal_reference_is' => 'Internal reference is ":trigger_value"', 'rule_trigger_internal_reference_is' => 'Internal reference is ":trigger_value"',
'rule_trigger_journal_id_choice' => 'Transaction journal ID is..', 'rule_trigger_journal_id_choice' => 'Transaction journal ID is..',
'rule_trigger_journal_id' => 'Transaction journal ID is ":trigger_value"', 'rule_trigger_journal_id' => 'Transaction journal ID is ":trigger_value"',
'rule_trigger_no_external_url' => 'Transaction has no external URL', 'rule_trigger_any_external_url' => 'Transaction has an (any) external URL',
'rule_trigger_any_external_url' => 'Transaction has an external URL', 'rule_trigger_any_external_url_choice' => 'Transaction has an (any) external URL',
'rule_trigger_any_external_url_choice' => 'Transaction has an external URL', 'rule_trigger_any_external_id' => 'Transaction has an (any) external ID',
'rule_trigger_any_external_id_choice' => 'Transaction has an (any) external ID',
'rule_trigger_no_external_url_choice' => 'Transaction has no external URL', 'rule_trigger_no_external_url_choice' => 'Transaction has no external URL',
'rule_trigger_no_external_url' => 'Transaction has no external URL',
'rule_trigger_no_external_id_choice' => 'Transaction has no external ID',
'rule_trigger_no_external_id' => 'Transaction has no external ID',
'rule_trigger_id_choice' => 'Transaction ID is..', 'rule_trigger_id_choice' => 'Transaction ID is..',
'rule_trigger_id' => 'Transaction ID is ":trigger_value"', 'rule_trigger_id' => 'Transaction ID is ":trigger_value"',
'rule_trigger_sepa_ct_is_choice' => 'SEPA CT is..', 'rule_trigger_sepa_ct_is_choice' => 'SEPA CT is..',
@ -1178,6 +1182,7 @@ return [
// Ignore this comment // Ignore this comment
// actions // actions
// set, clear, add, remove, append/prepend
'rule_action_delete_transaction_choice' => 'DELETE transaction(!)', 'rule_action_delete_transaction_choice' => 'DELETE transaction(!)',
'rule_action_delete_transaction' => 'DELETE transaction(!)', 'rule_action_delete_transaction' => 'DELETE transaction(!)',
'rule_action_set_category' => 'Set category to ":action_value"', 'rule_action_set_category' => 'Set category to ":action_value"',
@ -1215,6 +1220,8 @@ return [
'rule_action_set_notes_choice' => 'Set notes to ..', 'rule_action_set_notes_choice' => 'Set notes to ..',
'rule_action_link_to_bill_choice' => 'Link to a bill ..', 'rule_action_link_to_bill_choice' => 'Link to a bill ..',
'rule_action_link_to_bill' => 'Link to bill ":action_value"', 'rule_action_link_to_bill' => 'Link to bill ":action_value"',
'rule_action_switch_accounts_choice' => 'Switch source and destination accounts (transfers only!)',
'rule_action_switch_accounts' => 'Switch source and destination ',
'rule_action_set_notes' => 'Set notes to ":action_value"', 'rule_action_set_notes' => 'Set notes to ":action_value"',
'rule_action_convert_deposit_choice' => 'Convert the transaction to a deposit', 'rule_action_convert_deposit_choice' => 'Convert the transaction to a deposit',
'rule_action_convert_deposit' => 'Convert the transaction to a deposit from ":action_value"', 'rule_action_convert_deposit' => 'Convert the transaction to a deposit from ":action_value"',
@ -2603,6 +2610,7 @@ return [
'ale_action_clear_tag' => 'Cleared tag', 'ale_action_clear_tag' => 'Cleared tag',
'ale_action_clear_all_tags' => 'Cleared all tags', 'ale_action_clear_all_tags' => 'Cleared all tags',
'ale_action_set_bill' => 'Linked to bill', 'ale_action_set_bill' => 'Linked to bill',
'ale_action_switch_accounts' => 'Switched source and destination account',
'ale_action_set_budget' => 'Set budget', 'ale_action_set_budget' => 'Set budget',
'ale_action_set_category' => 'Set category', 'ale_action_set_category' => 'Set category',
'ale_action_set_source' => 'Set source account', 'ale_action_set_source' => 'Set source account',