diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index 32c11968c0..9985526124 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -68,6 +68,7 @@ class TransactionJournalFactory ); // store basic transactions: + /** @var TransactionFactory $factory */ $factory = app(TransactionFactory::class); $factory->setUser($this->user); @@ -91,13 +92,12 @@ class TransactionJournalFactory $this->storeNote($journal, strval($data['notes'])); // store date meta fields (if present): - $this->storeMeta($journal, $data, 'interest_date'); - $this->storeMeta($journal, $data, 'book_date'); - $this->storeMeta($journal, $data, 'process_date'); - $this->storeMeta($journal, $data, 'due_date'); - $this->storeMeta($journal, $data, 'payment_date'); - $this->storeMeta($journal, $data, 'invoice_date'); - $this->storeMeta($journal, $data, 'internal_reference'); + $fields = ['sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep', 'sepa-ci', 'interest_date', 'book_date', 'process_date', + 'due_date', 'payment_date', 'invoice_date', 'internal_reference',]; + + foreach ($fields as $field) { + $this->storeMeta($journal, $data, $field); + } Log::debug('End of TransactionJournalFactory::create()'); return $journal; diff --git a/app/Import/Object/ImportBudget.php b/app/Import/Object/ImportBudget.php index 18b17a91e0..5327855d89 100644 --- a/app/Import/Object/ImportBudget.php +++ b/app/Import/Object/ImportBudget.php @@ -53,9 +53,9 @@ class ImportBudget } /** - * @return Budget + * @return Budget|null */ - public function getBudget(): Budget + public function getBudget(): ?Budget { if (null === $this->budget) { $this->store(); diff --git a/app/Import/Object/ImportCategory.php b/app/Import/Object/ImportCategory.php index e7f21db03f..176ce6fc0c 100644 --- a/app/Import/Object/ImportCategory.php +++ b/app/Import/Object/ImportCategory.php @@ -53,9 +53,9 @@ class ImportCategory } /** - * @return Category + * @return null|Category */ - public function getCategory(): Category + public function getCategory(): ?Category { if (null === $this->category) { $this->store(); diff --git a/app/Import/Object/ImportJournal.php b/app/Import/Object/ImportJournal.php index 4a7e607b2e..b0e48154d8 100644 --- a/app/Import/Object/ImportJournal.php +++ b/app/Import/Object/ImportJournal.php @@ -24,6 +24,7 @@ namespace FireflyIII\Import\Object; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Import\Converter\Amount; use FireflyIII\Import\Converter\ConverterInterface; use FireflyIII\Import\MapperPreProcess\PreProcessorInterface; use FireflyIII\User; @@ -36,6 +37,7 @@ use Steam; */ class ImportJournal { + /** @var ImportAccount */ public $asset; /** @var ImportBill */ @@ -156,6 +158,80 @@ class ImportJournal return $this->description; } + /** + * @return string|null + */ + public function getForeignAmount(): ?string + { + Log::debug('Now in getForeignAmount()'); + Log::debug(sprintf('foreign amount is %s', var_export($this->foreignAmount, true))); + + // no foreign amount? return null + if (null === $this->foreignAmount) { + Log::debug('Return NULL for foreign amount'); + + return null; + } + // converter is default amount converter: no special stuff + $converter = app(Amount::class); + $amount = $converter->convert($this->foreignAmount['value']); + Log::debug(sprintf('First attempt to convert foreign gives "%s"', $amount)); + // modify + foreach ($this->modifiers as $modifier) { + $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role']))); + /** @var ConverterInterface $converter */ + $converter = app($class); + Log::debug(sprintf('Now launching converter %s', $class)); + if ($converter->convert($modifier['value']) === -1) { + $this->convertedAmount = Steam::negative($amount); + } + Log::debug(sprintf('Foreign amount after conversion is %s', $amount)); + } + + Log::debug(sprintf('After modifiers the result is: "%s"', $amount)); + + + Log::debug(sprintf('converted foreign amount is: "%s"', $amount)); + if (0 === bccomp($amount, '0')) { + return null; + } + + return $amount; + } + + /** + * Get date field or NULL + * + * @param string $field + * + * @return Carbon|null + */ + public function getMetaDate(string $field): ?Carbon + { + if (isset($this->metaDates[$field])) { + return new Carbon($this->metaDates[$field]); + } + + return null; + } + + /** + * Get string field or NULL + * + * @param string $field + * + * @return string|null + */ + public function getMetaString(string $field): ?string + { + if (isset($this->metaFields[$field]) && strlen($this->metaFields[$field]) > 0) { + return strval($this->metaFields[$field]); + } + + return null; + } + + /** * @param string $hash */ @@ -213,7 +289,7 @@ class ImportJournal $this->foreignAmount = $array; break; case 'foreign-currency-code': - $this->foreignCurrency->setId($array); + $this->foreignCurrency->setCode($array); break; case 'amount_debit': $this->amountDebit = $array; diff --git a/app/Import/Storage/ImportStorage.php b/app/Import/Storage/ImportStorage.php index 4759eee90e..b67cddfc32 100644 --- a/app/Import/Storage/ImportStorage.php +++ b/app/Import/Storage/ImportStorage.php @@ -25,9 +25,9 @@ namespace FireflyIII\Import\Storage; use ErrorException; use Exception; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Import\Object\ImportJournal; use FireflyIII\Models\ImportJob; -use FireflyIII\Models\Note; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; @@ -75,6 +75,8 @@ class ImportStorage private $applyRules = false; /** @var string */ private $dateFormat = 'Ymd'; + /** @var TransactionJournalFactory */ + private $factory; /** @var bool */ private $matchBills = false; /** @var Collection */ @@ -92,6 +94,7 @@ class ImportStorage $this->objects = new Collection; $this->journals = new Collection; $this->errors = new Collection; + } /** @@ -111,6 +114,8 @@ class ImportStorage $this->journalRepository = app(JournalRepositoryInterface::class); $this->repository->setUser($job->user); $this->journalRepository->setUser($job->user); + $this->factory = app(TransactionJournalFactory::class); + $this->factory->setUser($job->user); $config = $this->repository->getConfiguration($job); $currency = app('amount')->getDefaultCurrencyByUser($job->user); @@ -158,6 +163,7 @@ class ImportStorage } catch (FireflyException | ErrorException | Exception $e) { $this->errors->push($e->getMessage()); Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage())); + Log::error($e->getTraceAsString()); } } ); @@ -179,12 +185,12 @@ class ImportStorage Log::debug(sprintf('Going to store object #%d/%d with description "%s"', ($index + 1), $this->total, $importJournal->getDescription())); $assetAccount = $importJournal->asset->getAccount(); $amount = $importJournal->getAmount(); + $foreignAmount = $importJournal->getForeignAmount(); $currencyId = $this->getCurrencyId($importJournal); $foreignCurrencyId = $this->getForeignCurrencyId($importJournal, $currencyId); $date = $importJournal->getDate($this->dateFormat)->format('Y-m-d'); $opposingAccount = $this->getOpposingAccount($importJournal->opposing, $assetAccount->id, $amount); $transactionType = $this->getTransactionType($amount, $opposingAccount); - $description = $importJournal->getDescription(); $this->addStep(); /** @@ -192,7 +198,7 @@ class ImportStorage */ $parameters = [ 'type' => $transactionType, - 'description' => $description, + 'description' => $importJournal->getDescription(), 'amount' => $amount, 'date' => $date, 'asset' => $assetAccount->name, @@ -211,52 +217,83 @@ class ImportStorage unset($parameters); $this->addStep(); - // store journal and create transactions: - $parameters = [ - 'type' => $transactionType, - 'currency' => $currencyId, - 'foreign_currency' => $foreignCurrencyId, - 'asset' => $assetAccount, - 'opposing' => $opposingAccount, - 'description' => $description, - 'date' => $date, - 'hash' => $importJournal->hash, - 'amount' => $amount, - ]; - $journal = $this->storeJournal($parameters); - unset($parameters); - $this->addStep(); - // store meta object things: - $this->storeCategory($journal, $importJournal->category->getCategory()); - $this->storeBudget($journal, $importJournal->budget->getBudget()); + try { + $budget = $importJournal->budget->getBudget(); + $category = $importJournal->category->getCategory(); + $bill = $importJournal->bill->getBill(); + $source = $assetAccount; + $destination = $opposingAccount; - // to save bill, also give it the amount: - $importJournal->bill->setAmount($amount); - - $this->storeBill($journal, $importJournal->bill->getBill()); - $this->storeMetaDates($journal, $importJournal->metaDates); - $this->storeTags($importJournal->tags, $journal); - - foreach ($importJournal->metaFields as $field => $value) { - $this->journalRepository->setMetaString($journal, $field, $value); + if ($transactionType === TransactionType::DEPOSIT) { + $destination = $assetAccount; + $source = $opposingAccount; + } + Log::debug( + sprintf('Will make #%s (%s) the source and #%s (%s) the destination.', $source->id, $source->name, $destination->id, $destination->name) + ); + $data = [ + 'user' => $this->job->user_id, + 'type' => $transactionType, + 'date' => $importJournal->getDate($this->dateFormat), + 'description' => $importJournal->getDescription(), + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => is_null($bill) ? null : $bill->id, + 'bill_name' => null, + 'tags' => $importJournal->tags, + 'interest_date' => $importJournal->getMetaDate('interest_date'), + 'book_date' => $importJournal->getMetaDate('book_date'), + 'process_date' => $importJournal->getMetaDate('process_date'), + 'due_date' => $importJournal->getMetaDate('due_date'), + 'payment_date' => $importJournal->getMetaDate('payment_date'), + 'invoice_date' => $importJournal->getMetaDate('invoice_date'), + 'internal_reference' => $importJournal->metaFields['internal_reference'] ?? null, + 'notes' => $importJournal->notes, + 'sepa-cc' => $importJournal->getMetaString('sepa-cc'), + 'sepa-ct-op' => $importJournal->getMetaString('sepa-ct-op'), + 'sepa-ct-id' => $importJournal->getMetaString('sepa-ct-id'), + 'sepa-db' => $importJournal->getMetaString('sepa-db'), + 'sepa-country' => $importJournal->getMetaString('sepa-country'), + 'sepa-ep' => $importJournal->getMetaString('sepa-ep'), + 'sepa-ci' => $importJournal->getMetaString('sepa-ci'), + 'transactions' => [ + // single transaction: + [ + 'description' => null, + 'amount' => $amount, + 'currency_id' => intval($currencyId), + 'currency_code' => null, + 'foreign_amount' => $foreignAmount, + 'foreign_currency_id' => $foreignCurrencyId, + 'foreign_currency_code' => null, + 'budget_id' => is_null($budget) ? null : $budget->id, + 'budget_name' => null, + 'category_id' => is_null($category) ? null : $category->id, + 'category_name' => null, + 'source_id' => $source->id, + 'source_name' => null, + 'destination_id' => $destination->id, + 'destination_name' => null, + 'reconciled' => false, + 'identifier' => 0, + ], + ], + ]; + $factoryJournal = $this->factory->create($data); + $this->journals->push($factoryJournal); + } catch (FireflyException $e) { + Log::error(sprintf('Could not use factory to store journal: %s', $e->getMessage())); + Log::error($e->getTraceAsString()); } - // set notes for journal: - $dbNote = new Note(); - $dbNote->noteable()->associate($journal); - $dbNote->text = trim($importJournal->notes); - $dbNote->save(); - - // set journal completed: - $journal->completed = true; - $journal->save(); + $this->addStep(); $this->addStep(); // run rules if config calls for it: if (true === $this->applyRules) { Log::info('Will apply rules to this journal.'); - $this->applyRules($journal); + $this->applyRules($factoryJournal); } Preferences::setForUser($this->job->user, 'lastActivity', microtime()); @@ -268,7 +305,7 @@ class ImportStorage // match bills if config calls for it. if (true === $this->matchBills) { Log::info('Will match bills.'); - $this->matchBills($journal); + $this->matchBills($factoryJournal); } if (!(true === $this->matchBills)) { @@ -276,9 +313,14 @@ class ImportStorage } $this->addStep(); - $this->journals->push($journal); + $this->journals->push($factoryJournal); - Log::info(sprintf('Imported new journal #%d: "%s", amount %s %s.', $journal->id, $journal->description, $journal->transactionCurrency->code, $amount)); + Log::info( + sprintf( + 'Imported new journal #%d: "%s", amount %s %s.', $factoryJournal->id, $factoryJournal->description, $factoryJournal->transactionCurrency->code, + $amount + ) + ); return true; } diff --git a/app/Import/Storage/ImportSupport.php b/app/Import/Storage/ImportSupport.php index 9e3be430fd..8bbd0a454f 100644 --- a/app/Import/Storage/ImportSupport.php +++ b/app/Import/Storage/ImportSupport.php @@ -22,25 +22,19 @@ declare(strict_types=1); namespace FireflyIII\Import\Storage; -use Carbon\Carbon; -use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Import\Object\ImportAccount; use FireflyIII\Import\Object\ImportJournal; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Bill; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Category; use FireflyIII\Models\ImportJob; use FireflyIII\Models\Rule; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\TransactionRules\Processor; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; @@ -113,32 +107,6 @@ trait ImportSupport return true; } - /** - * @param array $parameters - * - * @return bool - * - * @throws FireflyException - */ - private function createTransaction(array $parameters): bool - { - $transaction = new Transaction; - $transaction->account_id = $parameters['account']; - $transaction->transaction_journal_id = intval($parameters['id']); - $transaction->transaction_currency_id = intval($parameters['currency']); - $transaction->amount = $parameters['amount']; - $transaction->foreign_currency_id = 0 === intval($parameters['foreign_currency']) ? null : intval($parameters['foreign_currency']); - $transaction->foreign_amount = null === $transaction->foreign_currency_id ? null : $parameters['foreign_amount']; - $transaction->save(); - if (null === $transaction->id) { - $errorText = join(', ', $transaction->getErrors()->all()); - throw new FireflyException($errorText); - } - Log::debug(sprintf('Created transaction with ID #%d, account #%d, amount %s', $transaction->id, $parameters['account'], $parameters['amount'])); - - return true; - } - /** * @return Collection */ @@ -196,7 +164,7 @@ trait ImportSupport private function getForeignCurrencyId(ImportJournal $importJournal, int $currencyId): ?int { // use given currency by import journal. - $currency = $importJournal->currency->getTransactionCurrency(); + $currency = $importJournal->foreignCurrency->getTransactionCurrency(); if (null !== $currency && intval($currency->id) !== intval($currencyId)) { return $currency->id; } @@ -379,139 +347,4 @@ trait ImportSupport return false; } - - /** - * @param TransactionJournal $journal - * @param Bill $bill - */ - private function storeBill(TransactionJournal $journal, Bill $bill) - { - if (null !== $bill) { - Log::debug(sprintf('Linked bill #%d to journal #%d', $bill->id, $journal->id)); - $journal->bill()->associate($bill); - $journal->save(); - } - } - - /** - * @param TransactionJournal $journal - * @param Budget $budget - */ - private function storeBudget(TransactionJournal $journal, Budget $budget) - { - if (null !== $budget) { - Log::debug(sprintf('Linked budget #%d to journal #%d', $budget->id, $journal->id)); - $journal->budgets()->save($budget); - } - } - - /** - * @param TransactionJournal $journal - * @param Category $category - */ - private function storeCategory(TransactionJournal $journal, Category $category) - { - if (null !== $category) { - Log::debug(sprintf('Linked category #%d to journal #%d', $category->id, $journal->id)); - $journal->categories()->save($category); - } - } - - /** - * @param array $parameters - * - * @return TransactionJournal - * - * @throws FireflyException - */ - private function storeJournal(array $parameters): TransactionJournal - { - // find transaction type: - $transactionType = TransactionType::whereType($parameters['type'])->first(); - - // create a journal: - $journal = new TransactionJournal; - $journal->user_id = $this->job->user_id; - $journal->transaction_type_id = $transactionType->id; - $journal->transaction_currency_id = $parameters['currency']; - $journal->description = $parameters['description']; - $journal->date = $parameters['date']; - $journal->order = 0; - $journal->tag_count = 0; - $journal->completed = false; - - if (!$journal->save()) { - $errorText = join(', ', $journal->getErrors()->all()); - // throw error - throw new FireflyException($errorText); - } - // save meta data: - $this->journalRepository->setMetaString($journal, 'importHash', $parameters['hash']); - Log::debug(sprintf('Created journal with ID #%d', $journal->id)); - - // create transactions: - $one = [ - 'id' => $journal->id, - 'account' => $parameters['asset']->id, - 'currency' => $parameters['currency'], - 'amount' => $parameters['amount'], - 'foreign_currency' => $parameters['foreign_currency'], - 'foreign_amount' => null === $parameters['foreign_currency'] ? null : $parameters['amount'], - ]; - $opposite = app('steam')->opposite($parameters['amount']); - $two = [ - 'id' => $journal->id, - 'account' => $parameters['opposing']->id, - 'currency' => $parameters['currency'], - 'amount' => $opposite, - 'foreign_currency' => $parameters['foreign_currency'], - 'foreign_amount' => null === $parameters['foreign_currency'] ? null : $opposite, - ]; - $this->createTransaction($one); - $this->createTransaction($two); - - return $journal; - } - - /** - * @param TransactionJournal $journal - * @param array $dates - */ - private function storeMetaDates(TransactionJournal $journal, array $dates) - { - // all other date fields as meta thing: - foreach ($dates as $name => $value) { - try { - $date = new Carbon($value); - $this->journalRepository->setMetaDate($journal, $name, $date); - } catch (Exception $e) { - // don't care, ignore: - Log::warning(sprintf('Could not parse "%s" into a valid Date object for field %s', $value, $name)); - } - } - } - - /** - * @param array $tags - * @param TransactionJournal $journal - */ - private function storeTags(array $tags, TransactionJournal $journal): void - { - $repository = app(TagRepositoryInterface::class); - $repository->setUser($journal->user); - - foreach ($tags as $tag) { - $dbTag = $repository->findByTag($tag); - if (null === $dbTag->id) { - $dbTag = $repository->store( - ['tag' => $tag, 'date' => null, 'description' => null, 'latitude' => null, 'longitude' => null, - 'zoomLevel' => null, 'tagMode' => 'nothing',] - ); - } - $journal->tags()->save($dbTag); - Log::debug(sprintf('Linked tag %d ("%s") to journal #%d', $dbTag->id, $dbTag->tag, $journal->id)); - } - - return; - } } diff --git a/app/Services/Internal/Support/TransactionServiceTrait.php b/app/Services/Internal/Support/TransactionServiceTrait.php index ce1ec06e65..f14da605c0 100644 --- a/app/Services/Internal/Support/TransactionServiceTrait.php +++ b/app/Services/Internal/Support/TransactionServiceTrait.php @@ -265,6 +265,7 @@ trait TransactionServiceTrait return; } + $transaction->foreign_currency_id = $currency->id; $transaction->save(); diff --git a/app/Support/Steam.php b/app/Support/Steam.php index e3453c7f2b..1c5301c9ef 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -290,10 +290,13 @@ class Steam /** * @param string $amount * - * @return string + * @return string|null */ - public function opposite(string $amount): string + public function opposite(string $amount = null): ?string { + if (is_null($amount)) { + return null; + } $amount = bcmul($amount, '-1'); return $amount;