mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	First attempt to fix #7589
This commit is contained in:
		| @@ -352,8 +352,6 @@ trait RecurringTransactionTrait | ||||
|     /** | ||||
|      * @param  RecurrenceTransaction  $transaction | ||||
|      * @param  int  $categoryId | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function setCategory(RecurrenceTransaction $transaction, int $categoryId): void | ||||
|     { | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user