From bc33d1b67dd4a446de9fadbf953813e2bc58f27e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 16 Jun 2019 13:16:04 +0200 Subject: [PATCH] Renamed various fields from their old camel casing to new ones. --- .../Controllers/Account/EditController.php | 17 +- app/Http/Requests/AccountFormRequest.php | 81 ++- .../Account/AccountRepository.php | 16 + .../Account/AccountRepositoryInterface.php | 7 + .../Journal/JournalRepository.php | 15 + .../Journal/JournalRepositoryInterface.php | 8 + .../PiggyBank/PiggyBankRepository.php | 40 +- .../PiggyBankRepositoryInterface.php | 3 +- .../Internal/Support/AccountServiceTrait.php | 590 ++++++++++-------- .../Internal/Support/JournalServiceTrait.php | 5 +- .../Support/RecurringTransactionTrait.php | 126 ++-- .../Support/TransactionServiceTrait.php | 242 +++---- .../Internal/Update/AccountUpdateService.php | 6 +- .../Update/TransactionUpdateService.php | 237 +++---- app/Support/Http/Controllers/CreateStuff.php | 68 +- .../Http/Controllers/ModelInformation.php | 8 +- .../Import/Routine/Bunq/PaymentConverter.php | 4 +- .../Routine/File/OpposingAccountMapper.php | 2 +- .../Actions/SetDestinationAccount.php | 4 +- .../Actions/SetSourceAccount.php | 4 +- app/Transformers/RecurrenceTransformer.php | 36 +- app/Validation/AccountValidator.php | 114 ++++ 22 files changed, 979 insertions(+), 654 deletions(-) diff --git a/app/Http/Controllers/Account/EditController.php b/app/Http/Controllers/Account/EditController.php index b714381a38..a011dd553c 100644 --- a/app/Http/Controllers/Account/EditController.php +++ b/app/Http/Controllers/Account/EditController.php @@ -126,15 +126,15 @@ class EditController extends Controller // code to handle active-checkboxes $hasOldInput = null !== $request->old('_token'); $preFilled = [ - 'accountNumber' => $repository->getMetaValue($account, 'accountNumber'), - 'accountRole' => $repository->getMetaValue($account, 'accountRole'), - 'ccType' => $repository->getMetaValue($account, 'ccType'), - 'ccMonthlyPaymentDate' => $repository->getMetaValue($account, 'ccMonthlyPaymentDate'), + 'account_number' => $repository->getMetaValue($account, 'accountNumber'), + 'account_role' => $repository->getMetaValue($account, 'accountRole'), + 'cc_type' => $repository->getMetaValue($account, 'ccType'), + 'cc_monthly_payment_date' => $repository->getMetaValue($account, 'ccMonthlyPaymentDate'), 'BIC' => $repository->getMetaValue($account, 'BIC'), - 'openingBalanceDate' => $openingBalanceDate, + 'opening_balance_date' => $openingBalanceDate, 'liability_type_id' => $account->account_type_id, - 'openingBalance' => $openingBalanceAmount, - 'virtualBalance' => $account->virtual_balance, + 'opening_balance' => $openingBalanceAmount, + 'virtual_balance' => $account->virtual_balance, 'currency_id' => $currency->id, 'include_net_worth' => $includeNetWorth, 'interest' => $repository->getMetaValue($account, 'interest'), @@ -142,9 +142,6 @@ class EditController extends Controller 'notes' => $this->repository->getNoteText($account), 'active' => $hasOldInput ? (bool)$request->old('active') : $account->active, ]; - if ('liabilities' === $what) { - $preFilled['openingBalance'] = bcmul($preFilled['openingBalance'], '-1'); - } $request->session()->flash('preFilled', $preFilled); diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index 33d3310334..3212c2c58f 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -50,24 +50,24 @@ class AccountFormRequest extends Request public function getAccountData(): array { $data = [ - 'name' => $this->string('name'), - 'active' => $this->boolean('active'), - 'accountType' => $this->string('what'), - 'account_type_id' => 0, - 'currency_id' => $this->integer('currency_id'), - 'virtualBalance' => $this->string('virtualBalance'), - 'iban' => $this->string('iban'), - 'BIC' => $this->string('BIC'), - 'accountNumber' => $this->string('accountNumber'), - 'accountRole' => $this->string('accountRole'), - 'openingBalance' => $this->string('openingBalance'), - 'openingBalanceDate' => $this->date('openingBalanceDate'), - 'ccType' => $this->string('ccType'), - 'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'), - 'notes' => $this->string('notes'), - 'interest' => $this->string('interest'), - 'interest_period' => $this->string('interest_period'), - 'include_net_worth' => '1', + 'name' => $this->string('name'), + 'active' => $this->boolean('active'), + 'account_type' => $this->string('what'), + 'account_type_id' => 0, + 'currency_id' => $this->integer('currency_id'), + 'virtual_balance' => $this->string('virtualBalance'), + 'iban' => $this->string('iban'), + 'BIC' => $this->string('BIC'), + 'account_number' => $this->string('accountNumber'), + 'account_role' => $this->string('accountRole'), + 'opening_balance' => $this->string('openingBalance'), + 'opening_balance_date' => $this->date('openingBalanceDate'), + 'cc_type' => $this->string('ccType'), + 'cc_monthly_payment_date' => $this->string('ccMonthlyPaymentDate'), + 'notes' => $this->string('notes'), + 'interest' => $this->string('interest'), + 'interest_period' => $this->string('interest_period'), + 'include_net_worth' => '1', ]; if (false === $this->boolean('include_net_worth')) { $data['include_net_worth'] = '0'; @@ -75,13 +75,9 @@ class AccountFormRequest extends Request // if the account type is "liabilities" there are actually four types of liability // that could have been selected. - if ('liabilities' === $data['accountType']) { - $data['accountType'] = null; + if ('liabilities' === $data['account_type']) { + $data['account_type'] = null; $data['account_type_id'] = $this->integer('liability_type_id'); - // also reverse the opening balance: - if ('' !== $data['openingBalance']) { - $data['openingBalance'] = bcmul($data['openingBalance'], '-1'); - } } return $data; @@ -98,27 +94,28 @@ class AccountFormRequest extends Request $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); $rules = [ - 'name' => 'required|min:1|uniqueAccountForUser', - 'openingBalance' => 'numeric|required_with:openingBalanceDate|nullable', - 'openingBalanceDate' => 'date|required_with:openingBalance|nullable', - 'iban' => ['iban', 'nullable', new UniqueIban(null, $this->string('what'))], - 'BIC' => 'bic|nullable', - 'virtualBalance' => 'numeric|nullable', - 'currency_id' => 'exists:transaction_currencies,id', - 'accountNumber' => 'between:1,255|uniqueAccountNumberForUser|nullable', - 'accountRole' => 'in:' . $accountRoles, - 'active' => 'boolean', - 'ccType' => 'in:' . $ccPaymentTypes, - 'ccMonthlyPaymentDate' => 'date', - 'amount_currency_id_openingBalance' => 'exists:transaction_currencies,id', - 'amount_currency_id_virtualBalance' => 'exists:transaction_currencies,id', - 'what' => 'in:' . $types, - 'interest_period' => 'in:daily,monthly,yearly', + 'name' => 'required|min:1|uniqueAccountForUser', + 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', + 'opening_balance_date' => 'date|required_with:opening_balance|nullable', + 'iban' => ['iban', 'nullable', new UniqueIban(null, $this->string('what'))], + 'BIC' => 'bic|nullable', + 'virtual_balance' => 'numeric|nullable', + 'currency_id' => 'exists:transaction_currencies,id', + 'account_number' => 'between:1,255|uniqueAccountNumberForUser|nullable', + 'account_role' => 'in:' . $accountRoles, + 'active' => 'boolean', + 'ccType' => 'in:' . $ccPaymentTypes, + 'cc_monthly_payment_date' => 'date', + 'amount_currency_id_opening_balance' => 'exists:transaction_currencies,id', + 'amount_currency_id_virtua_balance' => 'exists:transaction_currencies,id', + 'what' => 'in:' . $types, + 'interest_period' => 'in:daily,monthly,yearly', ]; + // TODO verify if this will work. if ('liabilities' === $this->get('what')) { - $rules['openingBalance'] = ['numeric', 'required', new ZeroOrMore]; - $rules['openingBalanceDate'] = 'date|required'; + $rules['opening_balance'] = ['numeric', 'required', new ZeroOrMore]; + $rules['opening_balance_date'] = 'date|required'; } /** @var Account $account */ diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 485afa99fe..05fa87310b 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -28,6 +28,7 @@ use FireflyIII\Factory\AccountFactory; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Services\Internal\Destroy\AccountDestroyService; @@ -619,4 +620,19 @@ class AccountRepository implements AccountRepositoryInterface ->transactionTypes([TransactionType::OPENING_BALANCE]) ->first(['transaction_journals.*']); } + + /** + * @param Account $account + * @return TransactionGroup|null + */ + public function getOpeningBalanceGroup(Account $account): ?TransactionGroup + { + $journal = $this->getOpeningBalance($account); + $group = null; + if (null !== $journal) { + $group = $journal->transactionGroup; + } + + return $group; + } } diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index de8ddf7125..b45aefbc9a 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -26,6 +26,7 @@ use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Support\Collection; @@ -43,6 +44,12 @@ interface AccountRepositoryInterface */ public function getOpeningBalance(Account $account): ?TransactionJournal; + /** + * @param Account $account + * @return TransactionGroup|null + */ + public function getOpeningBalanceGroup(Account $account): ?TransactionGroup; + /** * Moved here from account CRUD. * diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 12986dbf0f..c16a27a98c 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -813,4 +813,19 @@ class JournalRepository implements JournalRepositoryInterface ->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account']) ->get(['transaction_journals.*']); } + + /** + * Get all transaction journals with a specific type, for the logged in user. + * + * @param array $types + * @return Collection + */ + public function getJournals(array $types): Collection + { + return $this->user->transactionJournals() + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->whereIn('transaction_types.type', $types) + ->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account']) + ->get(['transaction_journals.*']); + } } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 44ab15720f..e0671daa1c 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -49,6 +49,14 @@ interface JournalRepositoryInterface */ public function getAllJournals(array $types): Collection; + /** + * Get all transaction journals with a specific type, for the logged in user. + * + * @param array $types + * @return Collection + */ + public function getJournals(array $types): Collection; + /** * @param TransactionJournal $journal * @param TransactionType $type diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 90a756d471..b696059547 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -57,7 +57,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -79,7 +79,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBankRepetition $repetition - * @param string $amount + * @param string $amount * * @return string */ @@ -94,7 +94,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -110,7 +110,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -143,7 +143,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return PiggyBankEvent */ @@ -156,8 +156,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface } /** - * @param PiggyBank $piggyBank - * @param string $amount + * @param PiggyBank $piggyBank + * @param string $amount * @param TransactionJournal $journal * * @return PiggyBankEvent @@ -225,20 +225,14 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface } /** - * @param PiggyBank|null $piggyBank - * @param int|null $piggyBankId - * @param string|null $piggyBankName + * @param int|null $piggyBankId + * @param string|null $piggyBankName * * @return PiggyBank|null */ - public function findPiggyBank(?PiggyBank $piggyBank, ?int $piggyBankId, ?string $piggyBankName): ?PiggyBank + public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank { Log::debug('Searching for piggy information.'); - if ($piggyBank instanceof PiggyBank && $piggyBank->account->user_id === $this->user->id) { - Log::debug(sprintf('Piggy object in parameters, will return Piggy #%d', $piggyBank->id)); - - return $piggyBank; - } if (null !== $piggyBankId) { $searchResult = $this->findNull((int)$piggyBankId); @@ -291,9 +285,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * Used for connecting to a piggy bank. * - * @param PiggyBank $piggyBank + * @param PiggyBank $piggyBank * @param PiggyBankRepetition $repetition - * @param TransactionJournal $journal + * @param TransactionJournal $journal * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -466,7 +460,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface * Get for piggy account what is left to put in piggies. * * @param PiggyBank $piggyBank - * @param Carbon $date + * @param Carbon $date * * @return string */ @@ -491,7 +485,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -511,7 +505,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface * set id of piggy bank. * * @param PiggyBank $piggyBank - * @param int $order + * @param int $order * * @return bool */ @@ -556,7 +550,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param array $data + * @param array $data * * @return PiggyBank */ @@ -588,7 +582,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $note + * @param string $note * * @return bool * @throws \Exception diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 997137e967..e5d784760f 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -117,13 +117,12 @@ interface PiggyBankRepositoryInterface public function findNull(int $piggyBankId): ?PiggyBank; /** - * @param PiggyBank|null $piggyBank * @param int|null $piggyBankId * @param string|null $piggyBankName * * @return PiggyBank|null */ - public function findPiggyBank(?PiggyBank $piggyBank, ?int $piggyBankId, ?string $piggyBankName): ?PiggyBank; + public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank; /** * Get current amount saved in piggy bank. diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index 9f0db9bc94..0e20dfda60 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -24,18 +24,15 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Support; use Exception; -use FireflyIII\Factory\AccountFactory; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\AccountMetaFactory; -use FireflyIII\Factory\TransactionFactory; -use FireflyIII\Factory\TransactionJournalFactory; +use FireflyIII\Factory\TransactionGroupFactory; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Note; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Services\Internal\Destroy\JournalDestroyService; -use FireflyIII\User; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService; use Log; use Validator; @@ -45,35 +42,15 @@ use Validator; */ trait AccountServiceTrait { + /** @var AccountRepositoryInterface */ + protected $accountRepository; + /** @var array */ - public $validAssetFields = ['accountRole', 'accountNumber', 'currency_id', 'BIC', 'include_net_worth']; + protected $validAssetFields = ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; /** @var array */ - public $validCCFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType', 'accountNumber', 'currency_id', 'BIC', 'include_net_worth']; + protected $validCCFields = ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; /** @var array */ - public $validFields = ['accountNumber', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; - - /** - * @param Account $account - * - * @return bool - */ - public function deleteIB(Account $account): bool - { - Log::debug(sprintf('deleteIB() for account #%d', $account->id)); - $openingBalance = $this->getIBJournal($account); - - // opening balance data? update it! - if (null !== $openingBalance) { - Log::debug('Opening balance journal found, delete journal.'); - /** @var JournalDestroyService $service */ - $service = app(JournalDestroyService::class); - $service->destroy($openingBalance); - - return true; - } - - return true; - } + protected $validFields = ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; /** * @param null|string $iban @@ -98,217 +75,63 @@ trait AccountServiceTrait return $iban; } - /** - * Find existing opening balance. - * - * @param Account $account - * - * @return TransactionJournal|null - */ - public function getIBJournal(Account $account): ?TransactionJournal - { - $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.account_id', $account->id) - ->transactionTypes([TransactionType::OPENING_BALANCE]) - ->first(['transaction_journals.*']); - if (null === $journal) { - Log::debug('Could not find a opening balance journal, return NULL.'); +// /** +// * @param User $user +// * @param string $name +// * +// * @return Account +// * @throws \FireflyIII\Exceptions\FireflyException +// */ +// public function storeOpposingAccount(User $user, string $name): Account +// { +// $opposingAccountName = (string)trans('firefly.initial_balance_account', ['name' => $name]); +// Log::debug('Going to create an opening balance opposing account.'); +// /** @var AccountFactory $factory */ +// $factory = app(AccountFactory::class); +// $factory->setUser($user); +// +// return $factory->findOrCreate($opposingAccountName, AccountType::INITIAL_BALANCE); +// } - return null; - } - Log::debug(sprintf('Found opening balance: journal #%d.', $journal->id)); - - return $journal; - } - - /** - * @param Account $account - * @param array $data - * - * @return TransactionJournal|null - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function storeIBJournal(Account $account, array $data): ?TransactionJournal - { - $amount = (string)$data['openingBalance']; - Log::debug(sprintf('Submitted amount is %s', $amount)); - - if (0 === bccomp($amount, '0')) { - return null; - } - - // store journal, without transactions: - $name = $data['name']; - $currencyId = $data['currency_id']; - $journalData = [ - 'type' => TransactionType::OPENING_BALANCE, - 'user' => $account->user->id, - 'transaction_currency_id' => $currencyId, - 'description' => (string)trans('firefly.initial_balance_description', ['account' => $account->name]), - 'completed' => true, - 'date' => $data['openingBalanceDate'], - 'bill_id' => null, - 'bill_name' => null, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'tags' => null, - 'notes' => null, - 'transactions' => [], - - ]; - /** @var TransactionJournalFactory $factory */ - $factory = app(TransactionJournalFactory::class); - $factory->setUser($account->user); - $journal = $factory->create($journalData); - $opposing = $this->storeOpposingAccount($account->user, $name); - Log::notice(sprintf('Created new opening balance journal: #%d', $journal->id)); - - $firstAccount = $account; - $secondAccount = $opposing; - $firstAmount = $amount; - $secondAmount = bcmul($amount, '-1'); - Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount)); - - if (bccomp($amount, '0') === -1) { - Log::debug(sprintf('%s is a negative number.', $amount)); - $firstAccount = $opposing; - $secondAccount = $account; - $firstAmount = bcmul($amount, '-1'); - $secondAmount = $amount; - Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount)); - } - /** @var TransactionFactory $factory */ - $factory = app(TransactionFactory::class); - $factory->setUser($account->user); - $factory->create( - [ - 'account' => $firstAccount, - 'transaction_journal' => $journal, - 'amount' => $firstAmount, - 'currency_id' => $currencyId, - 'description' => null, - 'identifier' => 0, - 'foreign_amount' => null, - 'reconciled' => false, - ] - ); - $factory->create( - [ - 'account' => $secondAccount, - 'transaction_journal' => $journal, - 'amount' => $secondAmount, - 'currency_id' => $currencyId, - 'description' => null, - 'identifier' => 0, - 'foreign_amount' => null, - 'reconciled' => false, - ] - ); - - return $journal; - } - - /** - * @param User $user - * @param string $name - * - * @return Account - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function storeOpposingAccount(User $user, string $name): Account - { - $opposingAccountName = (string)trans('firefly.initial_balance_account', ['name' => $name]); - Log::debug('Going to create an opening balance opposing account.'); - /** @var AccountFactory $factory */ - $factory = app(AccountFactory::class); - $factory->setUser($user); - - return $factory->findOrCreate($opposingAccountName, AccountType::INITIAL_BALANCE); - } - - /** - * @param Account $account - * @param array $data - * - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function updateIB(Account $account, array $data): bool - { - Log::debug(sprintf('updateInitialBalance() for account #%d', $account->id)); - $openingBalance = $this->getIBJournal($account); - - // no opening balance journal? create it: - if (null === $openingBalance) { - Log::debug('No opening balance journal yet, create journal.'); - $this->storeIBJournal($account, $data); - - return true; - } - - // opening balance data? update it! - if (null !== $openingBalance->id) { - Log::debug('Opening balance journal found, update journal.'); - $this->updateIBJournal($account, $openingBalance, $data); - - return true; - } - - return true; // @codeCoverageIgnore - } - - /** - * @param Account $account - * @param TransactionJournal $journal - * @param array $data - * - * @return bool - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function updateIBJournal(Account $account, TransactionJournal $journal, array $data): bool - { - $date = $data['openingBalanceDate']; - $amount = (string)$data['openingBalance']; - $negativeAmount = bcmul($amount, '-1'); - $currencyId = (int)$data['currency_id']; - Log::debug(sprintf('Submitted amount for opening balance to update is "%s"', $amount)); - if (0 === bccomp($amount, '0')) { - Log::notice(sprintf('Amount "%s" is zero, delete opening balance.', $amount)); - /** @var JournalDestroyService $service */ - $service = app(JournalDestroyService::class); - $service->destroy($journal); - - return true; - } - $journal->date = $date; - $journal->transaction_currency_id = $currencyId; - $journal->save(); - /** @var Transaction $transaction */ - foreach ($journal->transactions()->get() as $transaction) { - if ((int)$account->id === (int)$transaction->account_id) { - Log::debug(sprintf('Will (eq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $amount)); - $transaction->amount = $amount; - $transaction->transaction_currency_id = $currencyId; - $transaction->save(); - } - if (!((int)$account->id === (int)$transaction->account_id)) { - Log::debug(sprintf('Will (neq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $negativeAmount)); - $transaction->amount = $negativeAmount; - $transaction->transaction_currency_id = $currencyId; - $transaction->save(); - } - } - Log::debug('Updated opening balance journal.'); - - return true; - } +// /** +// * @param Account $account +// * @param array $data +// * +// * @return bool +// * @throws \FireflyIII\Exceptions\FireflyException +// */ +// public function updateOB(Account $account, array $data): bool +// { +// Log::debug(sprintf('updateIB() for account #%d', $account->id)); +// +// $openingBalanceGroup = $this->getOBGroup($account); +// +// // no opening balance journal? create it: +// if (null === $openingBalanceGroup) { +// Log::debug('No opening balance journal yet, create group.'); +// $this->storeOBGroup($account, $data); +// +// return true; +// } +// +// // opening balance data? update it! +// if (null !== $openingBalanceGroup->id) { +// Log::debug('Opening balance group found, update group.'); +// $this->updateOBGroup($account, $openingBalanceGroup, $data); +// +// return true; +// } +// +// return true; // @codeCoverageIgnore +// } /** * Update meta data for account. Depends on type which fields are valid. * + * TODO this method treats expense accounts and liabilities the same way (tries to save interest) + * * @param Account $account - * @param array $data + * @param array $data * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function updateMetaData(Account $account, array $data): void @@ -318,7 +141,7 @@ trait AccountServiceTrait if ($account->accountType->type === AccountType::ASSET) { $fields = $this->validAssetFields; } - if ($account->accountType->type === AccountType::ASSET && 'ccAsset' === $data['accountRole']) { + if ($account->accountType->type === AccountType::ASSET && 'ccAsset' === $data['account_role']) { $fields = $this->validCCFields; } /** @var AccountMetaFactory $factory */ @@ -328,9 +151,120 @@ trait AccountServiceTrait } } +// /** +// * Find existing opening balance. +// * +// * @param Account $account +// * +// * @return TransactionJournal|null +// */ +// public function getIBJournal(Account $account): ?TransactionJournal +// { +// $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') +// ->where('transactions.account_id', $account->id) +// ->transactionTypes([TransactionType::OPENING_BALANCE]) +// ->first(['transaction_journals.*']); +// if (null === $journal) { +// Log::debug('Could not find a opening balance journal, return NULL.'); +// +// return null; +// } +// Log::debug(sprintf('Found opening balance: journal #%d.', $journal->id)); +// +// return $journal; +// } + +// /** +// * @param Account $account +// * @param array $data +// * +// * @return TransactionJournal|null +// * @throws \FireflyIII\Exceptions\FireflyException +// * @SuppressWarnings(PHPMD.ExcessiveMethodLength) +// */ +// public function storeIBJournal(Account $account, array $data): ?TransactionJournal +// { +// $amount = (string)$data['openingBalance']; +// Log::debug(sprintf('Submitted amount is %s', $amount)); +// +// if (0 === bccomp($amount, '0')) { +// return null; +// } +// +// // store journal, without transactions: +// $name = $data['name']; +// $currencyId = $data['currency_id']; +// $journalData = [ +// 'type' => TransactionType::OPENING_BALANCE, +// 'user' => $account->user->id, +// 'transaction_currency_id' => $currencyId, +// 'description' => (string)trans('firefly.initial_balance_description', ['account' => $account->name]), +// 'completed' => true, +// 'date' => $data['openingBalanceDate'], +// 'bill_id' => null, +// 'bill_name' => null, +// 'piggy_bank_id' => null, +// 'piggy_bank_name' => null, +// 'tags' => null, +// 'notes' => null, +// 'transactions' => [], +// +// ]; +// /** @var TransactionJournalFactory $factory */ +// $factory = app(TransactionJournalFactory::class); +// $factory->setUser($account->user); +// $journal = $factory->create($journalData); +// $opposing = $this->storeOpposingAccount($account->user, $name); +// Log::notice(sprintf('Created new opening balance journal: #%d', $journal->id)); +// +// $firstAccount = $account; +// $secondAccount = $opposing; +// $firstAmount = $amount; +// $secondAmount = bcmul($amount, '-1'); +// Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount)); +// +// if (bccomp($amount, '0') === -1) { +// Log::debug(sprintf('%s is a negative number.', $amount)); +// $firstAccount = $opposing; +// $secondAccount = $account; +// $firstAmount = bcmul($amount, '-1'); +// $secondAmount = $amount; +// Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount)); +// } +// /** @var TransactionFactory $factory */ +// $factory = app(TransactionFactory::class); +// $factory->setUser($account->user); +// $factory->create( +// [ +// 'account' => $firstAccount, +// 'transaction_journal' => $journal, +// 'amount' => $firstAmount, +// 'currency_id' => $currencyId, +// 'description' => null, +// 'identifier' => 0, +// 'foreign_amount' => null, +// 'reconciled' => false, +// ] +// ); +// $factory->create( +// [ +// 'account' => $secondAccount, +// 'transaction_journal' => $journal, +// 'amount' => $secondAmount, +// 'currency_id' => $currencyId, +// 'description' => null, +// 'identifier' => 0, +// 'foreign_amount' => null, +// 'reconciled' => false, +// ] +// ); +// +// return $journal; +// } + /** * @param Account $account - * @param string $note + * @param string $note * * @return bool */ @@ -366,10 +300,10 @@ trait AccountServiceTrait * * @return bool */ - public function validIBData(array $data): bool + public function validOBData(array $data): bool { - $data['openingBalance'] = (string)($data['openingBalance'] ?? ''); - if ('' !== $data['openingBalance'] && isset($data['openingBalance'], $data['openingBalanceDate'])) { + $data['opening_balance'] = (string)($data['opening_balance'] ?? ''); + if ('' !== $data['opening_balance'] && isset($data['opening_balance'], $data['opening_balance_date'])) { Log::debug('Array has valid opening balance data.'); return true; @@ -378,4 +312,172 @@ trait AccountServiceTrait return false; } + +// /** +// * @param Account $account +// * @param TransactionJournal $journal +// * @param array $data +// * +// * @return bool +// * @SuppressWarnings(PHPMD.CyclomaticComplexity) +// */ +// protected function updateIBJournal(Account $account, TransactionJournal $journal, array $data): bool +// { +// $date = $data['openingBalanceDate']; +// $amount = (string)$data['openingBalance']; +// $negativeAmount = bcmul($amount, '-1'); +// $currencyId = (int)$data['currency_id']; +// Log::debug(sprintf('Submitted amount for opening balance to update is "%s"', $amount)); +// if (0 === bccomp($amount, '0')) { +// Log::notice(sprintf('Amount "%s" is zero, delete opening balance.', $amount)); +// /** @var JournalDestroyService $service */ +// $service = app(JournalDestroyService::class); +// $service->destroy($journal); +// +// return true; +// } +// $journal->date = $date; +// $journal->transaction_currency_id = $currencyId; +// $journal->save(); +// /** @var Transaction $transaction */ +// foreach ($journal->transactions()->get() as $transaction) { +// if ((int)$account->id === (int)$transaction->account_id) { +// Log::debug(sprintf('Will (eq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $amount)); +// $transaction->amount = $amount; +// $transaction->transaction_currency_id = $currencyId; +// $transaction->save(); +// } +// if (!((int)$account->id === (int)$transaction->account_id)) { +// Log::debug(sprintf('Will (neq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $negativeAmount)); +// $transaction->amount = $negativeAmount; +// $transaction->transaction_currency_id = $currencyId; +// $transaction->save(); +// } +// } +// Log::debug('Updated opening balance journal.'); +// +// return true; +// } + + /** + * Delete TransactionGroup with opening balance in it. + * + * @param Account $account + */ + protected function deleteOBGroup(Account $account): void + { + Log::debug(sprintf('deleteOB() for account #%d', $account->id)); + $openingBalanceGroup = $this->getOBGroup($account); + + // opening balance data? update it! + if (null !== $openingBalanceGroup) { + Log::debug('Opening balance journal found, delete journal.'); + /** @var TransactionGroupDestroyService $service */ + $service = app(TransactionGroupDestroyService::class); + $service->destroy($openingBalanceGroup); + } + } + + /** + * @param Account $account + * @param array $data + * @return TransactionGroup|null + */ + protected function createOBGroup(Account $account, array $data): ?TransactionGroup + { + Log::debug('Now going to create an OB group.'); + $language = app('preferences')->getForUser($account->user, 'language', 'en_US')->data; + $sourceId = null; + $sourceName = null; + $destId = null; + $destName = null; + $amount = $data['opening_balance']; + if (1 === bccomp($amount, '0')) { + Log::debug(sprintf('Amount is %s, which is positive. Source is a new IB account, destination is #%d', $amount, $account->id)); + // amount is positive. + $sourceName = trans('firefly.initial_balance_description', ['account' => $account->name], $language); + $destId = $account->id; + } + if (-1 === bccomp($amount, '0')) { + Log::debug(sprintf('Amount is %s, which is negative. Destination is a new IB account, source is #%d', $amount, $account->id)); + // amount is not positive + $destName = trans('firefly.initial_balance_account', ['account' => $account->name], $language); + $sourceId = $account->id; + } + $amount = app('steam')->positive($amount); + $submission = [ + 'group_title' => null, + 'user' => $account->user_id, + 'transactions' => [ + [ + 'type' => 'Opening balance', + 'date' => $data['opening_balance_date'], + 'source_id' => $sourceId, + 'source_name' => $sourceName, + 'destination_id' => $destId, + 'destination_name' => $destName, + 'user' => $account->user_id, + 'order' => 0, + 'amount' => $amount, + 'foreign_amount' => null, + 'description' => trans('firefly.initial_balance_description', ['account' => $account->name]), + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'reconciled' => false, + 'notes' => null, + 'tags' => [], + ], + ], + ]; + Log::debug('Going for submission', $submission); + $group = null; + /** @var TransactionGroupFactory $factory */ + $factory = app(TransactionGroupFactory::class); + $factory->setUser($account->user); + + try { + $group = $factory->create($submission); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + } + // @codeCoverageIgnoreEnd + + return $group; + } + + /** + * Update or create the opening balance group. Assumes valid data in $data. + * + * Returns null if this fails. + * + * @param Account $account + * @param array $data + * @return TransactionGroup|null + */ + protected function updateOBGroup(Account $account, array $data): ?TransactionGroup + { + if (null === $this->getOBGroup($account)) { + return $this->createOBGroup($account, $data); + } + + // edit in this method + die('cannot handle edit'); + } + + /** + * Returns the opening balance group, or NULL if it does not exist. + * + * @param Account $account + * @return TransactionGroup|null + */ + protected function getOBGroup(Account $account): ?TransactionGroup + { + return $this->accountRepository->getOpeningBalanceGroup($account); + } } diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index d4a1b2ce2c..c866196aa2 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -60,7 +60,6 @@ trait JournalServiceTrait */ protected function getForeignAmount(?string $amount): ?string { - $result = null; if (null === $amount) { Log::debug('No foreign amount info in array. Return NULL'); @@ -152,7 +151,7 @@ trait JournalServiceTrait $result = $this->accountRepository->store( [ 'account_type_id' => null, - 'accountType' => $preferredType, + 'account_type' => $preferredType, 'name' => $accountName, 'active' => true, 'iban' => null, @@ -243,9 +242,11 @@ trait JournalServiceTrait // try to delete existing notes. try { $note->delete(); + // @codeCoverageIgnoreStart } catch (Exception $e) { Log::debug(sprintf('Could not delete journal notes: %s', $e->getMessage())); } + // @codeCoverageIgnoreEnd } } diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index 2a3f14894b..9f19e51a3d 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Support; use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\AccountFactory; use FireflyIII\Factory\BudgetFactory; use FireflyIII\Factory\CategoryFactory; use FireflyIII\Factory\PiggyBankFactory; @@ -35,7 +37,8 @@ use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; -use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Validation\AccountValidator; use Log; @@ -47,9 +50,9 @@ trait RecurringTransactionTrait { /** * @param Recurrence $recurrence - * @param array $repetitions + * @param array $repetitions */ - public function createRepetitions(Recurrence $recurrence, array $repetitions): void + protected function createRepetitions(Recurrence $recurrence, array $repetitions): void { /** @var array $array */ foreach ($repetitions as $array) { @@ -66,33 +69,72 @@ trait RecurringTransactionTrait } } + /** + * @param array $expectedTypes + * @param Account|null $account + * @param int|null $accountId + * @param string|null $accountName + * + * @return Account + */ + protected function findAccount(array $expectedTypes, ?int $accountId, ?string $accountName): Account + { + $result = null; + $accountId = (int)$accountId; + $accountName = (string)$accountName; + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser($this->user); + + // if user has submitted an account ID, search for it. + $result = $repository->findNull((int)$accountId); + if (null !== $result) { + return $result; + } + + // if user has submitted a name, search for it: + $result = $repository->findByName($accountName, $expectedTypes); + if (null !== $result) { + return $result; + } + + // maybe we can create it? Try to avoid LOAN and other asset types. + $cannotCreate = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; + /** @var AccountFactory $factory */ + $factory = app(AccountFactory::class); + $factory->setUser($this->user); + foreach ($expectedTypes as $expectedType) { + if (in_array($expectedType, $cannotCreate, true)) { + continue; + } + if (!in_array($expectedType, $cannotCreate, true)) { + try { + $result = $factory->findOrCreate($accountName, $expectedType); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + } + // @codeCoverageIgnoreEnd + } + } + + return $result ?? $repository->getCashAccount(); + } + /** * Store transactions of a recurring transactions. It's complex but readable. * * @param Recurrence $recurrence - * @param array $transactions - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @param array $transactions + * @throws FireflyException */ - public function createTransactions(Recurrence $recurrence, array $transactions): void + protected function createTransactions(Recurrence $recurrence, array $transactions): void { foreach ($transactions as $array) { - $source = null; - $destination = null; - switch ($recurrence->transactionType->type) { - case TransactionType::WITHDRAWAL: - $source = $this->findAccount(AccountType::ASSET, null, $array['source_id'], $array['source_name']); - $destination = $this->findAccount(AccountType::EXPENSE,null, $array['destination_id'], $array['destination_name']); - break; - case TransactionType::DEPOSIT: - $source = $this->findAccount(AccountType::REVENUE, null, $array['source_id'], $array['source_name']); - $destination = $this->findAccount(AccountType::ASSET, null, $array['destination_id'], $array['destination_name']); - break; - case TransactionType::TRANSFER: - $source = $this->findAccount(AccountType::ASSET,null, $array['source_id'], $array['source_name']); - $destination = $this->findAccount(AccountType::ASSET, null, $array['destination_id'], $array['destination_name']); - break; - } + $sourceTypes = config(sprintf('firefly.expected_source_types.source.%s', $recurrence->transactionType->type)); + $destTypes = config(sprintf('firefly.expected_source_types.destination.%s', $recurrence->transactionType->type)); + $source = $this->findAccount($sourceTypes, $array['source_id'], $array['source_name']); + $destination = $this->findAccount($destTypes, $array['destination_id'], $array['destination_name']); /** @var TransactionCurrencyFactory $factory */ $factory = app(TransactionCurrencyFactory::class); @@ -101,6 +143,20 @@ trait RecurringTransactionTrait if (null === $currency) { $currency = app('amount')->getDefaultCurrencyByUser($recurrence->user); } + + // once the accounts have been determined, we still verify their validity: + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setUser($recurrence->user); + $validator->setTransactionType($recurrence->transactionType->type); + if (!$validator->validateSource($source->id, null)) { + throw new FireflyException(sprintf('Source invalid: %s', $validator->sourceError)); // @codeCoverageIgnore + } + + if (!$validator->validateDestination($destination->id, null)) { + throw new FireflyException(sprintf('Destination invalid: %s', $validator->sourceError)); // @codeCoverageIgnore + } + $transaction = new RecurrenceTransaction( [ 'recurrence_id' => $recurrence->id, @@ -150,7 +206,7 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence */ - public function deleteRepetitions(Recurrence $recurrence): void + protected function deleteRepetitions(Recurrence $recurrence): void { $recurrence->recurrenceRepetitions()->delete(); } @@ -158,7 +214,7 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence */ - public function deleteTransactions(Recurrence $recurrence): void + protected function deleteTransactions(Recurrence $recurrence): void { /** @var RecurrenceTransaction $transaction */ foreach ($recurrence->recurrenceTransactions as $transaction) { @@ -171,23 +227,13 @@ trait RecurringTransactionTrait } } - /** - * @param null|string $expectedType - * @param Account|null $account - * @param int|null $accountId - * @param null|string $accountName - * - * @return Account|null - */ - abstract public function findAccount(?string $expectedType, ?Account $account, ?int $accountId, ?string $accountName): ?Account; - /** * Update meta data for recurring transaction. * * @param Recurrence $recurrence - * @param array $data + * @param array $data */ - public function updateMetaData(Recurrence $recurrence, array $data): void + protected function updateMetaData(Recurrence $recurrence, array $data): void { // only two special meta fields right now. Let's just hard code them. $piggyId = (int)($data['meta']['piggy_bank_id'] ?? 0.0); @@ -202,8 +248,8 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence - * @param int $piggyId - * @param string $piggyName + * @param int $piggyId + * @param string $piggyName */ protected function updatePiggyBank(Recurrence $recurrence, int $piggyId, string $piggyName): void { @@ -229,7 +275,7 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence - * @param array $tags + * @param array $tags */ protected function updateTags(Recurrence $recurrence, array $tags): void { diff --git a/app/Services/Internal/Support/TransactionServiceTrait.php b/app/Services/Internal/Support/TransactionServiceTrait.php index 87156a2172..dd0e9a2e88 100644 --- a/app/Services/Internal/Support/TransactionServiceTrait.php +++ b/app/Services/Internal/Support/TransactionServiceTrait.php @@ -43,129 +43,129 @@ use Log; trait TransactionServiceTrait { - /** - * @param TransactionJournal $journal - * @param string $direction - * - * @return string|null - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function accountType(TransactionJournal $journal, string $direction): ?string - { - $types = []; - $type = $journal->transactionType->type; - if (TransactionType::WITHDRAWAL === $type) { - $types['source'] = AccountType::ASSET; - $types['destination'] = AccountType::EXPENSE; - } - if (TransactionType::DEPOSIT === $type) { - $types['source'] = AccountType::REVENUE; - $types['destination'] = AccountType::ASSET; - } - if (TransactionType::TRANSFER === $type) { - $types['source'] = AccountType::ASSET; - $types['destination'] = AccountType::ASSET; - } - if (TransactionType::RECONCILIATION === $type) { - $types['source'] = null; - $types['destination'] = null; - } +// /** +// * @param TransactionJournal $journal +// * @param string $direction +// * +// * @return string|null +// * @SuppressWarnings(PHPMD.CyclomaticComplexity) +// */ +// public function accountType(TransactionJournal $journal, string $direction): ?string +// { +// $types = []; +// $type = $journal->transactionType->type; +// if (TransactionType::WITHDRAWAL === $type) { +// $types['source'] = AccountType::ASSET; +// $types['destination'] = AccountType::EXPENSE; +// } +// if (TransactionType::DEPOSIT === $type) { +// $types['source'] = AccountType::REVENUE; +// $types['destination'] = AccountType::ASSET; +// } +// if (TransactionType::TRANSFER === $type) { +// $types['source'] = AccountType::ASSET; +// $types['destination'] = AccountType::ASSET; +// } +// if (TransactionType::RECONCILIATION === $type) { +// $types['source'] = null; +// $types['destination'] = null; +// } +// +// return $types[$direction] ?? null; +// } - return $types[$direction] ?? null; - } +// /** +// * @param string|null $expectedType +// * @param Account|null $account +// * @param int|null $accountId +// * @param string|null $accountName +// * +// * @return Account|null +// * @throws FireflyException +// * @SuppressWarnings(PHPMD.CyclomaticComplexity) +// */ +// public function findAccount(?string $expectedType, ?Account $account, ?int $accountId, ?string $accountName): ?Account +// { +// $result = null; +// +// if (null !== $account && $account->user_id === $this->user->id) { +// return $account; +// } +// +// $accountId = (int)$accountId; +// $accountName = (string)$accountName; +// $repository = app(AccountRepositoryInterface::class); +// $repository->setUser($this->user); +// +// if (null === $expectedType) { +// return $repository->findNull($accountId); +// } +// +// if ($accountId > 0) { +// // must be able to find it based on ID. Validator should catch invalid ID's. +// return $repository->findNull($accountId); +// } +// if (AccountType::ASSET === $expectedType) { +// return $repository->findByName($accountName, [AccountType::ASSET]); +// } +// // for revenue and expense: +// if ('' !== $accountName) { +// /** @var AccountFactory $factory */ +// $factory = app(AccountFactory::class); +// $factory->setUser($this->user); +// +// return $factory->findOrCreate($accountName, $expectedType); +// } +// +// return $repository->getCashAccount(); +// } - /** - * @param string|null $expectedType - * @param Account|null $account - * @param int|null $accountId - * @param string|null $accountName - * - * @return Account|null - * @throws FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function findAccount(?string $expectedType, ?Account $account, ?int $accountId, ?string $accountName): ?Account - { - $result = null; - - if (null !== $account && $account->user_id === $this->user->id) { - return $account; - } - - $accountId = (int)$accountId; - $accountName = (string)$accountName; - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - if (null === $expectedType) { - return $repository->findNull($accountId); - } - - if ($accountId > 0) { - // must be able to find it based on ID. Validator should catch invalid ID's. - return $repository->findNull($accountId); - } - if (AccountType::ASSET === $expectedType) { - return $repository->findByName($accountName, [AccountType::ASSET]); - } - // for revenue and expense: - if ('' !== $accountName) { - /** @var AccountFactory $factory */ - $factory = app(AccountFactory::class); - $factory->setUser($this->user); - - return $factory->findOrCreate($accountName, $expectedType); - } - - return $repository->getCashAccount(); - } - - /** - * @param int|null $currencyId - * @param null|string $currencyCode - * - * @return TransactionCurrency|null - */ - protected function findCurrency(?int $currencyId, ?string $currencyCode): ?TransactionCurrency - { - $factory = app(TransactionCurrencyFactory::class); - - return $factory->find($currencyId, $currencyCode); - } - - /** - * @param Transaction $transaction - * @param string|null $amount - */ - protected function setForeignAmount(Transaction $transaction, ?string $amount): void - { - $amount = '' === (string)$amount ? null : $amount; - $transaction->foreign_amount = $amount; - $transaction->save(); - } - - /** - * @param Transaction $transaction - * @param TransactionCurrency|null $currency - */ - protected function setForeignCurrency(Transaction $transaction, ?TransactionCurrency $currency): void - { - if (null === $currency) { - $transaction->foreign_currency_id = null; - $transaction->save(); - - return; - } - // enable currency if not enabled: - if (false === $currency->enabled) { - $currency->enabled = true; - $currency->save(); - } - - $transaction->foreign_currency_id = $currency->id; - $transaction->save(); - - } +// /** +// * @param int|null $currencyId +// * @param null|string $currencyCode +// * +// * @return TransactionCurrency|null +// */ +// protected function findCurrency(?int $currencyId, ?string $currencyCode): ?TransactionCurrency +// { +// $factory = app(TransactionCurrencyFactory::class); +// +// return $factory->find($currencyId, $currencyCode); +// } +// +// /** +// * @param Transaction $transaction +// * @param string|null $amount +// */ +// protected function setForeignAmount(Transaction $transaction, ?string $amount): void +// { +// $amount = '' === (string)$amount ? null : $amount; +// $transaction->foreign_amount = $amount; +// $transaction->save(); +// } +// +// /** +// * @param Transaction $transaction +// * @param TransactionCurrency|null $currency +// */ +// protected function setForeignCurrency(Transaction $transaction, ?TransactionCurrency $currency): void +// { +// if (null === $currency) { +// $transaction->foreign_currency_id = null; +// $transaction->save(); +// +// return; +// } +// // enable currency if not enabled: +// if (false === $currency->enabled) { +// $currency->enabled = true; +// $currency->save(); +// } +// +// $transaction->foreign_currency_id = $currency->id; +// $transaction->save(); +// +// } } diff --git a/app/Services/Internal/Update/AccountUpdateService.php b/app/Services/Internal/Update/AccountUpdateService.php index 2b18a1269b..f614f5fd52 100644 --- a/app/Services/Internal/Update/AccountUpdateService.php +++ b/app/Services/Internal/Update/AccountUpdateService.php @@ -62,7 +62,7 @@ class AccountUpdateService // update the account itself: $account->name = $data['name']; $account->active = $data['active']; - $account->virtual_balance = '' === trim($data['virtualBalance']) ? '0' : $data['virtualBalance']; + $account->virtual_balance = '' === trim($data['virtual_balance']) ? '0' : $data['virtual_balance']; $account->iban = $data['iban']; $account->save(); @@ -87,13 +87,13 @@ class AccountUpdateService $this->updateMetaData($account, $data); // has valid initial balance (IB) data? - if ($this->validIBData($data)) { + if ($this->validOBData($data)) { // then do update! $this->updateIB($account, $data); } // if not, delete it when exist. - if (!$this->validIBData($data)) { + if (!$this->validOBData($data)) { $this->deleteIB($account); } diff --git a/app/Services/Internal/Update/TransactionUpdateService.php b/app/Services/Internal/Update/TransactionUpdateService.php index f0cc03f295..0caa542aca 100644 --- a/app/Services/Internal/Update/TransactionUpdateService.php +++ b/app/Services/Internal/Update/TransactionUpdateService.php @@ -30,6 +30,7 @@ use Log; /** * Class TransactionUpdateService + * TODO i think this is deprecated. */ class TransactionUpdateService { @@ -48,24 +49,24 @@ class TransactionUpdateService } } - /** - * @param int $transactionId - * - * @return Transaction|null - */ - public function reconcile(int $transactionId): ?Transaction - { - $transaction = Transaction::find($transactionId); - if (null !== $transaction) { - $transaction->reconciled = true; - $transaction->save(); - - return $transaction; - } - - return null; - - } +// /** +// * @param int $transactionId +// * +// * @return Transaction|null +// */ +// public function reconcile(int $transactionId): ?Transaction +// { +// $transaction = Transaction::find($transactionId); +// if (null !== $transaction) { +// $transaction->reconciled = true; +// $transaction->save(); +// +// return $transaction; +// } +// +// return null; +// +// } /** * @param User $user @@ -75,104 +76,104 @@ class TransactionUpdateService $this->user = $user; } - /** - * @param Transaction $transaction - * @param array $data - * - * @return Transaction - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * - */ - public function update(Transaction $transaction, array $data): Transaction - { - $currency = $this->findCurrency($data['currency_id'], $data['currency_code']); - $journal = $transaction->transactionJournal; - $amount = (string)$data['amount']; - $account = null; - // update description: - $transaction->description = $data['description']; - $foreignAmount = null; - if ((float)$transaction->amount < 0) { - // this is the source transaction. - $type = $this->accountType($journal, 'source'); - $account = $this->findAccount($type, $data['source_id'], $data['source_name']); - $amount = app('steam')->negative($amount); - $foreignAmount = app('steam')->negative((string)$data['foreign_amount']); - } - - if ((float)$transaction->amount > 0) { - // this is the destination transaction. - $type = $this->accountType($journal, 'destination'); - $account = $this->findAccount($type, $data['destination_id'], $data['destination_name']); - $amount = app('steam')->positive($amount); - $foreignAmount = app('steam')->positive((string)$data['foreign_amount']); - } - - // update the actual transaction: - $transaction->description = $data['description']; - $transaction->amount = $amount; - $transaction->foreign_amount = null; - $transaction->transaction_currency_id = null === $currency ? $transaction->transaction_currency_id : $currency->id; - $transaction->account_id = $account->id; - $transaction->reconciled = $data['reconciled']; - $transaction->save(); - - // set foreign currency - $foreign = $this->findCurrency($data['foreign_currency_id'], $data['foreign_currency_code']); - // set foreign amount: - if (null !== $foreign && null !== $data['foreign_amount']) { - $this->setForeignCurrency($transaction, $foreign); - $this->setForeignAmount($transaction, $foreignAmount); - } - if (null === $foreign || null === $data['foreign_amount']) { - $this->setForeignCurrency($transaction, null); - $this->setForeignAmount($transaction, null); - } - - // set budget: - $budget = $this->findBudget($data['budget_id'], $data['budget_name']); - $this->setBudget($transaction, $budget); - - // set category - $category = $this->findCategory($data['category_id'], $data['category_name']); - $this->setCategory($transaction, $category); - - return $transaction; - } - - /** - * Update budget for a journal. - * - * @param Transaction $transaction - * @param int $budgetId - * - * @return Transaction - */ - public function updateBudget(Transaction $transaction, int $budgetId): Transaction - { - $budget = $this->findBudget($budgetId, null); - $this->setBudget($transaction, $budget); - - return $transaction; - - } - - /** - * Update category for a journal. - * - * @param Transaction $transaction - * @param string $category - * - * @return Transaction - */ - public function updateCategory(Transaction $transaction, string $category): Transaction - { - $found = $this->findCategory(0, $category); - $this->setCategory($transaction, $found); - - return $transaction; - } +// /** +// * @param Transaction $transaction +// * @param array $data +// * +// * @return Transaction +// * @throws \FireflyIII\Exceptions\FireflyException +// * @SuppressWarnings(PHPMD.ExcessiveMethodLength) +// * @SuppressWarnings(PHPMD.CyclomaticComplexity) +// * @SuppressWarnings(PHPMD.NPathComplexity) +// * +// */ +// public function update(Transaction $transaction, array $data): Transaction +// { +// $currency = $this->findCurrency($data['currency_id'], $data['currency_code']); +// $journal = $transaction->transactionJournal; +// $amount = (string)$data['amount']; +// $account = null; +// // update description: +// $transaction->description = $data['description']; +// $foreignAmount = null; +// if ((float)$transaction->amount < 0) { +// // this is the source transaction. +// $type = $this->accountType($journal, 'source'); +// $account = $this->findAccount($type, $data['source_id'], $data['source_name']); +// $amount = app('steam')->negative($amount); +// $foreignAmount = app('steam')->negative((string)$data['foreign_amount']); +// } +// +// if ((float)$transaction->amount > 0) { +// // this is the destination transaction. +// $type = $this->accountType($journal, 'destination'); +// $account = $this->findAccount($type, $data['destination_id'], $data['destination_name']); +// $amount = app('steam')->positive($amount); +// $foreignAmount = app('steam')->positive((string)$data['foreign_amount']); +// } +// +// // update the actual transaction: +// $transaction->description = $data['description']; +// $transaction->amount = $amount; +// $transaction->foreign_amount = null; +// $transaction->transaction_currency_id = null === $currency ? $transaction->transaction_currency_id : $currency->id; +// $transaction->account_id = $account->id; +// $transaction->reconciled = $data['reconciled']; +// $transaction->save(); +// +// // set foreign currency +// $foreign = $this->findCurrency($data['foreign_currency_id'], $data['foreign_currency_code']); +// // set foreign amount: +// if (null !== $foreign && null !== $data['foreign_amount']) { +// $this->setForeignCurrency($transaction, $foreign); +// $this->setForeignAmount($transaction, $foreignAmount); +// } +// if (null === $foreign || null === $data['foreign_amount']) { +// $this->setForeignCurrency($transaction, null); +// $this->setForeignAmount($transaction, null); +// } +// +// // set budget: +// $budget = $this->findBudget($data['budget_id'], $data['budget_name']); +// $this->setBudget($transaction, $budget); +// +// // set category +// $category = $this->findCategory($data['category_id'], $data['category_name']); +// $this->setCategory($transaction, $category); +// +// return $transaction; +// } +// +// /** +// * Update budget for a journal. +// * +// * @param Transaction $transaction +// * @param int $budgetId +// * +// * @return Transaction +// */ +// public function updateBudget(Transaction $transaction, int $budgetId): Transaction +// { +// $budget = $this->findBudget($budgetId, null); +// $this->setBudget($transaction, $budget); +// +// return $transaction; +// +// } +// +// /** +// * Update category for a journal. +// * +// * @param Transaction $transaction +// * @param string $category +// * +// * @return Transaction +// */ +// public function updateCategory(Transaction $transaction, string $category): Transaction +// { +// $found = $this->findCategory(0, $category); +// $this->setCategory($transaction, $found); +// +// return $transaction; +// } } diff --git a/app/Support/Http/Controllers/CreateStuff.php b/app/Support/Http/Controllers/CreateStuff.php index 94d3a5b997..ca8ce86963 100644 --- a/app/Support/Http/Controllers/CreateStuff.php +++ b/app/Support/Http/Controllers/CreateStuff.php @@ -47,7 +47,7 @@ trait CreateStuff /** * Creates an asset account. * - * @param NewUserFormRequest $request + * @param NewUserFormRequest $request * @param TransactionCurrency $currency * * @return bool @@ -57,16 +57,16 @@ trait CreateStuff /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $assetAccount = [ - 'name' => $request->get('bank_name'), - 'iban' => null, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'account_type_id' => null, - 'active' => true, - 'accountRole' => 'defaultAsset', - 'openingBalance' => $request->input('bank_balance'), - 'openingBalanceDate' => new Carbon, - 'currency_id' => $currency->id, + 'name' => $request->get('bank_name'), + 'iban' => null, + 'account_type' => 'asset', + 'virtual_balance' => 0, + 'account_type_id' => null, + 'active' => true, + 'account_role' => 'defaultAsset', + 'opening_balance' => $request->input('bank_balance'), + 'opening_balance_date' => new Carbon, + 'currency_id' => $currency->id, ]; $repository->store($assetAccount); @@ -78,7 +78,7 @@ trait CreateStuff * Creates a cash wallet. * * @param TransactionCurrency $currency - * @param string $language + * @param string $language * * @return bool */ @@ -87,16 +87,16 @@ trait CreateStuff /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $assetAccount = [ - 'name' => (string)trans('firefly.cash_wallet', [], $language), - 'iban' => null, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'account_type_id' => null, - 'active' => true, - 'accountRole' => 'cashWalletAsset', - 'openingBalance' => null, - 'openingBalanceDate' => null, - 'currency_id' => $currency->id, + 'name' => (string)trans('firefly.cash_wallet', [], $language), + 'iban' => null, + 'account_type' => 'asset', + 'virtual_balance' => 0, + 'account_type_id' => null, + 'active' => true, + 'account_role' => 'cashWalletAsset', + 'opening_balance' => null, + 'opening_balance_date' => null, + 'currency_id' => $currency->id, ]; $repository->store($assetAccount); @@ -130,9 +130,9 @@ trait CreateStuff /** * Create a savings account. * - * @param NewUserFormRequest $request + * @param NewUserFormRequest $request * @param TransactionCurrency $currency - * @param string $language + * @param string $language * * @return bool */ @@ -141,16 +141,16 @@ trait CreateStuff /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $savingsAccount = [ - 'name' => (string)trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language), - 'iban' => null, - 'accountType' => 'asset', - 'account_type_id' => null, - 'virtualBalance' => 0, - 'active' => true, - 'accountRole' => 'savingAsset', - 'openingBalance' => $request->input('savings_balance'), - 'openingBalanceDate' => new Carbon, - 'currency_id' => $currency->id, + 'name' => (string)trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language), + 'iban' => null, + 'account_type' => 'asset', + 'account_type_id' => null, + 'virtual_balance' => 0, + 'active' => true, + 'account_role' => 'savingAsset', + 'opening_balance' => $request->input('savings_balance'), + 'opening_balance_date' => new Carbon, + 'currency_id' => $currency->id, ]; $repository->store($savingsAccount); diff --git a/app/Support/Http/Controllers/ModelInformation.php b/app/Support/Http/Controllers/ModelInformation.php index 60cecd60f9..f45c7a8448 100644 --- a/app/Support/Http/Controllers/ModelInformation.php +++ b/app/Support/Http/Controllers/ModelInformation.php @@ -115,9 +115,9 @@ trait ModelInformation } $data = [ 'name' => $data['destination_account_expense'], - 'accountType' => 'expense', + 'account_type' => 'expense', 'account_type_id' => null, - 'virtualBalance' => 0, + 'virtual_balance' => 0, 'active' => true, 'iban' => null, ]; @@ -171,8 +171,8 @@ trait ModelInformation $data = [ 'name' => $data['source_account_revenue'], - 'accountType' => 'revenue', - 'virtualBalance' => 0, + 'account_type' => 'revenue', + 'virtual_balance' => 0, 'active' => true, 'account_type_id' => null, 'iban' => null, diff --git a/app/Support/Import/Routine/Bunq/PaymentConverter.php b/app/Support/Import/Routine/Bunq/PaymentConverter.php index 070fb0d68f..c6928d062d 100644 --- a/app/Support/Import/Routine/Bunq/PaymentConverter.php +++ b/app/Support/Import/Routine/Bunq/PaymentConverter.php @@ -228,8 +228,8 @@ class PaymentConverter 'iban' => $party->getIban(), 'name' => $party->getLabelUser()->getDisplayName(), 'account_type_id' => null, - 'accountType' => $expectedType, - 'virtualBalance' => null, + 'account_type' => $expectedType, + 'virtual_balance' => null, 'active' => true, ]; $account = $this->accountFactory->create($data); diff --git a/app/Support/Import/Routine/File/OpposingAccountMapper.php b/app/Support/Import/Routine/File/OpposingAccountMapper.php index a6541785f3..40c2acf498 100644 --- a/app/Support/Import/Routine/File/OpposingAccountMapper.php +++ b/app/Support/Import/Routine/File/OpposingAccountMapper.php @@ -116,7 +116,7 @@ class OpposingAccountMapper 'iban' => $data['iban'] ?? null, 'accountNumber' => $data['number'] ?? null, 'account_type_id' => null, - 'accountType' => $expectedType, + 'account_type' => $expectedType, 'active' => true, 'BIC' => $data['bic'] ?? null, ]; diff --git a/app/TransactionRules/Actions/SetDestinationAccount.php b/app/TransactionRules/Actions/SetDestinationAccount.php index 956ae71670..3737a01ab6 100644 --- a/app/TransactionRules/Actions/SetDestinationAccount.php +++ b/app/TransactionRules/Actions/SetDestinationAccount.php @@ -140,9 +140,9 @@ class SetDestinationAccount implements ActionInterface if (null === $account) { $data = [ 'name' => $this->action->action_value, - 'accountType' => 'expense', + 'account_type' => 'expense', 'account_type_id' => null, - 'virtualBalance' => 0, + 'virtual_balance' => 0, 'active' => true, 'iban' => null, ]; diff --git a/app/TransactionRules/Actions/SetSourceAccount.php b/app/TransactionRules/Actions/SetSourceAccount.php index ff2f0e870f..fd23cba980 100644 --- a/app/TransactionRules/Actions/SetSourceAccount.php +++ b/app/TransactionRules/Actions/SetSourceAccount.php @@ -142,9 +142,9 @@ class SetSourceAccount implements ActionInterface // create new revenue account with this name: $data = [ 'name' => $this->action->action_value, - 'accountType' => 'revenue', + 'account_type' => 'revenue', 'account_type_id' => null, - 'virtualBalance' => 0, + 'virtual_balance' => 0, 'active' => true, 'iban' => null, ]; diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index 2955941e21..a1085c0f41 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -267,6 +267,30 @@ class RecurrenceTransformer extends AbstractTransformer $foreignCurrencySymbol = $transaction->foreignCurrency->symbol; $foreignCurrencyDp = $transaction->foreignCurrency->decimal_places; } + + // source info: + $sourceName = ''; + $sourceId = null; + $sourceType = null; + $sourceIban = null; + if (null !== $sourceAccount) { + $sourceName = $sourceAccount->name; + $sourceId = $sourceAccount->id; + $sourceType = $sourceAccount->accountType->type; + $sourceIban = $sourceAccount->iban; + } + $destinationName = ''; + $destinationId = null; + $destinationType = null; + $destinationIban = null; + if (null !== $destinationAccount) { + $destinationName = $destinationAccount->name; + $destinationId = $destinationAccount->id; + $destinationType = $destinationAccount->accountType->type; + $destinationIban = $destinationAccount->iban; + } + + $amount = round($transaction->amount, $transaction->transactionCurrency->decimal_places); $foreignAmount = null; if (null !== $transaction->foreign_currency_id && null !== $transaction->foreign_amount) { @@ -281,10 +305,14 @@ class RecurrenceTransformer extends AbstractTransformer 'foreign_currency_code' => $foreignCurrencyCode, 'foreign_currency_symbol' => $foreignCurrencySymbol, 'foreign_currency_decimal_places' => $foreignCurrencyDp, - 'source_id' => $transaction->source_id, - 'source_name' => null === $sourceAccount ? '' : $sourceAccount->name, - 'destination_id' => $transaction->destination_id, - 'destination_name' => null === $destinationAccount ? '' : $destinationAccount->name, + 'source_id' => $sourceId, + 'source_name' => $sourceName, + 'source_iban' => $sourceIban, + 'source_type' => $sourceType, + 'destination_id' => $destinationId, + 'destination_name' => $destinationName, + 'destination_iban' => $destinationIban, + 'destination_type' => $destinationType, 'amount' => $amount, 'foreign_amount' => $foreignAmount, 'description' => $transaction->description, diff --git a/app/Validation/AccountValidator.php b/app/Validation/AccountValidator.php index 0fb79358a7..dd629f01bf 100644 --- a/app/Validation/AccountValidator.php +++ b/app/Validation/AccountValidator.php @@ -121,6 +121,9 @@ class AccountValidator case TransactionType::TRANSFER: $result = $this->validateTransferDestination($destinationId, $destinationName); break; + case TransactionType::OPENING_BALANCE: + $result = $this->validateOBDestination($destinationId, $destinationName); + break; //case TransactionType::OPENING_BALANCE: //case TransactionType::RECONCILIATION: // die(sprintf('Cannot handle type "%s"', $this->transactionType)); @@ -153,6 +156,9 @@ class AccountValidator case TransactionType::TRANSFER: $result = $this->validateTransferSource($accountId, $accountName); break; + case TransactionType::OPENING_BALANCE: + $result = $this->validateOBSource($accountId, $accountName); + break; //case TransactionType::OPENING_BALANCE: //case TransactionType::RECONCILIATION: // die(sprintf('Cannot handle type "%s"', $this->transactionType)); @@ -181,6 +187,7 @@ class AccountValidator break; case AccountType::EXPENSE: case AccountType::REVENUE: + case AccountType::INITIAL_BALANCE: $result = true; break; } @@ -285,6 +292,52 @@ class AccountValidator return $result; } + /** + * @param int|null $accountId + * @param $accountName + * + * @return bool + */ + private function validateOBDestination(?int $accountId, $accountName): bool + { + $result = null; + Log::debug(sprintf('Now in validateOBDestination(%d, "%s")', $accountId, $accountName)); + + // source can be any of the following types. + $validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? []; + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL we return false, + // because the destination of a deposit can't be created. + $this->destError = (string)trans('validation.ob_dest_need_data'); + Log::error('Both values are NULL, cant create OB destination.'); + $result = false; + } + // if the account can be created anyway we don't need to search. + if (null === $result && true === $this->canCreateTypes($validTypes)) { + Log::debug('Can create some of these types, so return true.'); + $result = true; + } + + if (null === $result) { + // otherwise try to find the account: + $search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName); + if (null === $search) { + Log::debug('findExistingAccount() returned NULL, so the result is false.', $validTypes); + $this->destError = (string)trans('validation.ob_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); + $result = false; + } + if (null !== $search) { + Log::debug(sprintf('findExistingAccount() returned #%d ("%s"), so the result is true.', $search->id, $search->name)); + $this->destination = $search; + $result = true; + } + } + $result = $result ?? false; + Log::debug(sprintf('validateOBDestination(%d, "%s") will return %s', $accountId, $accountName, var_export($result, true))); + + return $result; + } + /** * @param int|null $accountId * @param string|null $accountName @@ -331,6 +384,67 @@ class AccountValidator return $result; } + /** + * Source of an opening balance can either be an asset account + * or an "initial balance account". The latter can be created. + * @param int|null $accountId + * @param string|null $accountName + * + * @return bool + */ + private function validateOBSource(?int $accountId, ?string $accountName): bool + { + Log::debug(sprintf('Now in validateOBSource(%d, "%s")', $accountId, $accountName)); + Log::debug(sprintf('The account name is null: %s', var_export(null === $accountName, true))); + $result = null; + // source can be any of the following types. + $validTypes = array_keys($this->combinations[$this->transactionType]); + + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL return false, + // because the source of a deposit can't be created. + // (this never happens). + $this->sourceError = (string)trans('validation.ob_source_need_data'); + $result = false; + } + + // if the user submits an ID only but that ID is not of the correct type, + // return false. + if (null !== $accountId && null === $accountName) { + Log::debug('Source ID is not null, but name is null.'); + $search = $this->accountRepository->findNull($accountId); + + // the source resulted in an account, but it's not of a valid type. + if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) { + $message = sprintf('User submitted only an ID (#%d), which is a "%s", so this is not a valid source.', $accountId, $search->accountType->type); + Log::debug($message); + $this->sourceError = $message; + $result = false; + } + // the source resulted in an account, AND it's of a valid type. + if (null !== $search && in_array($search->accountType->type, $validTypes, true)) { + Log::debug(sprintf('Found account of correct type: #%d, "%s"', $search->id, $search->name)); + $this->source = $search; + $result = true; + } + } + + // if the account can be created anyway we don't need to search. + if (null === $result && true === $this->canCreateTypes($validTypes)) { + Log::debug('Result is still null.'); + $result = true; + + // set the source to be a (dummy) initial balance account. + $account = new Account; + $accountType = AccountType::whereType(AccountType::INITIAL_BALANCE)->first(); + $account->accountType = $accountType; + $this->source = $account; + } + $result = $result ?? false; + + return $result; + } + /** * @param int|null $accountId * @param $accountName