diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 61d2a0fa43..8b3abddfce 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -23,7 +23,6 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use Illuminate\Support\Collection; use Preferences; use Session; @@ -204,7 +203,7 @@ class MassController extends Controller * * @return mixed */ - public function update(MassEditJournalRequest $request, JournalRepositoryInterface $repository, JournalUpdateInterface $updater) + public function update(MassEditJournalRequest $request, JournalRepositoryInterface $repository) { $journalIds = $request->get('journals'); $count = 0; @@ -250,7 +249,7 @@ class MassController extends Controller 'tags' => $tags, ]; // call repository update function. - $updater->update($journal, $data); + $repository->update($journal, $data); $count++; } diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 67e2c7cb19..75bb6a3c8b 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -27,7 +27,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Log; use Preferences; @@ -365,13 +364,13 @@ class SingleController extends Controller } /** - * @param JournalFormRequest $request - * @param JournalUpdateInterface $updater - * @param TransactionJournal $journal + * @param JournalFormRequest $request + * @param JournalRepositoryInterface $repository + * @param TransactionJournal $journal * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(JournalFormRequest $request, JournalUpdateInterface $updater, TransactionJournal $journal) + public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { @@ -380,7 +379,7 @@ class SingleController extends Controller // @codeCoverageIgnoreEnd $data = $request->getJournalData(); - $journal = $updater->update($journal, $data); + $journal = $repository->update($journal, $data); /** @var array $files */ $files = $request->hasFile('attachments') ? $request->file('attachments') : null; $this->attachments->saveAttachmentsForModel($journal, $files); diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index fa899d6bff..e6668bc43e 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -23,8 +23,8 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use Illuminate\Http\Request; use Log; use Preferences; @@ -122,20 +122,20 @@ class SplitController extends Controller /** - * @param Request $request - * @param JournalUpdateInterface $updater - * @param TransactionJournal $journal + * @param Request $request + * @param JournalRepositoryInterface $repository + * @param TransactionJournal $journal * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(Request $request, JournalUpdateInterface $updater, TransactionJournal $journal) + public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } $data = $this->arrayFromInput($request); - $journal = $updater->updateSplitJournal($journal, $data); + $journal = $repository->updateSplitJournal($journal, $data); /** @var array $files */ $files = $request->hasFile('attachments') ? $request->file('attachments') : null; // save attachments: diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php index 7fcabbe606..d221714c21 100644 --- a/app/Providers/JournalServiceProvider.php +++ b/app/Providers/JournalServiceProvider.php @@ -20,8 +20,6 @@ use FireflyIII\Repositories\Journal\JournalRepository; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTasker; use FireflyIII\Repositories\Journal\JournalTaskerInterface; -use FireflyIII\Repositories\Journal\JournalUpdate; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; @@ -52,7 +50,6 @@ class JournalServiceProvider extends ServiceProvider $this->registerRepository(); $this->registerTasker(); $this->registerCollector(); - $this->registerUpdater(); } /** @@ -115,23 +112,4 @@ class JournalServiceProvider extends ServiceProvider ); } - /** - * - */ - private function registerUpdater() - { - $this->app->bind( - JournalUpdateInterface::class, - function (Application $app) { - /** @var JournalUpdateInterface $tasker */ - $update = app(JournalUpdate::class); - - if ($app->auth->check()) { - $update->setUser(auth()->user()); - } - - return $update; - } - ); - } } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index bb9ace5034..c7d6422a50 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -24,8 +24,6 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Support\Collection; use Log; @@ -37,6 +35,7 @@ use Log; */ class AccountRepository implements AccountRepositoryInterface { + use FindAccountsTrait; /** @var User */ private $user; @@ -77,192 +76,6 @@ class AccountRepository implements AccountRepositoryInterface return true; } - /** - * @param $accountId - * - * @return Account - */ - public function find(int $accountId): Account - { - $account = $this->user->accounts()->find($accountId); - if (is_null($account)) { - return new Account; - } - - return $account; - } - - /** - * @param string $number - * @param array $types - * - * @return Account - */ - public function findByAccountNumber(string $number, array $types): Account - { - $query = $this->user->accounts() - ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') - ->where('account_meta.name', 'accountNumber') - ->where('account_meta.data', json_encode($number)); - - if (count($types) > 0) { - $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); - $query->whereIn('account_types.type', $types); - } - - /** @var Collection $accounts */ - $accounts = $query->get(['accounts.*']); - if ($accounts->count() > 0) { - return $accounts->first(); - } - - return new Account; - } - - /** - * @param string $iban - * @param array $types - * - * @return Account - */ - public function findByIban(string $iban, array $types): Account - { - $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban'); - - if (count($types) > 0) { - $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); - $query->whereIn('account_types.type', $types); - } - - $accounts = $query->get(['accounts.*']); - /** @var Account $account */ - foreach ($accounts as $account) { - if ($account->iban === $iban) { - return $account; - } - } - - return new Account; - } - - /** - * @param string $name - * @param array $types - * - * @return Account - */ - public function findByName(string $name, array $types): Account - { - $query = $this->user->accounts(); - - if (count($types) > 0) { - $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); - $query->whereIn('account_types.type', $types); - - } - Log::debug(sprintf('Searching for account named %s of the following type(s)', $name), ['types' => $types]); - - $accounts = $query->get(['accounts.*']); - /** @var Account $account */ - foreach ($accounts as $account) { - if ($account->name === $name) { - Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id)); - - return $account; - } - } - Log::debug('Found nothing.'); - - return new Account; - } - - /** - * @param array $accountIds - * - * @return Collection - */ - public function getAccountsById(array $accountIds): Collection - { - /** @var Collection $result */ - $query = $this->user->accounts(); - - if (count($accountIds) > 0) { - $query->whereIn('accounts.id', $accountIds); - } - - $result = $query->get(['accounts.*']); - $result = $result->sortBy( - function (Account $account) { - return strtolower($account->name); - } - ); - - return $result; - } - - /** - * @param array $types - * - * @return Collection - */ - public function getAccountsByType(array $types): Collection - { - /** @var Collection $result */ - $query = $this->user->accounts(); - if (count($types) > 0) { - $query->accountTypeIn($types); - } - - $result = $query->get(['accounts.*']); - $result = $result->sortBy( - function (Account $account) { - return strtolower($account->name); - } - ); - - return $result; - } - - /** - * @param array $types - * - * @return Collection - */ - public function getActiveAccountsByType(array $types): Collection - { - /** @var Collection $result */ - $query = $this->user->accounts()->with( - ['accountmeta' => function (HasMany $query) { - $query->where('name', 'accountRole'); - }] - ); - if (count($types) > 0) { - $query->accountTypeIn($types); - } - $query->where('active', 1); - $result = $query->get(['accounts.*']); - $result = $result->sortBy( - function (Account $account) { - return strtolower($account->name); - } - ); - - return $result; - } - - /** - * @return Account - */ - public function getCashAccount(): Account - { - $type = AccountType::where('type', AccountType::CASH)->first(); - $account = Account::firstOrCreateEncrypted( - ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1] - ); - - return $account; - } - /** * Returns the date of the very last transaction in this account. * diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php new file mode 100644 index 0000000000..062c7c1b8f --- /dev/null +++ b/app/Repositories/Account/FindAccountsTrait.php @@ -0,0 +1,212 @@ +user->accounts()->find($accountId); + if (is_null($account)) { + return new Account; + } + + return $account; + } + + /** + * @param string $number + * @param array $types + * + * @return Account + */ + public function findByAccountNumber(string $number, array $types): Account + { + $query = $this->user->accounts() + ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') + ->where('account_meta.name', 'accountNumber') + ->where('account_meta.data', json_encode($number)); + + if (count($types) > 0) { + $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); + $query->whereIn('account_types.type', $types); + } + + /** @var Collection $accounts */ + $accounts = $query->get(['accounts.*']); + if ($accounts->count() > 0) { + return $accounts->first(); + } + + return new Account; + } + + /** + * @param string $iban + * @param array $types + * + * @return Account + */ + public function findByIban(string $iban, array $types): Account + { + $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban'); + + if (count($types) > 0) { + $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); + $query->whereIn('account_types.type', $types); + } + + $accounts = $query->get(['accounts.*']); + /** @var Account $account */ + foreach ($accounts as $account) { + if ($account->iban === $iban) { + return $account; + } + } + + return new Account; + } + + /** + * @param string $name + * @param array $types + * + * @return Account + */ + public function findByName(string $name, array $types): Account + { + $query = $this->user->accounts(); + + if (count($types) > 0) { + $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); + $query->whereIn('account_types.type', $types); + + } + Log::debug(sprintf('Searching for account named %s of the following type(s)', $name), ['types' => $types]); + + $accounts = $query->get(['accounts.*']); + /** @var Account $account */ + foreach ($accounts as $account) { + if ($account->name === $name) { + Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id)); + + return $account; + } + } + Log::debug('Found nothing.'); + + return new Account; + } + + /** + * @param array $accountIds + * + * @return Collection + */ + public function getAccountsById(array $accountIds): Collection + { + /** @var Collection $result */ + $query = $this->user->accounts(); + + if (count($accountIds) > 0) { + $query->whereIn('accounts.id', $accountIds); + } + + $result = $query->get(['accounts.*']); + $result = $result->sortBy( + function (Account $account) { + return strtolower($account->name); + } + ); + + return $result; + } + + /** + * @param array $types + * + * @return Collection + */ + public function getAccountsByType(array $types): Collection + { + /** @var Collection $result */ + $query = $this->user->accounts(); + if (count($types) > 0) { + $query->accountTypeIn($types); + } + + $result = $query->get(['accounts.*']); + $result = $result->sortBy( + function (Account $account) { + return strtolower($account->name); + } + ); + + return $result; + } + + /** + * @param array $types + * + * @return Collection + */ + public function getActiveAccountsByType(array $types): Collection + { + /** @var Collection $result */ + $query = $this->user->accounts()->with( + ['accountmeta' => function (HasMany $query) { + $query->where('name', 'accountRole'); + }] + ); + if (count($types) > 0) { + $query->accountTypeIn($types); + } + $query->where('active', 1); + $result = $query->get(['accounts.*']); + $result = $result->sortBy( + function (Account $account) { + return strtolower($account->name); + } + ); + + return $result; + } + + /** + * @return Account + */ + public function getCashAccount(): Account + { + $type = AccountType::where('type', AccountType::CASH)->first(); + $account = Account::firstOrCreateEncrypted( + ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1] + ); + + return $account; + } +} \ No newline at end of file diff --git a/app/Repositories/Journal/CreateJournalsTrait.php b/app/Repositories/Journal/CreateJournalsTrait.php new file mode 100644 index 0000000000..dbfa818e84 --- /dev/null +++ b/app/Repositories/Journal/CreateJournalsTrait.php @@ -0,0 +1,186 @@ + 0) { + $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); + if (!is_null($tag)) { + Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id)); + $tagRepository->connect($journal, $tag); + } + } + } + + return true; + } + + /** + * @param Transaction $transaction + * @param int $budgetId + */ + protected function storeBudgetWithTransaction(Transaction $transaction, int $budgetId) + { + if (intval($budgetId) > 0 && $transaction->transactionJournal->transactionType->type !== TransactionType::TRANSFER) { + /** @var \FireflyIII\Models\Budget $budget */ + $budget = Budget::find($budgetId); + $transaction->budgets()->save($budget); + } + } + + /** + * @param Transaction $transaction + * @param string $category + */ + protected function storeCategoryWithTransaction(Transaction $transaction, string $category) + { + if (strlen($category) > 0) { + $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $transaction->transactionJournal->user_id]); + $transaction->categories()->save($category); + } + } + + /** + * The reference to storeAccounts() in this function is an indication of spagetti code but alas, + * I leave it as it is. + * + * @param TransactionJournal $journal + * @param array $transaction + * @param int $identifier + * + * @return Collection + */ + protected function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection + { + // store source and destination accounts (depends on type) + $accounts = $this->storeAccounts($this->user, $journal->transactionType, $transaction); + + // store transaction one way: + $amount = bcmul(strval($transaction['amount']), '-1'); + $foreignAmount = is_null($transaction['foreign_amount']) ? null : bcmul(strval($transaction['foreign_amount']), '-1'); + $one = $this->storeTransaction( + [ + 'journal' => $journal, + 'account' => $accounts['source'], + 'amount' => $amount, + 'transaction_currency_id' => $transaction['transaction_currency_id'], + 'foreign_amount' => $foreignAmount, + 'foreign_currency_id' => $transaction['foreign_currency_id'], + 'description' => $transaction['description'], + 'category' => null, + 'budget' => null, + 'identifier' => $identifier, + ] + ); + $this->storeCategoryWithTransaction($one, $transaction['category']); + $this->storeBudgetWithTransaction($one, $transaction['budget_id']); + + // and the other way: + $amount = strval($transaction['amount']); + $foreignAmount = is_null($transaction['foreign_amount']) ? null : strval($transaction['foreign_amount']); + $two = $this->storeTransaction( + [ + 'journal' => $journal, + 'account' => $accounts['destination'], + 'amount' => $amount, + 'transaction_currency_id' => $transaction['transaction_currency_id'], + 'foreign_amount' => $foreignAmount, + 'foreign_currency_id' => $transaction['foreign_currency_id'], + 'description' => $transaction['description'], + 'category' => null, + 'budget' => null, + 'identifier' => $identifier, + ] + ); + $this->storeCategoryWithTransaction($two, $transaction['category']); + $this->storeBudgetWithTransaction($two, $transaction['budget_id']); + + return new Collection([$one, $two]); + } + + /** + * @param array $data + * + * @return Transaction + */ + protected function storeTransaction(array $data): Transaction + { + $fields = [ + 'transaction_journal_id' => $data['journal']->id, + 'account_id' => $data['account']->id, + 'amount' => $data['amount'], + 'foreign_amount' => $data['foreign_amount'], + 'transaction_currency_id' => $data['transaction_currency_id'], + 'foreign_currency_id' => $data['foreign_currency_id'], + 'description' => $data['description'], + 'identifier' => $data['identifier'], + ]; + + + if (is_null($data['foreign_currency_id'])) { + unset($fields['foreign_currency_id']); + } + if (is_null($data['foreign_amount'])) { + unset($fields['foreign_amount']); + } + + /** @var Transaction $transaction */ + $transaction = Transaction::create($fields); + + Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id)); + + if (!is_null($data['category'])) { + $transaction->categories()->save($data['category']); + } + + if (!is_null($data['budget'])) { + $transaction->categories()->save($data['budget']); + } + + return $transaction; + + } + +} \ No newline at end of file diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index d8402430a1..774ae77947 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -14,10 +14,8 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; use FireflyIII\Models\Account; -use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\User; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; @@ -31,6 +29,8 @@ use Preferences; */ class JournalRepository implements JournalRepositoryInterface { + use CreateJournalsTrait, UpdateJournalsTrait, SupportJournalsTrait; + /** @var User */ private $user; /** @var array */ @@ -170,8 +170,8 @@ class JournalRepository implements JournalRepositoryInterface // find transaction type. /** @var TransactionType $transactionType */ $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); - $accounts = JournalSupport::storeAccounts($this->user, $transactionType, $data); - $data = JournalSupport::verifyNativeAmount($data, $accounts); + $accounts = $this->storeAccounts($this->user, $transactionType, $data); + $data = $this->verifyNativeAmount($data, $accounts); $amount = strval($data['amount']); $journal = new TransactionJournal( [ @@ -186,8 +186,8 @@ class JournalRepository implements JournalRepositoryInterface $journal->save(); // store stuff: - JournalSupport::storeCategoryWithJournal($journal, $data['category']); - JournalSupport::storeBudgetWithJournal($journal, $data['budget_id']); + $this->storeCategoryWithJournal($journal, $data['category']); + $this->storeBudgetWithJournal($journal, $data['budget_id']); // store two transactions: $one = [ @@ -202,7 +202,7 @@ class JournalRepository implements JournalRepositoryInterface 'budget' => null, 'identifier' => 0, ]; - JournalSupport::storeTransaction($one); + $this->storeTransaction($one); $two = [ 'journal' => $journal, @@ -217,7 +217,7 @@ class JournalRepository implements JournalRepositoryInterface 'identifier' => 0, ]; - JournalSupport::storeTransaction($two); + $this->storeTransaction($two); // store tags @@ -241,30 +241,112 @@ class JournalRepository implements JournalRepositoryInterface } /** + * @param TransactionJournal $journal + * @param array $data * - * * Remember: a balancingAct takes at most one expense and one transfer. - * an advancePayment takes at most one expense, infinite deposits and NO transfers. + * @return TransactionJournal + */ + public function update(TransactionJournal $journal, array $data): TransactionJournal + { + + // update actual journal: + $journal->description = $data['description']; + $journal->date = $data['date']; + $accounts = $this->storeAccounts($this->user, $journal->transactionType, $data); + $data = $this->verifyNativeAmount($data, $accounts); + $data['amount'] = strval($data['amount']); + $data['foreign_amount'] = is_null($data['foreign_amount']) ? null : strval($data['foreign_amount']); + + // unlink all categories, recreate them: + $journal->categories()->detach(); + $journal->budgets()->detach(); + + $this->storeCategoryWithJournal($journal, $data['category']); + $this->storeBudgetWithJournal($journal, $data['budget_id']); + + // negative because source loses money. + $this->updateSourceTransaction($journal, $accounts['source'], $data); + + // positive because destination gets money. + $this->updateDestinationTransaction($journal, $accounts['destination'], $data); + + $journal->save(); + + // update tags: + if (isset($data['tags']) && is_array($data['tags'])) { + $this->updateTags($journal, $data['tags']); + } + + // update meta fields: + $result = $journal->save(); + if ($result) { + foreach ($data as $key => $value) { + if (in_array($key, $this->validMetaFields)) { + $journal->setMeta($key, $value); + continue; + } + Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id)); + } + + return $journal; + } + + return $journal; + } + + /** + * Same as above but for transaction journal with multiple transactions. * * @param TransactionJournal $journal - * @param array $array + * @param array $data * - * @return bool + * @return TransactionJournal */ - private function saveTags(TransactionJournal $journal, array $array): bool + public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal { - /** @var TagRepositoryInterface $tagRepository */ - $tagRepository = app(TagRepositoryInterface::class); + // update actual journal: + $journal->description = $data['journal_description']; + $journal->date = $data['date']; + $journal->save(); + Log::debug(sprintf('Updated split journal #%d', $journal->id)); - foreach ($array as $name) { - if (strlen(trim($name)) > 0) { - $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); - if (!is_null($tag)) { - Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id)); - $tagRepository->connect($journal, $tag); + // unlink all categories: + $journal->categories()->detach(); + $journal->budgets()->detach(); + + // update meta fields: + $result = $journal->save(); + if ($result) { + foreach ($data as $key => $value) { + if (in_array($key, $this->validMetaFields)) { + $journal->setMeta($key, $value); + continue; } + Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id)); } } - return true; + // update tags: + if (isset($data['tags']) && is_array($data['tags'])) { + $this->updateTags($journal, $data['tags']); + } + + // delete original transactions, and recreate them. + $journal->transactions()->delete(); + + // store each transaction. + $identifier = 0; + Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions']))); + + foreach ($data['transactions'] as $transaction) { + Log::debug(sprintf('Split journal update split transaction %d', $identifier)); + $transaction = $this->appendTransactionData($transaction, $data); + $this->storeSplitTransaction($journal, $transaction, $identifier); + $identifier++; + } + + $journal->save(); + + return $journal; } } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index a25ee4fa23..028f2d52d7 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -95,4 +95,20 @@ interface JournalRepositoryInterface */ public function store(array $data): TransactionJournal; + /** + * @param TransactionJournal $journal + * @param array $data + * + * @return TransactionJournal + */ + public function update(TransactionJournal $journal, array $data): TransactionJournal; + + /** + * @param TransactionJournal $journal + * @param array $data + * + * @return TransactionJournal + */ + public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal; + } diff --git a/app/Repositories/Journal/JournalUpdate.php b/app/Repositories/Journal/JournalUpdate.php deleted file mode 100644 index ff438369a2..0000000000 --- a/app/Repositories/Journal/JournalUpdate.php +++ /dev/null @@ -1,290 +0,0 @@ -user = $user; - } - - /** - * @param TransactionJournal $journal - * @param array $data - * - * @return TransactionJournal - */ - public function update(TransactionJournal $journal, array $data): TransactionJournal - { - - // update actual journal: - $journal->description = $data['description']; - $journal->date = $data['date']; - $accounts = JournalSupport::storeAccounts($this->user, $journal->transactionType, $data); - $data = JournalSupport::verifyNativeAmount($data, $accounts); - $data['amount'] = strval($data['amount']); - $data['foreign_amount'] = is_null($data['foreign_amount']) ? null : strval($data['foreign_amount']); - - // unlink all categories, recreate them: - $journal->categories()->detach(); - $journal->budgets()->detach(); - - JournalSupport::storeCategoryWithJournal($journal, $data['category']); - JournalSupport::storeBudgetWithJournal($journal, $data['budget_id']); - - // negative because source loses money. - $this->updateSourceTransaction($journal, $accounts['source'], $data); - - // positive because destination gets money. - $this->updateDestinationTransaction($journal, $accounts['destination'], $data); - - $journal->save(); - - // update tags: - if (isset($data['tags']) && is_array($data['tags'])) { - JournalSupport::updateTags($journal, $data['tags']); - } - - // update meta fields: - $result = $journal->save(); - if ($result) { - foreach ($data as $key => $value) { - if (in_array($key, $this->validMetaFields)) { - $journal->setMeta($key, $value); - continue; - } - Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id)); - } - - return $journal; - } - - return $journal; - } - - /** - * Same as above but for transaction journal with multiple transactions. - * - * @param TransactionJournal $journal - * @param array $data - * - * @return TransactionJournal - */ - public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal - { - // update actual journal: - $journal->description = $data['journal_description']; - $journal->date = $data['date']; - $journal->save(); - Log::debug(sprintf('Updated split journal #%d', $journal->id)); - - // unlink all categories: - $journal->categories()->detach(); - $journal->budgets()->detach(); - - // update meta fields: - $result = $journal->save(); - if ($result) { - foreach ($data as $key => $value) { - if (in_array($key, $this->validMetaFields)) { - $journal->setMeta($key, $value); - continue; - } - Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id)); - } - } - - - // update tags: - if (isset($data['tags']) && is_array($data['tags'])) { - JournalSupport::updateTags($journal, $data['tags']); - } - - // delete original transactions, and recreate them. - $journal->transactions()->delete(); - - // store each transaction. - $identifier = 0; - Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions']))); - - foreach ($data['transactions'] as $transaction) { - Log::debug(sprintf('Split journal update split transaction %d', $identifier)); - $transaction = $this->appendTransactionData($transaction, $data); - $this->storeSplitTransaction($journal, $transaction, $identifier); - $identifier++; - } - - $journal->save(); - - return $journal; - } - - /** - * When the user edits a split journal, each line is missing crucial data: - * - * - Withdrawal lines are missing the source account ID - * - Deposit lines are missing the destination account ID - * - Transfers are missing both. - * - * We need to append the array. - * - * @param array $transaction - * @param array $data - * - * @return array - */ - private function appendTransactionData(array $transaction, array $data): array - { - switch ($data['what']) { - case strtolower(TransactionType::TRANSFER): - case strtolower(TransactionType::WITHDRAWAL): - $transaction['source_account_id'] = intval($data['journal_source_account_id']); - break; - } - - switch ($data['what']) { - case strtolower(TransactionType::TRANSFER): - case strtolower(TransactionType::DEPOSIT): - $transaction['destination_account_id'] = intval($data['journal_destination_account_id']); - break; - } - - return $transaction; - } - - /** - * @param TransactionJournal $journal - * @param array $transaction - * @param int $identifier - * - * @return Collection - */ - private function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection - { - // store source and destination accounts (depends on type) - $accounts = JournalSupport::storeAccounts($this->user, $journal->transactionType, $transaction); - - // store transaction one way: - $amount = bcmul(strval($transaction['amount']), '-1'); - $foreignAmount = is_null($transaction['foreign_amount']) ? null : bcmul(strval($transaction['foreign_amount']), '-1'); - $one = JournalSupport::storeTransaction( - [ - 'journal' => $journal, - 'account' => $accounts['source'], - 'amount' => $amount, - 'transaction_currency_id' => $transaction['transaction_currency_id'], - 'foreign_amount' => $foreignAmount, - 'foreign_currency_id' => $transaction['foreign_currency_id'], - 'description' => $transaction['description'], - 'category' => null, - 'budget' => null, - 'identifier' => $identifier, - ] - ); - JournalSupport::storeCategoryWithTransaction($one, $transaction['category']); - JournalSupport::storeBudgetWithTransaction($one, $transaction['budget_id']); - - // and the other way: - $amount = strval($transaction['amount']); - $foreignAmount = is_null($transaction['foreign_amount']) ? null : strval($transaction['foreign_amount']); - $two = JournalSupport::storeTransaction( - [ - 'journal' => $journal, - 'account' => $accounts['destination'], - 'amount' => $amount, - 'transaction_currency_id' => $transaction['transaction_currency_id'], - 'foreign_amount' => $foreignAmount, - 'foreign_currency_id' => $transaction['foreign_currency_id'], - 'description' => $transaction['description'], - 'category' => null, - 'budget' => null, - 'identifier' => $identifier, - ] - ); - JournalSupport::storeCategoryWithTransaction($two, $transaction['category']); - JournalSupport::storeBudgetWithTransaction($two, $transaction['budget_id']); - - return new Collection([$one, $two]); - } - - /** - * @param TransactionJournal $journal - * @param Account $account - * @param array $data - * - * @throws FireflyException - */ - private function updateDestinationTransaction(TransactionJournal $journal, Account $account, array $data) - { - $set = $journal->transactions()->where('amount', '>', 0)->get(); - if ($set->count() != 1) { - throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count())); - } - /** @var Transaction $transaction */ - $transaction = $set->first(); - $transaction->amount = app('steam')->positive($data['amount']); - $transaction->transaction_currency_id = $data['currency_id']; - $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : app('steam')->positive($data['foreign_amount']); - $transaction->foreign_currency_id = $data['foreign_currency_id']; - $transaction->account_id = $account->id; - $transaction->save(); - - } - - /** - * @param TransactionJournal $journal - * @param Account $account - * @param array $data - * - * @throws FireflyException - */ - private function updateSourceTransaction(TransactionJournal $journal, Account $account, array $data) - { - // should be one: - $set = $journal->transactions()->where('amount', '<', 0)->get(); - if ($set->count() != 1) { - throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count())); - } - /** @var Transaction $transaction */ - $transaction = $set->first(); - $transaction->amount = bcmul(app('steam')->positive($data['amount']), '-1'); - $transaction->transaction_currency_id = $data['currency_id']; - $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : bcmul(app('steam')->positive($data['foreign_amount']), '-1'); - $transaction->foreign_currency_id = $data['foreign_currency_id']; - $transaction->account_id = $account->id; - $transaction->save(); - } - -} \ No newline at end of file diff --git a/app/Repositories/Journal/JournalUpdateInterface.php b/app/Repositories/Journal/JournalUpdateInterface.php deleted file mode 100644 index 21f6f80c85..0000000000 --- a/app/Repositories/Journal/JournalUpdateInterface.php +++ /dev/null @@ -1,44 +0,0 @@ - null, @@ -50,11 +46,11 @@ class JournalSupport Log::debug(sprintf('Going to store accounts for type %s', $type->type)); switch ($type->type) { case TransactionType::WITHDRAWAL: - $accounts = self::storeWithdrawalAccounts($user, $data); + $accounts = $this->storeWithdrawalAccounts($user, $data); break; case TransactionType::DEPOSIT: - $accounts = self::storeDepositAccounts($user, $data); + $accounts = $this->storeDepositAccounts($user, $data); break; case TransactionType::TRANSFER: @@ -84,7 +80,7 @@ class JournalSupport * @param TransactionJournal $journal * @param int $budgetId */ - public static function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId) + protected function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId) { if (intval($budgetId) > 0 && $journal->transactionType->type === TransactionType::WITHDRAWAL) { /** @var \FireflyIII\Models\Budget $budget */ @@ -93,24 +89,11 @@ class JournalSupport } } - /** - * @param Transaction $transaction - * @param int $budgetId - */ - public static function storeBudgetWithTransaction(Transaction $transaction, int $budgetId) - { - if (intval($budgetId) > 0 && $transaction->transactionJournal->transactionType->type !== TransactionType::TRANSFER) { - /** @var \FireflyIII\Models\Budget $budget */ - $budget = Budget::find($budgetId); - $transaction->budgets()->save($budget); - } - } - /** * @param TransactionJournal $journal * @param string $category */ - public static function storeCategoryWithJournal(TransactionJournal $journal, string $category) + protected function storeCategoryWithJournal(TransactionJournal $journal, string $category) { if (strlen($category) > 0) { $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]); @@ -118,25 +101,13 @@ class JournalSupport } } - /** - * @param Transaction $transaction - * @param string $category - */ - public static function storeCategoryWithTransaction(Transaction $transaction, string $category) - { - if (strlen($category) > 0) { - $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $transaction->transactionJournal->user_id]); - $transaction->categories()->save($category); - } - } - /** * @param User $user * @param array $data * * @return array */ - public static function storeDepositAccounts(User $user, array $data): array + protected function storeDepositAccounts(User $user, array $data): array { Log::debug('Now in storeDepositAccounts().'); $destinationAccount = Account::where('user_id', $user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']); @@ -170,56 +141,13 @@ class JournalSupport ]; } - /** - * @param array $data - * - * @return Transaction - */ - public static function storeTransaction(array $data): Transaction - { - $fields = [ - 'transaction_journal_id' => $data['journal']->id, - 'account_id' => $data['account']->id, - 'amount' => $data['amount'], - 'foreign_amount' => $data['foreign_amount'], - 'transaction_currency_id' => $data['transaction_currency_id'], - 'foreign_currency_id' => $data['foreign_currency_id'], - 'description' => $data['description'], - 'identifier' => $data['identifier'], - ]; - - - if (is_null($data['foreign_currency_id'])) { - unset($fields['foreign_currency_id']); - } - if (is_null($data['foreign_amount'])) { - unset($fields['foreign_amount']); - } - - /** @var Transaction $transaction */ - $transaction = Transaction::create($fields); - - Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id)); - - if (!is_null($data['category'])) { - $transaction->categories()->save($data['category']); - } - - if (!is_null($data['budget'])) { - $transaction->categories()->save($data['budget']); - } - - return $transaction; - - } - /** * @param User $user * @param array $data * * @return array */ - public static function storeWithdrawalAccounts(User $user, array $data): array + protected function storeWithdrawalAccounts(User $user, array $data): array { Log::debug('Now in storeWithdrawalAccounts().'); $sourceAccount = Account::where('user_id', $user->id)->where('id', $data['source_account_id'])->first(['accounts.*']); @@ -256,49 +184,6 @@ class JournalSupport ]; } - /** - * @param TransactionJournal $journal - * @param array $array - * - * @return bool - */ - public static function updateTags(TransactionJournal $journal, array $array): bool - { - // create tag repository - /** @var TagRepositoryInterface $tagRepository */ - $tagRepository = app(TagRepositoryInterface::class); - - - // find or create all tags: - $tags = []; - $ids = []; - foreach ($array as $name) { - if (strlen(trim($name)) > 0) { - $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); - $tags[] = $tag; - $ids[] = $tag->id; - } - } - - // delete all tags connected to journal not in this array: - if (count($ids) > 0) { - DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete(); - } - // if count is zero, delete them all: - if (count($ids) == 0) { - DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete(); - } - - // connect each tag to journal (if not yet connected): - /** @var Tag $tag */ - foreach ($tags as $tag) { - Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id)); - $tagRepository->connect($journal, $tag); - } - - return true; - } - /** * This method checks the data array and the given accounts to verify that the native amount, currency * and possible the foreign currency and amount are properly saved. @@ -309,7 +194,7 @@ class JournalSupport * @return array * @throws FireflyException */ - public static function verifyNativeAmount(array $data, array $accounts): array + protected function verifyNativeAmount(array $data, array $accounts): array { /** @var TransactionType $transactionType */ $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); diff --git a/app/Repositories/Journal/UpdateJournalsTrait.php b/app/Repositories/Journal/UpdateJournalsTrait.php new file mode 100644 index 0000000000..8e249d5bff --- /dev/null +++ b/app/Repositories/Journal/UpdateJournalsTrait.php @@ -0,0 +1,156 @@ +transactions()->where('amount', '>', 0)->get(); + if ($set->count() != 1) { + throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count())); + } + /** @var Transaction $transaction */ + $transaction = $set->first(); + $transaction->amount = app('steam')->positive($data['amount']); + $transaction->transaction_currency_id = $data['currency_id']; + $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : app('steam')->positive($data['foreign_amount']); + $transaction->foreign_currency_id = $data['foreign_currency_id']; + $transaction->account_id = $account->id; + $transaction->save(); + + } + + /** + * @param TransactionJournal $journal + * @param Account $account + * @param array $data + * + * @throws FireflyException + */ + protected function updateSourceTransaction(TransactionJournal $journal, Account $account, array $data) + { + // should be one: + $set = $journal->transactions()->where('amount', '<', 0)->get(); + if ($set->count() != 1) { + throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count())); + } + /** @var Transaction $transaction */ + $transaction = $set->first(); + $transaction->amount = bcmul(app('steam')->positive($data['amount']), '-1'); + $transaction->transaction_currency_id = $data['currency_id']; + $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : bcmul(app('steam')->positive($data['foreign_amount']), '-1'); + $transaction->foreign_currency_id = $data['foreign_currency_id']; + $transaction->account_id = $account->id; + $transaction->save(); + } + + /** + * @param TransactionJournal $journal + * @param array $array + * + * @return bool + */ + protected function updateTags(TransactionJournal $journal, array $array): bool + { + // create tag repository + /** @var TagRepositoryInterface $tagRepository */ + $tagRepository = app(TagRepositoryInterface::class); + + + // find or create all tags: + $tags = []; + $ids = []; + foreach ($array as $name) { + if (strlen(trim($name)) > 0) { + $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); + $tags[] = $tag; + $ids[] = $tag->id; + } + } + + // delete all tags connected to journal not in this array: + if (count($ids) > 0) { + DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete(); + } + // if count is zero, delete them all: + if (count($ids) == 0) { + DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete(); + } + + // connect each tag to journal (if not yet connected): + /** @var Tag $tag */ + foreach ($tags as $tag) { + Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id)); + $tagRepository->connect($journal, $tag); + } + + return true; + } +} \ No newline at end of file