First attempt to fix #7589

This commit is contained in:
James Cole
2023-06-03 21:17:49 +02:00
parent 2e7a17560d
commit 463ebd296f
7 changed files with 249 additions and 121 deletions

View File

@@ -107,13 +107,22 @@ class RecurrenceUpdateService
// update all transactions (and associated meta-data)
if (array_key_exists('transactions', $data)) {
$this->updateTransactions($recurrence, $data['transactions'] ?? []);
// $this->deleteTransactions($recurrence);
// $this->createTransactions($recurrence, $data['transactions'] ?? []);
}
return $recurrence;
}
/**
* @param Recurrence $recurrence
* @param int $transactionId
* @return void
*/
private function deleteTransaction(Recurrence $recurrence, int $transactionId): void
{
Log::debug(sprintf('Will delete transaction #%d in recurrence #%d.', $transactionId, $recurrence->id));
$recurrence->recurrenceTransactions()->where('id', $transactionId)->delete();
}
/**
* @param Recurrence $recurrence
* @param array $data
@@ -146,42 +155,6 @@ class RecurrenceUpdateService
return $query->first();
}
/**
* @param Recurrence $recurrence
* @param array $data
*
* @return RecurrenceTransaction|null
*/
private function matchTransaction(Recurrence $recurrence, array $data): ?RecurrenceTransaction
{
Log::debug('Now in matchTransaction()');
$originalCount = $recurrence->recurrenceTransactions()->count();
if (1 === $originalCount) {
Log::debug('Return the first one.');
/** @var RecurrenceTransaction|null */
return $recurrence->recurrenceTransactions()->first();
}
// find it based on data
$fields = [
'id' => 'id',
'currency_id' => 'transaction_currency_id',
'foreign_currency_id' => 'foreign_currency_id',
'source_id' => 'source_id',
'destination_id' => 'destination_id',
'amount' => 'amount',
'foreign_amount' => 'foreign_amount',
'description' => 'description',
];
$query = $recurrence->recurrenceTransactions();
foreach ($fields as $field => $column) {
if (array_key_exists($field, $data)) {
$query->where($column, $data[$field]);
}
}
/** @var RecurrenceTransaction|null */
return $query->first();
}
/**
* @param Recurrence $recurrence
* @param string $text
@@ -202,6 +175,87 @@ class RecurrenceUpdateService
$dbNote?->delete();
}
/**
* @param Recurrence $recurrence
* @param array $combination
* @return void
*/
private function updateCombination(Recurrence $recurrence, array $combination): void
{
$original = $combination['original'];
$submitted = $combination['submitted'];
/** @var RecurrenceTransaction $transaction */
$transaction = $recurrence->recurrenceTransactions()->find($original['id']);
Log::debug(sprintf('Now in updateCombination(#%d)', $original['id']));
$currencyFactory = app(TransactionCurrencyFactory::class);
// loop all and try to match them:
$currency = null;
$foreignCurrency = null;
if (array_key_exists('currency_id', $submitted) || array_key_exists('currency_code', $submitted)) {
$currency = $currencyFactory->find($submitted['currency_id'] ?? null, $currency['currency_code'] ?? null);
}
if (null === $currency) {
unset($submitted['currency_id'], $submitted['currency_code']);
}
if (null !== $currency) {
$submitted['currency_id'] = (int)$currency->id;
}
if (array_key_exists('foreign_currency_id', $submitted) || array_key_exists('foreign_currency_code', $submitted)) {
$foreignCurrency = $currencyFactory->find($submitted['foreign_currency_id'] ?? null, $currency['foreign_currency_code'] ?? null);
}
if (null === $foreignCurrency) {
unset($submitted['foreign_currency_id'], $currency['foreign_currency_code']);
}
if (null !== $foreignCurrency) {
$submitted['foreign_currency_id'] = (int)$foreignCurrency->id;
}
// update fields that are part of the recurring transaction itself.
$fields = [
'source_id' => 'source_id',
'destination_id' => 'destination_id',
'amount' => 'amount',
'foreign_amount' => 'foreign_amount',
'description' => 'description',
'currency_id' => 'transaction_currency_id',
'foreign_currency_id' => 'foreign_currency_id',
];
foreach ($fields as $field => $column) {
if (array_key_exists($field, $submitted)) {
$transaction->$column = $submitted[$field];
$transaction->save();
}
}
// update meta data
if (array_key_exists('budget_id', $submitted)) {
$this->setBudget($transaction, (int)$submitted['budget_id']);
}
if (array_key_exists('bill_id', $submitted)) {
$this->setBill($transaction, (int)$submitted['bill_id']);
}
// reset category if name is set but empty:
// can be removed when v1 is retired.
if (array_key_exists('category_name', $submitted) && '' === (string)$submitted['category_name']) {
Log::debug('Category name is submitted but is empty. Set category to be empty.');
$submitted['category_name'] = null;
$submitted['category_id'] = 0;
}
if (array_key_exists('category_id', $submitted)) {
Log::debug(sprintf('Category ID is submitted, set category to be %d.', (int)$submitted['category_id']));
$this->setCategory($transaction, (int)$submitted['category_id']);
}
if (array_key_exists('tags', $submitted) && is_array($submitted['tags'])) {
$this->updateTags($transaction, $submitted['tags']);
}
if (array_key_exists('piggy_bank_id', $submitted)) {
$this->updatePiggyBank($transaction, (int)$submitted['piggy_bank_id']);
}
}
/**
*
* @param Recurrence $recurrence
@@ -213,7 +267,7 @@ class RecurrenceUpdateService
{
$originalCount = $recurrence->recurrenceRepetitions()->count();
if (0 === count($repetitions)) {
// wont drop repetition, rather avoid.
// won't drop repetition, rather avoid.
return;
}
// user added or removed repetitions, delete all and recreate:
@@ -251,98 +305,60 @@ class RecurrenceUpdateService
*
* @param Recurrence $recurrence
* @param array $transactions
*
* @throws FireflyException
* @throws JsonException
*/
private function updateTransactions(Recurrence $recurrence, array $transactions): void
{
Log::debug('Now in updateTransactions()');
$originalCount = $recurrence->recurrenceTransactions()->count();
Log::debug(sprintf('Original count is %d', $originalCount));
if (0 === count($transactions)) {
// won't drop transactions, rather avoid.
Log::warning('No transactions to update, too scared to continue!');
return;
}
// user added or removed repetitions, delete all and recreate:
if ($originalCount !== count($transactions)) {
Log::debug('Delete existing transactions and create new ones.');
$this->deleteTransactions($recurrence);
$this->createTransactions($recurrence, $transactions);
return;
}
$currencyFactory = app(TransactionCurrencyFactory::class);
// loop all and try to match them:
Log::debug(sprintf('Count is equal (%d), update transactions.', $originalCount));
foreach ($transactions as $current) {
$match = $this->matchTransaction($recurrence, $current);
if (null === $match) {
throw new FireflyException('Cannot match recurring transaction to existing transaction. Not sure what to do. Break.');
}
// complex loop to find currency:
$currency = null;
$foreignCurrency = null;
if (array_key_exists('currency_id', $current) || array_key_exists('currency_code', $current)) {
$currency = $currencyFactory->find($current['currency_id'] ?? null, $currency['currency_code'] ?? null);
}
if (null === $currency) {
unset($current['currency_id'], $current['currency_code']);
}
if (null !== $currency) {
$current['currency_id'] = (int)$currency->id;
}
if (array_key_exists('foreign_currency_id', $current) || array_key_exists('foreign_currency_code', $current)) {
$foreignCurrency = $currencyFactory->find($current['foreign_currency_id'] ?? null, $currency['foreign_currency_code'] ?? null);
}
if (null === $foreignCurrency) {
unset($current['foreign_currency_id'], $currency['foreign_currency_code']);
}
if (null !== $foreignCurrency) {
$current['foreign_currency_id'] = (int)$foreignCurrency->id;
}
// update fields that are part of the recurring transaction itself.
$fields = [
'source_id' => 'source_id',
'destination_id' => 'destination_id',
'amount' => 'amount',
'foreign_amount' => 'foreign_amount',
'description' => 'description',
'currency_id' => 'transaction_currency_id',
'foreign_currency_id' => 'foreign_currency_id',
];
foreach ($fields as $field => $column) {
if (array_key_exists($field, $current)) {
$match->$column = $current[$field];
$match->save();
$combinations = [];
$originalTransactions = $recurrence->recurrenceTransactions()->get()->toArray();
/**
* First, make sure to loop all existing transactions and match them to a counterpart in the submitted transactions array.
*/
foreach ($originalTransactions as $i => $originalTransaction) {
foreach ($transactions as $ii => $submittedTransaction) {
if (array_key_exists('id', $submittedTransaction) && (int)$originalTransaction['id'] === (int)$submittedTransaction['id']) {
Log::debug(sprintf('Match original transaction #%d with an entry in the submitted array.', $originalTransaction['id']));
$combinations[] = [
'original' => $originalTransaction,
'submitted' => $submittedTransaction,
];
unset($originalTransactions[$i]);
unset($transactions[$ii]);
}
}
// update meta data
if (array_key_exists('budget_id', $current)) {
$this->setBudget($match, (int)$current['budget_id']);
}
if (array_key_exists('bill_id', $current)) {
$this->setBill($match, (int)$current['bill_id']);
}
// reset category if name is set but empty:
// can be removed when v1 is retired.
if (array_key_exists('category_name', $current) && '' === (string)$current['category_name']) {
Log::debug('Category name is submitted but is empty. Set category to be empty.');
$current['category_name'] = null;
$current['category_id'] = 0;
}
if (array_key_exists('category_id', $current)) {
Log::debug(sprintf('Category ID is submitted, set category to be %d.', (int)$current['category_id']));
$this->setCategory($match, (int)$current['category_id']);
}
if (array_key_exists('tags', $current) && is_array($current['tags'])) {
$this->updateTags($match, $current['tags']);
}
if (array_key_exists('piggy_bank_id', $current)) {
$this->updatePiggyBank($match, (int)$current['piggy_bank_id']);
}
/**
* If one left of both we can match those as well and presto.
*/
if (1 === count($originalTransactions) && 1 === count($transactions)) {
$first = array_shift($originalTransactions);
Log::debug(sprintf('One left of each, link them (ID is #%d)', $first['id']));
$combinations[] = [
'original' => $first,
'submitted' => array_shift($transactions),
];
unset($first);
}
// if they are both empty, we can safely loop all combinations and update them.
if (0 === count($originalTransactions) && 0 === count($transactions)) {
foreach ($combinations as $combination) {
$this->updateCombination($recurrence, $combination);
}
}
// anything left in the original transactions array can be deleted.
foreach ($originalTransactions as $original) {
Log::debug(sprintf('Original transaction #%d is unmatched, delete it!', $original['id']));
$this->deleteTransaction($recurrence, (int)$original['id']);
}
// anything left is new.
$this->createTransactions($recurrence, $transactions);
}
}
}