2018-02-16 09:43:25 -06:00
|
|
|
<?php
|
2018-05-11 03:08:34 -05:00
|
|
|
|
2018-02-16 09:43:25 -06:00
|
|
|
/**
|
|
|
|
* TransactionJournalFactory.php
|
|
|
|
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
|
|
|
*
|
|
|
|
* This file is part of Firefly III.
|
|
|
|
*
|
|
|
|
* Firefly III is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* Firefly III is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2018-05-11 03:08:34 -05:00
|
|
|
declare(strict_types=1);
|
2018-02-16 09:43:25 -06:00
|
|
|
|
|
|
|
namespace FireflyIII\Factory;
|
|
|
|
|
2019-03-07 22:47:51 -06:00
|
|
|
use Carbon\Carbon;
|
|
|
|
use Exception;
|
2019-03-31 06:36:49 -05:00
|
|
|
use FireflyIII\Exceptions\FireflyException;
|
2019-03-17 11:05:16 -05:00
|
|
|
use FireflyIII\Models\Note;
|
|
|
|
use FireflyIII\Models\TransactionCurrency;
|
2018-02-16 09:43:25 -06:00
|
|
|
use FireflyIII\Models\TransactionJournal;
|
2019-03-17 11:05:16 -05:00
|
|
|
use FireflyIII\Models\TransactionType;
|
|
|
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
|
|
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
|
|
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
2019-03-07 22:47:51 -06:00
|
|
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
2019-03-17 11:05:16 -05:00
|
|
|
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
2019-03-07 22:47:51 -06:00
|
|
|
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
|
2019-03-17 11:05:16 -05:00
|
|
|
use FireflyIII\Support\NullArrayObject;
|
2018-02-16 09:43:25 -06:00
|
|
|
use FireflyIII\User;
|
2019-03-05 10:26:49 -06:00
|
|
|
use Illuminate\Support\Collection;
|
2018-02-23 08:12:47 -06:00
|
|
|
use Log;
|
2018-02-16 09:43:25 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class TransactionJournalFactory
|
|
|
|
*/
|
|
|
|
class TransactionJournalFactory
|
|
|
|
{
|
2019-03-17 11:05:16 -05:00
|
|
|
/** @var BillRepositoryInterface */
|
|
|
|
private $billRepository;
|
|
|
|
/** @var BudgetRepositoryInterface */
|
|
|
|
private $budgetRepository;
|
|
|
|
/** @var CategoryRepositoryInterface */
|
|
|
|
private $categoryRepository;
|
2019-03-07 22:47:51 -06:00
|
|
|
/** @var CurrencyRepositoryInterface */
|
|
|
|
private $currencyRepository;
|
2019-03-17 11:05:16 -05:00
|
|
|
/** @var array */
|
|
|
|
private $fields;
|
2019-03-18 10:52:49 -05:00
|
|
|
/** @var PiggyBankEventFactory */
|
|
|
|
private $piggyEventFactory;
|
2019-03-17 11:05:16 -05:00
|
|
|
/** @var PiggyBankRepositoryInterface */
|
|
|
|
private $piggyRepository;
|
2019-03-18 10:52:49 -05:00
|
|
|
/** @var TagFactory */
|
|
|
|
private $tagFactory;
|
2019-03-07 22:47:51 -06:00
|
|
|
/** @var TransactionFactory */
|
|
|
|
private $transactionFactory;
|
|
|
|
/** @var TransactionTypeRepositoryInterface */
|
|
|
|
private $typeRepository;
|
2018-09-22 23:53:15 -05:00
|
|
|
/** @var User The user */
|
|
|
|
private $user;
|
|
|
|
|
2018-09-06 05:29:32 -05:00
|
|
|
/**
|
|
|
|
* Constructor.
|
2019-03-17 11:05:16 -05:00
|
|
|
*
|
|
|
|
* @throws Exception
|
2018-09-06 05:29:32 -05:00
|
|
|
*/
|
|
|
|
public function __construct()
|
|
|
|
{
|
2019-03-31 06:36:49 -05:00
|
|
|
$this->fields = [
|
|
|
|
// sepa
|
|
|
|
'sepa_cc', 'sepa_ct_op', 'sepa_ct_id',
|
|
|
|
'sepa_db', 'sepa_country', 'sepa_ep',
|
|
|
|
'sepa_ci', 'sepa_batch_id',
|
|
|
|
|
|
|
|
// dates
|
|
|
|
'interest_date', 'book_date', 'process_date',
|
|
|
|
'due_date', 'payment_date', 'invoice_date',
|
|
|
|
|
|
|
|
// others
|
|
|
|
'recurrence_id', 'internal_reference', 'bunq_payment_id',
|
|
|
|
'import_hash', 'import_hash_v2', 'external_id', 'original_source'];
|
2019-03-17 11:05:16 -05:00
|
|
|
|
|
|
|
|
2018-12-15 00:59:02 -06:00
|
|
|
if ('testing' === config('app.env')) {
|
2018-09-06 05:29:32 -05:00
|
|
|
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
|
|
|
|
}
|
2019-03-31 06:36:49 -05:00
|
|
|
|
2019-03-07 22:47:51 -06:00
|
|
|
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
|
|
|
|
$this->typeRepository = app(TransactionTypeRepositoryInterface::class);
|
|
|
|
$this->transactionFactory = app(TransactionFactory::class);
|
2019-03-17 11:05:16 -05:00
|
|
|
$this->billRepository = app(BillRepositoryInterface::class);
|
|
|
|
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
|
|
|
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
|
|
|
$this->piggyRepository = app(PiggyBankRepositoryInterface::class);
|
2019-03-18 10:52:49 -05:00
|
|
|
$this->piggyEventFactory = app(PiggyBankEventFactory::class);
|
|
|
|
$this->tagFactory = app(TagFactory::class);
|
2018-09-06 05:29:32 -05:00
|
|
|
}
|
|
|
|
|
2018-02-16 09:43:25 -06:00
|
|
|
/**
|
2019-03-31 06:36:49 -05:00
|
|
|
* Store a new (set of) transaction journals.
|
2018-07-06 12:06:08 -05:00
|
|
|
*
|
2018-02-16 09:43:25 -06:00
|
|
|
* @param array $data
|
|
|
|
*
|
2019-03-31 06:36:49 -05:00
|
|
|
* @return Collection
|
|
|
|
* @throws FireflyException
|
2018-02-16 09:43:25 -06:00
|
|
|
*/
|
2019-03-31 06:36:49 -05:00
|
|
|
public function create(array $data): Collection
|
2018-02-16 09:43:25 -06:00
|
|
|
{
|
2019-03-31 06:36:49 -05:00
|
|
|
// convert to special object.
|
2019-03-18 10:52:49 -05:00
|
|
|
$data = new NullArrayObject($data);
|
2019-03-31 06:36:49 -05:00
|
|
|
|
2018-02-23 08:12:47 -06:00
|
|
|
Log::debug('Start of TransactionJournalFactory::create()');
|
2019-03-07 22:47:51 -06:00
|
|
|
$collection = new Collection;
|
|
|
|
$transactions = $data['transactions'] ?? [];
|
2019-03-31 06:36:49 -05:00
|
|
|
if (0 === count($transactions)) {
|
2019-03-18 10:52:49 -05:00
|
|
|
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
|
2019-03-17 11:05:16 -05:00
|
|
|
|
|
|
|
return new Collection;
|
|
|
|
}
|
2018-02-18 09:35:26 -06:00
|
|
|
|
2019-03-17 11:05:16 -05:00
|
|
|
/** @var array $row */
|
|
|
|
foreach ($transactions as $index => $row) {
|
2019-03-31 06:36:49 -05:00
|
|
|
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
|
2019-03-18 10:52:49 -05:00
|
|
|
|
2019-03-31 06:36:49 -05:00
|
|
|
$journal = $this->createJournal(new NullArrayObject($row));
|
|
|
|
if (null !== $journal) {
|
|
|
|
$collection->push($journal);
|
2019-03-18 10:52:49 -05:00
|
|
|
}
|
2019-03-17 11:05:16 -05:00
|
|
|
}
|
2019-03-23 02:10:59 -05:00
|
|
|
|
2019-03-31 06:36:49 -05:00
|
|
|
return $collection;
|
2018-02-16 09:43:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the user.
|
|
|
|
*
|
|
|
|
* @param User $user
|
|
|
|
*/
|
|
|
|
public function setUser(User $user): void
|
|
|
|
{
|
|
|
|
$this->user = $user;
|
2019-03-07 22:47:51 -06:00
|
|
|
$this->currencyRepository->setUser($this->user);
|
|
|
|
$this->transactionFactory->setUser($this->user);
|
2019-03-17 11:05:16 -05:00
|
|
|
$this->billRepository->setUser($this->user);
|
|
|
|
$this->budgetRepository->setUser($this->user);
|
|
|
|
$this->categoryRepository->setUser($this->user);
|
|
|
|
$this->piggyRepository->setUser($this->user);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Link a piggy bank to this journal.
|
|
|
|
*
|
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param NullArrayObject $data
|
|
|
|
*/
|
|
|
|
public function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void
|
|
|
|
{
|
|
|
|
Log::debug('Will now store piggy event.');
|
|
|
|
if (!$journal->isTransfer()) {
|
|
|
|
Log::debug('Journal is not a transfer, do nothing.');
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:52:49 -05:00
|
|
|
$piggyBank = $this->piggyRepository->findPiggyBank($data['piggy_bank'], (int)$data['piggy_bank_id'], $data['piggy_bank_name']);
|
2019-03-17 11:05:16 -05:00
|
|
|
|
|
|
|
if (null !== $piggyBank) {
|
2019-03-18 10:52:49 -05:00
|
|
|
$this->piggyEventFactory->create($journal, $piggyBank);
|
2019-03-17 11:05:16 -05:00
|
|
|
Log::debug('Create piggy event.');
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Log::debug('Create no piggy event');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Link tags to journal.
|
|
|
|
*
|
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param array $tags
|
|
|
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
|
|
|
*/
|
|
|
|
public function storeTags(TransactionJournal $journal, ?array $tags): void
|
|
|
|
{
|
2019-03-18 10:52:49 -05:00
|
|
|
$this->tagFactory->setUser($journal->user);
|
2019-03-17 11:05:16 -05:00
|
|
|
$set = [];
|
|
|
|
if (!\is_array($tags)) {
|
2019-03-18 10:52:49 -05:00
|
|
|
return;
|
2019-03-17 11:05:16 -05:00
|
|
|
}
|
|
|
|
foreach ($tags as $string) {
|
|
|
|
if ('' !== $string) {
|
2019-03-18 10:52:49 -05:00
|
|
|
$tag = $this->tagFactory->findOrCreate($string);
|
2019-03-17 11:05:16 -05:00
|
|
|
if (null !== $tag) {
|
|
|
|
$set[] = $tag->id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$journal->tags()->sync($set);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param NullArrayObject $data
|
|
|
|
* @param string $field
|
|
|
|
*/
|
|
|
|
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
|
|
|
|
{
|
|
|
|
$set = [
|
|
|
|
'journal' => $journal,
|
|
|
|
'name' => $field,
|
|
|
|
'data' => (string)($data[$field] ?? ''),
|
|
|
|
];
|
|
|
|
|
|
|
|
Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
|
|
|
|
|
|
|
|
/** @var TransactionJournalMetaFactory $factory */
|
|
|
|
$factory = app(TransactionJournalMetaFactory::class);
|
|
|
|
$factory->updateOrCreate($set);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param string $notes
|
|
|
|
*/
|
|
|
|
protected function storeNote(TransactionJournal $journal, ?string $notes): void
|
|
|
|
{
|
|
|
|
$notes = (string)$notes;
|
|
|
|
if ('' !== $notes) {
|
|
|
|
$note = $journal->notes()->first();
|
|
|
|
if (null === $note) {
|
|
|
|
$note = new Note;
|
|
|
|
$note->noteable()->associate($journal);
|
|
|
|
}
|
|
|
|
$note->text = $notes;
|
|
|
|
$note->save();
|
|
|
|
Log::debug(sprintf('Stored notes for journal #%d', $journal->id));
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-31 06:36:49 -05:00
|
|
|
/**
|
|
|
|
* @param NullArrayObject $row
|
|
|
|
*
|
|
|
|
* @return TransactionJournal|null
|
|
|
|
* @throws FireflyException
|
|
|
|
*/
|
|
|
|
private function createJournal(NullArrayObject $row): ?TransactionJournal
|
|
|
|
{
|
|
|
|
$row['import_hash_v2'] = $this->hashArray($row);
|
|
|
|
|
|
|
|
/** Get basic fields */
|
|
|
|
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
|
|
|
$carbon = $row['date'] ?? new Carbon;
|
|
|
|
$currency = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']);
|
|
|
|
$foreignCurrency = $this->findForeignCurrency($row);
|
|
|
|
$bill = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']);
|
|
|
|
$billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null;
|
|
|
|
$description = app('steam')->cleanString((string)$row['description']);
|
|
|
|
|
|
|
|
/** Manipulate basic fields */
|
|
|
|
$carbon->setTimezone(config('app.timezone'));
|
|
|
|
|
|
|
|
/** Create a basic journal. */
|
|
|
|
$journal = TransactionJournal::create(
|
|
|
|
[
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
'transaction_type_id' => $type->id,
|
|
|
|
'bill_id' => $billId,
|
|
|
|
'transaction_currency_id' => $currency->id,
|
|
|
|
'description' => '' === $description ? '(empty description)' : $description,
|
|
|
|
'date' => $carbon->format('Y-m-d H:i:s'),
|
|
|
|
'order' => 0,
|
|
|
|
'tag_count' => 0,
|
|
|
|
'completed' => 0,
|
|
|
|
]
|
|
|
|
);
|
|
|
|
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
|
|
|
|
|
|
|
|
/** Create two transactions. */
|
|
|
|
$this->transactionFactory->setJournal($journal);
|
|
|
|
$this->transactionFactory->createPair($row, $currency, $foreignCurrency);
|
|
|
|
|
|
|
|
// verify that journal has two transactions. Otherwise, delete and cancel.
|
|
|
|
$count = $journal->transactions()->count();
|
|
|
|
if (2 !== $count) {
|
|
|
|
// @codeCoverageIgnoreStart
|
|
|
|
Log::error(sprintf('The journal unexpectedly has %d transaction(s). This is not OK. Cancel operation.', $count));
|
|
|
|
try {
|
|
|
|
$journal->delete();
|
|
|
|
} catch (Exception $e) {
|
|
|
|
Log::debug(sprintf('Dont care: %s.', $e->getMessage()));
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
}
|
|
|
|
$journal->completed = true;
|
|
|
|
$journal->save();
|
|
|
|
|
|
|
|
/** Link all other data to the journal. */
|
|
|
|
|
|
|
|
/** Link budget */
|
|
|
|
$this->storeBudget($journal, $row);
|
|
|
|
|
|
|
|
/** Link category */
|
|
|
|
$this->storeCategory($journal, $row);
|
|
|
|
|
|
|
|
/** Set notes */
|
|
|
|
$this->storeNote($journal, $row['notes']);
|
|
|
|
|
|
|
|
/** Set piggy bank */
|
|
|
|
$this->storePiggyEvent($journal, $row);
|
|
|
|
|
|
|
|
/** Set tags */
|
|
|
|
$this->storeTags($journal, $row['tags']);
|
|
|
|
|
|
|
|
/** set all meta fields */
|
|
|
|
$this->storeMetaFields($journal, $row);
|
|
|
|
|
|
|
|
return $journal;
|
|
|
|
}
|
|
|
|
|
2019-03-17 11:05:16 -05:00
|
|
|
/**
|
|
|
|
* This is a separate function because "findCurrency" will default to EUR and that may not be what we want.
|
|
|
|
*
|
|
|
|
* @param NullArrayObject $transaction
|
|
|
|
*
|
|
|
|
* @return TransactionCurrency|null
|
|
|
|
*/
|
|
|
|
private function findForeignCurrency(NullArrayObject $transaction): ?TransactionCurrency
|
|
|
|
{
|
|
|
|
if (null === $transaction['foreign_currency'] && null === $transaction['foreign_currency_id'] && null === $transaction['foreign_currency_code']) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-03-31 06:36:49 -05:00
|
|
|
return $this->currencyRepository->findCurrency((int)$transaction['foreign_currency_id'], $transaction['foreign_currency_code']);
|
2019-03-17 11:05:16 -05:00
|
|
|
}
|
|
|
|
|
2019-03-31 06:36:49 -05:00
|
|
|
/**
|
|
|
|
* @param NullArrayObject $row
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function hashArray(NullArrayObject $row): string
|
|
|
|
{
|
|
|
|
$row['import_hash_v2'] = null;
|
|
|
|
$row['original_source'] = null;
|
|
|
|
$json = json_encode($row);
|
|
|
|
if (false === $json) {
|
|
|
|
$json = json_encode((string)microtime());
|
|
|
|
}
|
|
|
|
$hash = hash('sha256', $json);
|
|
|
|
Log::debug(sprintf('The hash is: %s', $hash));
|
|
|
|
|
|
|
|
return $hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-17 11:05:16 -05:00
|
|
|
/**
|
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param NullArrayObject $data
|
|
|
|
*/
|
|
|
|
private function storeBudget(TransactionJournal $journal, NullArrayObject $data): void
|
|
|
|
{
|
2019-03-31 06:36:49 -05:00
|
|
|
if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) {
|
|
|
|
return;
|
|
|
|
}
|
2019-03-17 11:05:16 -05:00
|
|
|
$budget = $this->budgetRepository->findBudget($data['budget'], $data['budget_id'], $data['budget_name']);
|
|
|
|
if (null !== $budget) {
|
|
|
|
Log::debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id));
|
|
|
|
$journal->budgets()->sync([$budget->id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param NullArrayObject $data
|
|
|
|
*/
|
|
|
|
private function storeCategory(TransactionJournal $journal, NullArrayObject $data): void
|
|
|
|
{
|
|
|
|
$category = $this->categoryRepository->findCategory($data['category'], $data['category_id'], $data['category_name']);
|
|
|
|
if (null !== $category) {
|
|
|
|
Log::debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id));
|
|
|
|
$journal->categories()->sync([$category->id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param TransactionJournal $journal
|
|
|
|
* @param NullArrayObject $transaction
|
|
|
|
*/
|
|
|
|
private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void
|
|
|
|
{
|
|
|
|
foreach ($this->fields as $field) {
|
|
|
|
$this->storeMeta($journal, $transaction, $field);
|
|
|
|
}
|
2018-02-16 09:43:25 -06:00
|
|
|
}
|
|
|
|
|
2019-03-05 10:26:49 -06:00
|
|
|
|
2018-03-05 12:35:58 -06:00
|
|
|
}
|