mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-12 23:45:10 +00:00
Fix #10265
This commit is contained in:
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
|||||||
namespace FireflyIII\Factory;
|
namespace FireflyIII\Factory;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Enums\AccountTypeEnum;
|
||||||
use FireflyIII\Enums\TransactionTypeEnum;
|
use FireflyIII\Enums\TransactionTypeEnum;
|
||||||
use FireflyIII\Exceptions\DuplicateTransactionException;
|
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
@@ -103,7 +104,7 @@ class TransactionJournalFactory
|
|||||||
{
|
{
|
||||||
app('log')->debug('Now in TransactionJournalFactory::create()');
|
app('log')->debug('Now in TransactionJournalFactory::create()');
|
||||||
// convert to special object.
|
// convert to special object.
|
||||||
$dataObject = new NullArrayObject($data);
|
$dataObject = new NullArrayObject($data);
|
||||||
|
|
||||||
app('log')->debug('Start of TransactionJournalFactory::create()');
|
app('log')->debug('Start of TransactionJournalFactory::create()');
|
||||||
$collection = new Collection();
|
$collection = new Collection();
|
||||||
@@ -161,14 +162,14 @@ class TransactionJournalFactory
|
|||||||
$this->errorIfDuplicate($row['import_hash_v2']);
|
$this->errorIfDuplicate($row['import_hash_v2']);
|
||||||
|
|
||||||
// Some basic fields
|
// Some basic fields
|
||||||
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
||||||
$carbon = $row['date'] ?? today(config('app.timezone'));
|
$carbon = $row['date'] ?? today(config('app.timezone'));
|
||||||
$order = $row['order'] ?? 0;
|
$order = $row['order'] ?? 0;
|
||||||
$currency = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']);
|
$currency = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']);
|
||||||
$foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']);
|
$foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']);
|
||||||
$bill = $this->billRepository->findBill((int) $row['bill_id'], $row['bill_name']);
|
$bill = $this->billRepository->findBill((int) $row['bill_id'], $row['bill_name']);
|
||||||
$billId = TransactionTypeEnum::WITHDRAWAL->value === $type->type && null !== $bill ? $bill->id : null;
|
$billId = TransactionTypeEnum::WITHDRAWAL->value === $type->type && null !== $bill ? $bill->id : null;
|
||||||
$description = (string) $row['description'];
|
$description = (string) $row['description'];
|
||||||
|
|
||||||
// Manipulate basic fields
|
// Manipulate basic fields
|
||||||
$carbon->setTimezone(config('app.timezone'));
|
$carbon->setTimezone(config('app.timezone'));
|
||||||
@@ -190,7 +191,7 @@ class TransactionJournalFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** create or get source and destination accounts */
|
/** create or get source and destination accounts */
|
||||||
$sourceInfo = [
|
$sourceInfo = [
|
||||||
'id' => $row['source_id'],
|
'id' => $row['source_id'],
|
||||||
'name' => $row['source_name'],
|
'name' => $row['source_name'],
|
||||||
'iban' => $row['source_iban'],
|
'iban' => $row['source_iban'],
|
||||||
@@ -199,7 +200,7 @@ class TransactionJournalFactory
|
|||||||
'currency_id' => $currency->id,
|
'currency_id' => $currency->id,
|
||||||
];
|
];
|
||||||
|
|
||||||
$destInfo = [
|
$destInfo = [
|
||||||
'id' => $row['destination_id'],
|
'id' => $row['destination_id'],
|
||||||
'name' => $row['destination_name'],
|
'name' => $row['destination_name'],
|
||||||
'iban' => $row['destination_iban'],
|
'iban' => $row['destination_iban'],
|
||||||
@@ -209,8 +210,8 @@ class TransactionJournalFactory
|
|||||||
];
|
];
|
||||||
app('log')->debug('Source info:', $sourceInfo);
|
app('log')->debug('Source info:', $sourceInfo);
|
||||||
app('log')->debug('Destination info:', $destInfo);
|
app('log')->debug('Destination info:', $destInfo);
|
||||||
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
|
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
|
||||||
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
|
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
|
||||||
app('log')->debug('Done with getAccount(2x)');
|
app('log')->debug('Done with getAccount(2x)');
|
||||||
|
|
||||||
// this is the moment for a reconciliation sanity check (again).
|
// this is the moment for a reconciliation sanity check (again).
|
||||||
@@ -218,15 +219,15 @@ class TransactionJournalFactory
|
|||||||
[$sourceAccount, $destinationAccount] = $this->reconciliationSanityCheck($sourceAccount, $destinationAccount);
|
[$sourceAccount, $destinationAccount] = $this->reconciliationSanityCheck($sourceAccount, $destinationAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
$currency = $this->getCurrencyByAccount($type->type, $currency, $sourceAccount, $destinationAccount);
|
$currency = $this->getCurrencyByAccount($type->type, $currency, $sourceAccount, $destinationAccount);
|
||||||
$foreignCurrency = $this->compareCurrencies($currency, $foreignCurrency);
|
$foreignCurrency = $this->compareCurrencies($currency, $foreignCurrency);
|
||||||
$foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount);
|
$foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount);
|
||||||
$description = $this->getDescription($description);
|
$description = $this->getDescription($description);
|
||||||
|
|
||||||
app('log')->debug(sprintf('Date: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()->getName()));
|
app('log')->debug(sprintf('Date: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()->getName()));
|
||||||
|
|
||||||
/** Create a basic journal. */
|
/** Create a basic journal. */
|
||||||
$journal = TransactionJournal::create(
|
$journal = TransactionJournal::create(
|
||||||
[
|
[
|
||||||
'user_id' => $this->user->id,
|
'user_id' => $this->user->id,
|
||||||
'user_group_id' => $this->userGroup->id,
|
'user_group_id' => $this->userGroup->id,
|
||||||
@@ -244,7 +245,7 @@ class TransactionJournalFactory
|
|||||||
app('log')->debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
|
app('log')->debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
|
||||||
|
|
||||||
/** Create two transactions. */
|
/** Create two transactions. */
|
||||||
$transactionFactory = app(TransactionFactory::class);
|
$transactionFactory = app(TransactionFactory::class);
|
||||||
$transactionFactory->setJournal($journal);
|
$transactionFactory->setJournal($journal);
|
||||||
$transactionFactory->setAccount($sourceAccount);
|
$transactionFactory->setAccount($sourceAccount);
|
||||||
$transactionFactory->setCurrency($currency);
|
$transactionFactory->setCurrency($currency);
|
||||||
@@ -262,7 +263,7 @@ class TransactionJournalFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @var TransactionFactory $transactionFactory */
|
/** @var TransactionFactory $transactionFactory */
|
||||||
$transactionFactory = app(TransactionFactory::class);
|
$transactionFactory = app(TransactionFactory::class);
|
||||||
$transactionFactory->setJournal($journal);
|
$transactionFactory->setJournal($journal);
|
||||||
$transactionFactory->setAccount($destinationAccount);
|
$transactionFactory->setAccount($destinationAccount);
|
||||||
$transactionFactory->setAccountInformation($destInfo);
|
$transactionFactory->setAccountInformation($destInfo);
|
||||||
@@ -274,10 +275,10 @@ class TransactionJournalFactory
|
|||||||
// Firefly III will save the foreign currency information in such a way that both
|
// 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
|
// asset accounts can look at the "amount" and "transaction_currency_id" column and
|
||||||
// see the currency they expect to see.
|
// see the currency they expect to see.
|
||||||
$amount = (string) $row['amount'];
|
$amount = (string) $row['amount'];
|
||||||
$foreignAmount = (string) $row['foreign_amount'];
|
$foreignAmount = (string) $row['foreign_amount'];
|
||||||
if (null !== $foreignCurrency && $foreignCurrency->id !== $currency->id
|
if (null !== $foreignCurrency && $foreignCurrency->id !== $currency->id &&
|
||||||
&& TransactionTypeEnum::TRANSFER->value === $type->type
|
(TransactionTypeEnum::TRANSFER->value === $type->type || $this->isBetweenAssetAndLiability($sourceAccount, $destinationAccount))
|
||||||
) {
|
) {
|
||||||
$transactionFactory->setCurrency($foreignCurrency);
|
$transactionFactory->setCurrency($foreignCurrency);
|
||||||
$transactionFactory->setForeignCurrency($currency);
|
$transactionFactory->setForeignCurrency($currency);
|
||||||
@@ -295,7 +296,7 @@ class TransactionJournalFactory
|
|||||||
|
|
||||||
throw new FireflyException($e->getMessage(), 0, $e);
|
throw new FireflyException($e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
$journal->completed = true;
|
$journal->completed = true;
|
||||||
$journal->save();
|
$journal->save();
|
||||||
$this->storeBudget($journal, $row);
|
$this->storeBudget($journal, $row);
|
||||||
$this->storeCategory($journal, $row);
|
$this->storeCategory($journal, $row);
|
||||||
@@ -320,7 +321,7 @@ class TransactionJournalFactory
|
|||||||
app('log')->error(sprintf('Could not encode dataRow: %s', $e->getMessage()));
|
app('log')->error(sprintf('Could not encode dataRow: %s', $e->getMessage()));
|
||||||
$json = microtime();
|
$json = microtime();
|
||||||
}
|
}
|
||||||
$hash = hash('sha256', $json);
|
$hash = hash('sha256', $json);
|
||||||
app('log')->debug(sprintf('The hash is: %s', $hash), $dataRow);
|
app('log')->debug(sprintf('The hash is: %s', $hash), $dataRow);
|
||||||
|
|
||||||
return $hash;
|
return $hash;
|
||||||
@@ -341,13 +342,12 @@ class TransactionJournalFactory
|
|||||||
|
|
||||||
/** @var null|TransactionJournalMeta $result */
|
/** @var null|TransactionJournalMeta $result */
|
||||||
$result = TransactionJournalMeta::withTrashed()
|
$result = TransactionJournalMeta::withTrashed()
|
||||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||||
->whereNotNull('transaction_journals.id')
|
->whereNotNull('transaction_journals.id')
|
||||||
->where('transaction_journals.user_id', $this->user->id)
|
->where('transaction_journals.user_id', $this->user->id)
|
||||||
->where('data', \Safe\json_encode($hash, JSON_THROW_ON_ERROR))
|
->where('data', \Safe\json_encode($hash, JSON_THROW_ON_ERROR))
|
||||||
->with(['transactionJournal', 'transactionJournal.transactionGroup'])
|
->with(['transactionJournal', 'transactionJournal.transactionGroup'])
|
||||||
->first(['journal_meta.*'])
|
->first(['journal_meta.*']);
|
||||||
;
|
|
||||||
if (null !== $result) {
|
if (null !== $result) {
|
||||||
app('log')->warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash));
|
app('log')->warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash));
|
||||||
$journal = $result->transactionJournal()->withTrashed()->first();
|
$journal = $result->transactionJournal()->withTrashed()->first();
|
||||||
@@ -364,18 +364,18 @@ class TransactionJournalFactory
|
|||||||
private function validateAccounts(NullArrayObject $data): void
|
private function validateAccounts(NullArrayObject $data): void
|
||||||
{
|
{
|
||||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||||
$transactionType = $data['type'] ?? 'invalid';
|
$transactionType = $data['type'] ?? 'invalid';
|
||||||
$this->accountValidator->setUser($this->user);
|
$this->accountValidator->setUser($this->user);
|
||||||
$this->accountValidator->setTransactionType($transactionType);
|
$this->accountValidator->setTransactionType($transactionType);
|
||||||
|
|
||||||
// validate source account.
|
// validate source account.
|
||||||
$array = [
|
$array = [
|
||||||
'id' => null !== $data['source_id'] ? (int) $data['source_id'] : null,
|
'id' => null !== $data['source_id'] ? (int) $data['source_id'] : null,
|
||||||
'name' => null !== $data['source_name'] ? (string) $data['source_name'] : null,
|
'name' => null !== $data['source_name'] ? (string) $data['source_name'] : null,
|
||||||
'iban' => null !== $data['source_iban'] ? (string) $data['source_iban'] : null,
|
'iban' => null !== $data['source_iban'] ? (string) $data['source_iban'] : null,
|
||||||
'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null,
|
'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null,
|
||||||
];
|
];
|
||||||
$validSource = $this->accountValidator->validateSource($array);
|
$validSource = $this->accountValidator->validateSource($array);
|
||||||
|
|
||||||
// do something with result:
|
// do something with result:
|
||||||
if (false === $validSource) {
|
if (false === $validSource) {
|
||||||
@@ -384,7 +384,7 @@ class TransactionJournalFactory
|
|||||||
app('log')->debug('Source seems valid.');
|
app('log')->debug('Source seems valid.');
|
||||||
|
|
||||||
// validate destination account
|
// validate destination account
|
||||||
$array = [
|
$array = [
|
||||||
'id' => null !== $data['destination_id'] ? (int) $data['destination_id'] : null,
|
'id' => null !== $data['destination_id'] ? (int) $data['destination_id'] : null,
|
||||||
'name' => null !== $data['destination_name'] ? (string) $data['destination_name'] : null,
|
'name' => null !== $data['destination_name'] ? (string) $data['destination_name'] : null,
|
||||||
'iban' => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null,
|
'iban' => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null,
|
||||||
@@ -468,7 +468,7 @@ class TransactionJournalFactory
|
|||||||
// return user's default:
|
// return user's default:
|
||||||
return app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
|
return app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
|
||||||
}
|
}
|
||||||
$result = $preference ?? $currency;
|
$result = $preference ?? $currency;
|
||||||
app('log')->debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name));
|
app('log')->debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name));
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
@@ -556,7 +556,7 @@ class TransactionJournalFactory
|
|||||||
|
|
||||||
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
|
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
|
||||||
{
|
{
|
||||||
$set = [
|
$set = [
|
||||||
'journal' => $journal,
|
'journal' => $journal,
|
||||||
'name' => $field,
|
'name' => $field,
|
||||||
'data' => (string) ($data[$field] ?? ''),
|
'data' => (string) ($data[$field] ?? ''),
|
||||||
@@ -605,4 +605,22 @@ class TransactionJournalFactory
|
|||||||
$this->piggyRepository->setUserGroup($userGroup);
|
$this->piggyRepository->setUserGroup($userGroup);
|
||||||
$this->accountRepository->setUserGroup($userGroup);
|
$this->accountRepository->setUserGroup($userGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isBetweenAssetAndLiability(Account $source, Account $destination): bool
|
||||||
|
{
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
Log::debug('Not between asset and liability, return FALSE');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -163,7 +163,9 @@ class IndexController extends Controller
|
|||||||
|
|
||||||
/** @var Carbon $end */
|
/** @var Carbon $end */
|
||||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||||
$start->subDay();
|
|
||||||
|
// 2025-05-11 removed this so start is exactly the start of the month.
|
||||||
|
// $start->subDay();
|
||||||
|
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
$ids = $accounts->pluck('id')->toArray();
|
||||||
Log::debug(sprintf('index start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
Log::debug(sprintf('index start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||||
|
@@ -55,9 +55,9 @@ var edit_selected_txt = "{{ trans('firefly.mass_edit')|escape('js') }}";
|
|||||||
var edit_bulk_selected_txt = "{{ trans('firefly.bulk_edit')|escape('js') }}";
|
var edit_bulk_selected_txt = "{{ trans('firefly.bulk_edit')|escape('js') }}";
|
||||||
var delete_selected_txt = "{{ trans('firefly.mass_delete')|escape('js') }}";
|
var delete_selected_txt = "{{ trans('firefly.mass_delete')|escape('js') }}";
|
||||||
|
|
||||||
var mass_edit_url = '{{ route('transactions.mass.edit', ['']) }}';
|
var mass_edit_url = '{{ route('transactions.mass.edit', ['0']) }}';
|
||||||
var bulk_edit_url = '{{ route('transactions.bulk.edit', ['']) }}';
|
var bulk_edit_url = '{{ route('transactions.bulk.edit', ['0']) }}';
|
||||||
var mass_delete_url = '{{ route('transactions.mass.delete', ['']) }}';
|
var mass_delete_url = '{{ route('transactions.mass.delete', ['0']) }}';
|
||||||
|
|
||||||
// for demo:
|
// for demo:
|
||||||
var nextLabel = "{{ trans('firefly.intro_next_label')|escape('js') }}";
|
var nextLabel = "{{ trans('firefly.intro_next_label')|escape('js') }}";
|
||||||
|
@@ -259,7 +259,17 @@
|
|||||||
{% if transaction.transaction_type_type == 'Deposit' %}
|
{% if transaction.transaction_type_type == 'Deposit' %}
|
||||||
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
|
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
|
||||||
{% elseif transaction.transaction_type_type == 'Withdrawal' %}
|
{% elseif transaction.transaction_type_type == 'Withdrawal' %}
|
||||||
{{ formatAmountBySymbol(transaction.source_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
|
{% if 'Loan' == transaction.destination_account_type or 'Mortgage' == transaction.destination_account_type or 'Debt' == transaction.destination_account_type %}
|
||||||
|
{% if currency.id == transaction.currency_id %}
|
||||||
|
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if currency.id == transaction.foreign_currency_id %}
|
||||||
|
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
{{ formatAmountBySymbol(transaction.source_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
|
||||||
|
{% endif %}
|
||||||
{% elseif transaction.transaction_type_type == 'Opening balance' %}
|
{% elseif transaction.transaction_type_type == 'Opening balance' %}
|
||||||
{% if transaction.source_account_type == 'Initial balance account' %}
|
{% if transaction.source_account_type == 'Initial balance account' %}
|
||||||
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
|
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
|
||||||
|
Reference in New Issue
Block a user