mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-18 10:39:28 +00:00
645 lines
27 KiB
PHP
645 lines
27 KiB
PHP
<?php
|
|
|
|
/**
|
|
* TransactionJournalFactory.php
|
|
* Copyright (c) 2019 james@firefly-iii.org
|
|
*
|
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace FireflyIII\Factory;
|
|
|
|
use Carbon\Carbon;
|
|
use FireflyIII\Enums\AccountTypeEnum;
|
|
use FireflyIII\Enums\TransactionTypeEnum;
|
|
use FireflyIII\Exceptions\DuplicateTransactionException;
|
|
use FireflyIII\Exceptions\FireflyException;
|
|
use FireflyIII\Models\Account;
|
|
use FireflyIII\Models\Location;
|
|
use FireflyIII\Models\Transaction;
|
|
use FireflyIII\Models\TransactionCurrency;
|
|
use FireflyIII\Models\TransactionJournal;
|
|
use FireflyIII\Models\TransactionJournalMeta;
|
|
use FireflyIII\Models\UserGroup;
|
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
|
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
|
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
|
|
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
|
|
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
|
|
use FireflyIII\Support\Facades\FireflyConfig;
|
|
use FireflyIII\Support\NullArrayObject;
|
|
use FireflyIII\User;
|
|
use FireflyIII\Validation\AccountValidator;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Class TransactionJournalFactory
|
|
*
|
|
* @SuppressWarnings("PHPMD.CouplingBetweenObjects")
|
|
*/
|
|
class TransactionJournalFactory
|
|
{
|
|
use JournalServiceTrait;
|
|
|
|
private AccountRepositoryInterface $accountRepository;
|
|
private AccountValidator $accountValidator;
|
|
private BillRepositoryInterface $billRepository;
|
|
private CurrencyRepositoryInterface $currencyRepository;
|
|
private bool $errorOnHash;
|
|
private array $fields;
|
|
private PiggyBankEventFactory $piggyEventFactory;
|
|
private PiggyBankRepositoryInterface $piggyRepository;
|
|
private TransactionTypeRepositoryInterface $typeRepository;
|
|
private User $user;
|
|
private UserGroup $userGroup;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->errorOnHash = false;
|
|
$this->fields = config('firefly.journal_meta_fields');
|
|
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
|
|
$this->typeRepository = app(TransactionTypeRepositoryInterface::class);
|
|
$this->billRepository = app(BillRepositoryInterface::class);
|
|
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
|
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
|
$this->piggyRepository = app(PiggyBankRepositoryInterface::class);
|
|
$this->piggyEventFactory = app(PiggyBankEventFactory::class);
|
|
$this->tagFactory = app(TagFactory::class);
|
|
$this->accountValidator = app(AccountValidator::class);
|
|
$this->accountRepository = app(AccountRepositoryInterface::class);
|
|
}
|
|
|
|
/**
|
|
* Store a new (set of) transaction journals.
|
|
*
|
|
* @throws DuplicateTransactionException
|
|
* @throws FireflyException
|
|
*/
|
|
public function create(array $data): Collection
|
|
{
|
|
Log::debug('Now in TransactionJournalFactory::create()');
|
|
// convert to special object.
|
|
$dataObject = new NullArrayObject($data);
|
|
|
|
Log::debug('Start of TransactionJournalFactory::create()');
|
|
$collection = new Collection();
|
|
$transactions = $dataObject['transactions'] ?? [];
|
|
if (0 === count($transactions)) {
|
|
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
|
|
|
|
return new Collection();
|
|
}
|
|
|
|
try {
|
|
/** @var array $row */
|
|
foreach ($transactions as $index => $row) {
|
|
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
|
|
$journal = $this->createJournal(new NullArrayObject($row));
|
|
if (null !== $journal) {
|
|
$collection->push($journal);
|
|
}
|
|
if (null === $journal) {
|
|
Log::error('The createJournal() method returned NULL. This may indicate an error.');
|
|
}
|
|
}
|
|
} catch (DuplicateTransactionException $e) {
|
|
Log::warning('TransactionJournalFactory::create() caught a duplicate journal in createJournal()');
|
|
Log::error($e->getMessage());
|
|
Log::error($e->getTraceAsString());
|
|
$this->forceDeleteOnError($collection);
|
|
|
|
throw new DuplicateTransactionException($e->getMessage(), 0, $e);
|
|
} catch (FireflyException $e) {
|
|
Log::warning('TransactionJournalFactory::create() caught an exception.');
|
|
Log::error($e->getMessage());
|
|
Log::error($e->getTraceAsString());
|
|
$this->forceDeleteOnError($collection);
|
|
|
|
throw new FireflyException($e->getMessage(), 0, $e);
|
|
}
|
|
|
|
return $collection;
|
|
}
|
|
|
|
/**
|
|
* TODO typeOverrule: the account validator may have another opinion on the transaction type. not sure what to do
|
|
* with this.
|
|
*
|
|
* @throws DuplicateTransactionException
|
|
* @throws FireflyException
|
|
*
|
|
* @SuppressWarnings("PHPMD.ExcessiveMethodLength")
|
|
*/
|
|
private function createJournal(NullArrayObject $row): ?TransactionJournal
|
|
{
|
|
Log::debug('Now in TransactionJournalFactory::createJournal()');
|
|
$row['import_hash_v2'] = $this->hashArray($row);
|
|
|
|
$this->errorIfDuplicate($row['import_hash_v2']);
|
|
|
|
// Some basic fields
|
|
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
|
$carbon = $row['date'] ?? today(config('app.timezone'));
|
|
$order = $row['order'] ?? 0;
|
|
|
|
Log::debug('Find currency or return default.');
|
|
$currency = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']);
|
|
Log::debug('Find foreign currency or return NULL.');
|
|
$foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']);
|
|
$bill = $this->billRepository->findBill((int) $row['bill_id'], $row['bill_name']);
|
|
$billId = TransactionTypeEnum::WITHDRAWAL->value === $type->type && null !== $bill ? $bill->id : null;
|
|
$description = (string) $row['description'];
|
|
|
|
// Manipulate basic fields
|
|
$carbon->setTimezone(config('app.timezone'));
|
|
|
|
// 2024-11-19, overrule timezone with UTC and store it as UTC.
|
|
|
|
if (true === FireflyConfig::get('utc', false)->data) {
|
|
$carbon->setTimezone('UTC');
|
|
}
|
|
|
|
try {
|
|
// validate source and destination using a new Validator.
|
|
$this->validateAccounts($row);
|
|
} catch (FireflyException $e) {
|
|
Log::error('Could not validate source or destination.');
|
|
Log::error($e->getMessage());
|
|
|
|
return null;
|
|
}
|
|
|
|
/** create or get source and destination accounts */
|
|
$sourceInfo = [
|
|
'id' => $row['source_id'],
|
|
'name' => $row['source_name'],
|
|
'iban' => $row['source_iban'],
|
|
'number' => $row['source_number'],
|
|
'bic' => $row['source_bic'],
|
|
'currency_id' => $currency->id,
|
|
];
|
|
|
|
$destInfo = [
|
|
'id' => $row['destination_id'],
|
|
'name' => $row['destination_name'],
|
|
'iban' => $row['destination_iban'],
|
|
'number' => $row['destination_number'],
|
|
'bic' => $row['destination_bic'],
|
|
'currency_id' => $currency->id,
|
|
];
|
|
Log::debug('Source info:', $sourceInfo);
|
|
Log::debug('Destination info:', $destInfo);
|
|
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
|
|
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
|
|
Log::debug('Done with getAccount(2x)');
|
|
|
|
// this is the moment for a reconciliation sanity check (again).
|
|
if (TransactionTypeEnum::RECONCILIATION->value === $type->type) {
|
|
[$sourceAccount, $destinationAccount] = $this->reconciliationSanityCheck($sourceAccount, $destinationAccount);
|
|
}
|
|
|
|
$currency = $this->getCurrencyByAccount($type->type, $currency, $sourceAccount, $destinationAccount);
|
|
$foreignCurrency = $this->compareCurrencies($currency, $foreignCurrency);
|
|
$foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount);
|
|
$description = $this->getDescription($description);
|
|
|
|
Log::debug(sprintf('Currency is #%d "%s", foreign currency is #%d "%s"', $currency->id, $currency->code, $foreignCurrency?->id, $foreignCurrency));
|
|
Log::debug(sprintf('Date: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()->getName()));
|
|
|
|
/** Create a basic journal. */
|
|
$journal = TransactionJournal::create(
|
|
[
|
|
'user_id' => $this->user->id,
|
|
'user_group_id' => $this->userGroup->id,
|
|
'transaction_type_id' => $type->id,
|
|
'bill_id' => $billId,
|
|
'transaction_currency_id' => $currency->id,
|
|
'description' => substr($description, 0, 1000),
|
|
'date' => $carbon,
|
|
'date_tz' => $carbon->format('e'),
|
|
'order' => $order,
|
|
'tag_count' => 0,
|
|
'completed' => 0,
|
|
]
|
|
);
|
|
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
|
|
|
|
/** Create two transactions. */
|
|
$transactionFactory = app(TransactionFactory::class);
|
|
$transactionFactory->setJournal($journal);
|
|
$transactionFactory->setAccount($sourceAccount);
|
|
$transactionFactory->setCurrency($currency);
|
|
$transactionFactory->setAccountInformation($sourceInfo);
|
|
$transactionFactory->setForeignCurrency($foreignCurrency);
|
|
$transactionFactory->setReconciled($row['reconciled'] ?? false);
|
|
|
|
try {
|
|
$negative = $transactionFactory->createNegative((string) $row['amount'], (string) $row['foreign_amount']);
|
|
} catch (FireflyException $e) {
|
|
Log::error(sprintf('Exception creating negative transaction: %s', $e->getMessage()));
|
|
$this->forceDeleteOnError(new Collection([$journal]));
|
|
|
|
throw new FireflyException($e->getMessage(), 0, $e);
|
|
}
|
|
|
|
/** @var TransactionFactory $transactionFactory */
|
|
$transactionFactory = app(TransactionFactory::class);
|
|
$transactionFactory->setJournal($journal);
|
|
$transactionFactory->setAccount($destinationAccount);
|
|
$transactionFactory->setAccountInformation($destInfo);
|
|
$transactionFactory->setCurrency($currency);
|
|
$transactionFactory->setForeignCurrency($foreignCurrency);
|
|
$transactionFactory->setReconciled($row['reconciled'] ?? false);
|
|
|
|
// if the foreign currency is set and is different, and the transaction type is a transfer,
|
|
// Firefly III will save the foreign currency information in such a way that both
|
|
// asset accounts can look at the "amount" and "transaction_currency_id" column and
|
|
// see the currency they expect to see.
|
|
$amount = (string) $row['amount'];
|
|
$foreignAmount = (string) $row['foreign_amount'];
|
|
if (null !== $foreignCurrency && $foreignCurrency->id !== $currency->id
|
|
&& TransactionTypeEnum::TRANSFER->value === $type->type
|
|
) {
|
|
$transactionFactory->setCurrency($foreignCurrency);
|
|
$transactionFactory->setForeignCurrency($currency);
|
|
$amount = (string) $row['foreign_amount'];
|
|
$foreignAmount = (string) $row['amount'];
|
|
Log::debug('Swap native/foreign amounts in transfer for new save method.');
|
|
}
|
|
|
|
try {
|
|
$transactionFactory->createPositive($amount, $foreignAmount);
|
|
} catch (FireflyException $e) {
|
|
Log::error(sprintf('Exception creating positive transaction: %s', $e->getMessage()));
|
|
$this->forceTrDelete($negative);
|
|
$this->forceDeleteOnError(new Collection([$journal]));
|
|
|
|
throw new FireflyException($e->getMessage(), 0, $e);
|
|
}
|
|
$journal->completed = true;
|
|
$journal->save();
|
|
$this->storeBudget($journal, $row);
|
|
$this->storeCategory($journal, $row);
|
|
$this->storeNotes($journal, $row['notes']);
|
|
$this->storePiggyEvent($journal, $row);
|
|
$this->storeTags($journal, $row['tags']);
|
|
$this->storeMetaFields($journal, $row);
|
|
$this->storeLocation($journal, $row);
|
|
|
|
return $journal;
|
|
}
|
|
|
|
private function hashArray(NullArrayObject $row): string
|
|
{
|
|
$dataRow = $row->getArrayCopy();
|
|
|
|
unset($dataRow['import_hash_v2'], $dataRow['original_source']);
|
|
|
|
try {
|
|
$json = \Safe\json_encode($dataRow, JSON_THROW_ON_ERROR);
|
|
} catch (\JsonException $e) {
|
|
Log::error(sprintf('Could not encode dataRow: %s', $e->getMessage()));
|
|
$json = microtime();
|
|
}
|
|
$hash = hash('sha256', $json);
|
|
Log::debug(sprintf('The hash is: %s', $hash), $dataRow);
|
|
|
|
return $hash;
|
|
}
|
|
|
|
/**
|
|
* If this transaction already exists, throw an error.
|
|
*
|
|
* @throws DuplicateTransactionException
|
|
*/
|
|
private function errorIfDuplicate(string $hash): void
|
|
{
|
|
Log::debug(sprintf('In errorIfDuplicate(%s)', $hash));
|
|
if (false === $this->errorOnHash) {
|
|
return;
|
|
}
|
|
Log::debug('Will verify duplicate!');
|
|
|
|
/** @var null|TransactionJournalMeta $result */
|
|
$result = TransactionJournalMeta::withTrashed()
|
|
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
|
->whereNotNull('transaction_journals.id')
|
|
->where('transaction_journals.user_id', $this->user->id)
|
|
->where('data', \Safe\json_encode($hash, JSON_THROW_ON_ERROR))
|
|
->with(['transactionJournal', 'transactionJournal.transactionGroup'])
|
|
->first(['journal_meta.*']);
|
|
if (null !== $result) {
|
|
Log::warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash));
|
|
$journal = $result->transactionJournal()->withTrashed()->first();
|
|
$group = $journal?->transactionGroup()->withTrashed()->first();
|
|
$groupId = (int) $group?->id;
|
|
|
|
throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $groupId));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws FireflyException
|
|
*/
|
|
private function validateAccounts(NullArrayObject $data): void
|
|
{
|
|
Log::debug(sprintf('Now in %s', __METHOD__));
|
|
$transactionType = $data['type'] ?? 'invalid';
|
|
$this->accountValidator->setUser($this->user);
|
|
$this->accountValidator->setTransactionType($transactionType);
|
|
|
|
// validate source account.
|
|
$array = [
|
|
'id' => null !== $data['source_id'] ? (int) $data['source_id'] : null,
|
|
'name' => null !== $data['source_name'] ? (string) $data['source_name'] : null,
|
|
'iban' => null !== $data['source_iban'] ? (string) $data['source_iban'] : null,
|
|
'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null,
|
|
];
|
|
$validSource = $this->accountValidator->validateSource($array);
|
|
|
|
// do something with result:
|
|
if (false === $validSource) {
|
|
throw new FireflyException(sprintf('Source: %s', $this->accountValidator->sourceError));
|
|
}
|
|
Log::debug('Source seems valid.');
|
|
|
|
// validate destination account
|
|
$array = [
|
|
'id' => null !== $data['destination_id'] ? (int) $data['destination_id'] : null,
|
|
'name' => null !== $data['destination_name'] ? (string) $data['destination_name'] : null,
|
|
'iban' => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null,
|
|
'number' => null !== $data['destination_number'] ? (string) $data['destination_number'] : null,
|
|
];
|
|
|
|
$validDestination = $this->accountValidator->validateDestination($array);
|
|
// do something with result:
|
|
if (false === $validDestination) {
|
|
throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the user.
|
|
*/
|
|
public function setUser(User $user): void
|
|
{
|
|
$this->user = $user;
|
|
$this->userGroup = $user->userGroup;
|
|
$this->currencyRepository->setUser($this->user);
|
|
$this->tagFactory->setUser($user);
|
|
$this->billRepository->setUser($this->user);
|
|
$this->budgetRepository->setUser($this->user);
|
|
$this->categoryRepository->setUser($this->user);
|
|
$this->piggyRepository->setUser($this->user);
|
|
$this->accountRepository->setUser($this->user);
|
|
}
|
|
|
|
private function reconciliationSanityCheck(?Account $sourceAccount, ?Account $destinationAccount): array
|
|
{
|
|
Log::debug(sprintf('Now in %s', __METHOD__));
|
|
if (null !== $sourceAccount && null !== $destinationAccount) {
|
|
Log::debug('Both accounts exist, simply return them.');
|
|
|
|
return [$sourceAccount, $destinationAccount];
|
|
}
|
|
if (null === $destinationAccount) {
|
|
Log::debug('Destination account is NULL, source account is not.');
|
|
$account = $this->accountRepository->getReconciliation($sourceAccount);
|
|
Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type));
|
|
|
|
return [$sourceAccount, $account];
|
|
}
|
|
|
|
if (null === $sourceAccount) { // @phpstan-ignore-line
|
|
Log::debug('Source account is NULL, destination account is not.');
|
|
$account = $this->accountRepository->getReconciliation($destinationAccount);
|
|
Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type));
|
|
|
|
return [$account, $destinationAccount];
|
|
}
|
|
Log::debug('Unused fallback'); // @phpstan-ignore-line
|
|
|
|
return [$sourceAccount, $destinationAccount];
|
|
}
|
|
|
|
/**
|
|
* @throws FireflyException
|
|
*/
|
|
private function getCurrencyByAccount(string $type, ?TransactionCurrency $currency, Account $source, Account $destination): TransactionCurrency
|
|
{
|
|
Log::debug('Now in getCurrencyByAccount()');
|
|
|
|
/*
|
|
* Deze functie moet bij een transactie van liability naar asset wel degelijk de currency
|
|
* van de liability teruggeven en niet die van de destination. Fix voor #10265
|
|
*/
|
|
if ($this->isBetweenAssetAndLiability($source, $destination) && TransactionTypeEnum::DEPOSIT->value === $type) {
|
|
return $this->getCurrency($currency, $source);
|
|
}
|
|
|
|
return match ($type) {
|
|
default => $this->getCurrency($currency, $source),
|
|
TransactionTypeEnum::DEPOSIT->value => $this->getCurrency($currency, $destination),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @throws FireflyException
|
|
*/
|
|
private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency
|
|
{
|
|
Log::debug(sprintf('Now in getCurrency(#%d, "%s")', $currency->id, $account->name));
|
|
|
|
/** @var null|TransactionCurrency $preference */
|
|
$preference = $this->accountRepository->getAccountCurrency($account);
|
|
if (null === $preference && null === $currency) {
|
|
// return user's default:
|
|
return app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
|
|
}
|
|
$result = $preference ?? $currency;
|
|
Log::debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name));
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Set foreign currency to NULL if it's the same as the normal currency:
|
|
*/
|
|
private function compareCurrencies(?TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): ?TransactionCurrency
|
|
{
|
|
Log::debug(sprintf('Now in compareCurrencies("%s", "%s")', $currency?->code, $foreignCurrency?->code));
|
|
if (null === $currency) {
|
|
return null;
|
|
}
|
|
if (null !== $foreignCurrency && $foreignCurrency->id === $currency->id) {
|
|
return null;
|
|
}
|
|
|
|
return $foreignCurrency;
|
|
}
|
|
|
|
/**
|
|
* @throws FireflyException
|
|
*/
|
|
private function getForeignByAccount(string $type, ?TransactionCurrency $foreignCurrency, Account $destination): ?TransactionCurrency
|
|
{
|
|
Log::debug(sprintf('Now in getForeignByAccount("%s", #%d, "%s")', $type, $foreignCurrency?->id, $destination->name));
|
|
if (TransactionTypeEnum::TRANSFER->value === $type) {
|
|
return $this->getCurrency($foreignCurrency, $destination);
|
|
}
|
|
|
|
return $foreignCurrency;
|
|
}
|
|
|
|
private function getDescription(string $description): string
|
|
{
|
|
$description = '' === $description ? '(empty description)' : $description;
|
|
|
|
return substr($description, 0, 1024);
|
|
}
|
|
|
|
/**
|
|
* Force the deletion of an entire set of transaction journals and their meta object in case of
|
|
* an error creating a group.
|
|
*/
|
|
private function forceDeleteOnError(Collection $collection): void
|
|
{
|
|
Log::debug(sprintf('forceDeleteOnError on collection size %d item(s)', $collection->count()));
|
|
$service = app(JournalDestroyService::class);
|
|
|
|
/** @var TransactionJournal $journal */
|
|
foreach ($collection as $journal) {
|
|
Log::debug(sprintf('forceDeleteOnError on journal #%d', $journal->id));
|
|
$service->destroy($journal);
|
|
}
|
|
}
|
|
|
|
private function forceTrDelete(Transaction $transaction): void
|
|
{
|
|
$transaction->delete();
|
|
}
|
|
|
|
/**
|
|
* Link a piggy bank to this journal.
|
|
*/
|
|
private function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void
|
|
{
|
|
Log::debug('Will now store piggy event.');
|
|
|
|
$piggyBank = $this->piggyRepository->findPiggyBank((int) $data['piggy_bank_id'], $data['piggy_bank_name']);
|
|
|
|
if (null !== $piggyBank) {
|
|
$this->piggyEventFactory->create($journal, $piggyBank);
|
|
Log::debug('Create piggy event.');
|
|
|
|
return;
|
|
}
|
|
Log::debug('Create no piggy event');
|
|
}
|
|
|
|
private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void
|
|
{
|
|
foreach ($this->fields as $field) {
|
|
$this->storeMeta($journal, $transaction, $field);
|
|
}
|
|
}
|
|
|
|
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
|
|
{
|
|
$set = [
|
|
'journal' => $journal,
|
|
'name' => $field,
|
|
'data' => (string) ($data[$field] ?? ''),
|
|
];
|
|
if ($data[$field] instanceof Carbon) {
|
|
$data[$field]->setTimezone(config('app.timezone'));
|
|
Log::debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
|
|
$set['data'] = $data[$field]->format('Y-m-d H:i:s');
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void
|
|
{
|
|
if (null !== $data['longitude'] && null !== $data['latitude'] && null !== $data['zoom_level']) {
|
|
$location = new Location();
|
|
$location->longitude = $data['longitude'];
|
|
$location->latitude = $data['latitude'];
|
|
$location->zoom_level = $data['zoom_level'];
|
|
$location->locatable()->associate($journal);
|
|
$location->save();
|
|
}
|
|
}
|
|
|
|
public function setErrorOnHash(bool $errorOnHash): void
|
|
{
|
|
$this->errorOnHash = $errorOnHash;
|
|
if (true === $errorOnHash) {
|
|
Log::info('Will trigger duplication alert for this journal.');
|
|
}
|
|
}
|
|
|
|
public function setUserGroup(UserGroup $userGroup): void
|
|
{
|
|
$this->userGroup = $userGroup;
|
|
$this->currencyRepository->setUserGroup($userGroup);
|
|
$this->tagFactory->setUserGroup($userGroup);
|
|
$this->billRepository->setUserGroup($userGroup);
|
|
$this->budgetRepository->setUserGroup($userGroup);
|
|
$this->categoryRepository->setUserGroup($userGroup);
|
|
$this->piggyRepository->setUserGroup($userGroup);
|
|
$this->accountRepository->setUserGroup($userGroup);
|
|
}
|
|
|
|
private function isBetweenAssetAndLiability(Account $source, Account $destination): bool
|
|
{
|
|
if (null === $source || null === $destination) {
|
|
Log::warning('Either is false, stop.');
|
|
return false;
|
|
}
|
|
$sourceTypes = [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
|
|
|
|
// source is liability, destination is asset
|
|
if (in_array($source->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $destination->accountType->type) {
|
|
Log::debug('Source is a liability account, destination is an asset account, return TRUE.');
|
|
return true;
|
|
}
|
|
// source is asset, destination is liability
|
|
if (in_array($destination->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $source->accountType->type) {
|
|
Log::debug('Destination is a liability account, source is an asset account, return TRUE.');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|