Big update to properly support multi currencies.

This commit is contained in:
James Cole 2017-06-04 13:39:16 +02:00
parent 771ebde295
commit 82e74a2afd
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
34 changed files with 470 additions and 498 deletions

View File

@ -19,6 +19,7 @@ use FireflyIII\Http\Requests\MassDeleteJournalRequest;
use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
@ -126,8 +127,7 @@ class MassController extends Controller
$budgetRepository = app(BudgetRepositoryInterface::class); $budgetRepository = app(BudgetRepositoryInterface::class);
$budgets = $budgetRepository->getBudgets(); $budgets = $budgetRepository->getBudgets();
// skip transactions that have multiple destinations // skip transactions that have multiple destinations, multiple sources or are an opening balance.
// or multiple sources:
$filtered = new Collection; $filtered = new Collection;
$messages = []; $messages = [];
/** /**
@ -146,6 +146,10 @@ class MassController extends Controller
$messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]); $messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
continue; continue;
} }
if ($journal->transactionType->type === TransactionType::OPENING_BALANCE) {
$messages[] = trans('firefly.cannot_edit_opening_balance');
continue;
}
$filtered->push($journal); $filtered->push($journal);
} }
@ -158,13 +162,21 @@ class MassController extends Controller
Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventCategory', 'transactions');
Session::flash('gaEventAction', 'mass-edit'); Session::flash('gaEventAction', 'mass-edit');
// set some values to be used in the edit routine: // collect some useful meta data for the mass edit:
$filtered->each( $filtered->each(
function (TransactionJournal $journal) { function (TransactionJournal $journal) {
$journal->amount = $journal->amountPositive(); $transaction = $journal->positiveTransaction();
$currency = $transaction->transactionCurrency;
$journal->amount = floatval($transaction->amount);
$sources = $journal->sourceAccountList(); $sources = $journal->sourceAccountList();
$destinations = $journal->destinationAccountList(); $destinations = $journal->destinationAccountList();
$journal->transaction_count = $journal->transactions()->count(); $journal->transaction_count = $journal->transactions()->count();
$journal->currency_symbol = $currency->symbol;
$journal->transaction_type_type = $journal->transactionType->type;
$journal->foreign_amount = floatval($transaction->foreign_amount);
$journal->foreign_currency = $transaction->foreignCurrency;
if (!is_null($sources->first())) { if (!is_null($sources->first())) {
$journal->source_account_id = $sources->first()->id; $journal->source_account_id = $sources->first()->id;
$journal->source_account_name = $sources->first()->editname; $journal->source_account_name = $sources->first()->editname;
@ -195,6 +207,7 @@ class MassController extends Controller
{ {
$journalIds = $request->get('journals'); $journalIds = $request->get('journals');
$count = 0; $count = 0;
if (is_array($journalIds)) { if (is_array($journalIds)) {
foreach ($journalIds as $journalId) { foreach ($journalIds as $journalId) {
$journal = $repository->find(intval($journalId)); $journal = $repository->find(intval($journalId));
@ -208,6 +221,10 @@ class MassController extends Controller
$budgetId = $request->get('budget_id')[$journal->id] ?? 0; $budgetId = $request->get('budget_id')[$journal->id] ?? 0;
$category = $request->get('category')[$journal->id]; $category = $request->get('category')[$journal->id];
$tags = $journal->tags->pluck('tag')->toArray(); $tags = $journal->tags->pluck('tag')->toArray();
$amount = round($request->get('amount')[$journal->id], 12);
$foreignAmount = isset($request->get('foreign_amount')[$journal->id]) ? round($request->get('foreign_amount')[$journal->id], 12) : null;
$foreignCurrencyId = isset($request->get('foreign_currency_id')[$journal->id]) ?
intval($request->get('foreign_currency_id')[$journal->id]) : null;
// build data array // build data array
$data = [ $data = [
@ -218,16 +235,20 @@ class MassController extends Controller
'source_account_name' => $sourceAccountName, 'source_account_name' => $sourceAccountName,
'destination_account_id' => intval($destAccountId), 'destination_account_id' => intval($destAccountId),
'destination_account_name' => $destAccountName, 'destination_account_name' => $destAccountName,
'amount' => round($request->get('amount')[$journal->id], 12), 'amount' => $foreignAmount,
'currency_id' => $journal->transaction_currency_id, 'native_amount' => $amount,
'source_amount' => $amount,
'date' => new Carbon($request->get('date')[$journal->id]), 'date' => new Carbon($request->get('date')[$journal->id]),
'interest_date' => $journal->interest_date, 'interest_date' => $journal->interest_date,
'book_date' => $journal->book_date, 'book_date' => $journal->book_date,
'process_date' => $journal->process_date, 'process_date' => $journal->process_date,
'budget_id' => intval($budgetId), 'budget_id' => intval($budgetId),
'currency_id' => $foreignCurrencyId,
'foreign_amount' => $foreignAmount,
'destination_amount' => $foreignAmount,
//'foreign_currency_id' => $foreignCurrencyId,
'category' => $category, 'category' => $category,
'tags' => $tags, 'tags' => $tags,
]; ];
// call repository update function. // call repository update function.
$repository->update($journal, $data); $repository->update($journal, $data);
@ -235,6 +256,7 @@ class MassController extends Controller
$count++; $count++;
} }
} }
} }
Preferences::mark(); Preferences::mark();
Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count])); Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));

View File

@ -238,6 +238,7 @@ class SingleController extends Controller
$sourceAccounts = $journal->sourceAccountList(); $sourceAccounts = $journal->sourceAccountList();
$destinationAccounts = $journal->destinationAccountList(); $destinationAccounts = $journal->destinationAccountList();
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$pTransaction = $journal->positiveTransaction();
$preFilled = [ $preFilled = [
'date' => $journal->dateAsString(), 'date' => $journal->dateAsString(),
'interest_date' => $journal->dateAsString('interest_date'), 'interest_date' => $journal->dateAsString('interest_date'),
@ -250,8 +251,6 @@ class SingleController extends Controller
'source_account_name' => $sourceAccounts->first()->edit_name, 'source_account_name' => $sourceAccounts->first()->edit_name,
'destination_account_id' => $destinationAccounts->first()->id, 'destination_account_id' => $destinationAccounts->first()->id,
'destination_account_name' => $destinationAccounts->first()->edit_name, 'destination_account_name' => $destinationAccounts->first()->edit_name,
'amount' => $journal->amountPositive(),
'currency' => $journal->transactionCurrency,
// new custom fields: // new custom fields:
'due_date' => $journal->dateAsString('due_date'), 'due_date' => $journal->dateAsString('due_date'),
@ -260,26 +259,36 @@ class SingleController extends Controller
'interal_reference' => $journal->getMeta('internal_reference'), 'interal_reference' => $journal->getMeta('internal_reference'),
'notes' => $journal->getMeta('notes'), 'notes' => $journal->getMeta('notes'),
// exchange rate fields // amount fields
'native_amount' => $journal->amountPositive(), 'amount' => $pTransaction->amount,
'native_currency' => $journal->transactionCurrency, 'source_amount' => $pTransaction->amount,
'native_amount' => $pTransaction->amount,
'destination_amount' => $pTransaction->foreign_amount,
'currency' => $pTransaction->transactionCurrency,
'source_currency' => $pTransaction->transactionCurrency,
'native_currency' => $pTransaction->transactionCurrency,
'foreign_currency' => !is_null($pTransaction->foreignCurrency) ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency,
'destination_currency' => !is_null($pTransaction->foreignCurrency) ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency,
]; ];
// if user has entered a foreign currency, update some fields // amounts for withdrawals and deposits:
$foreignCurrencyId = intval($journal->getMeta('foreign_currency_id')); // (amount, native_amount, source_amount, destination_amount)
if ($foreignCurrencyId > 0) { if (($journal->isWithdrawal() || $journal->isDeposit()) && !is_null($pTransaction->foreign_amount)) {
// update some fields in pre-filled. $preFilled['amount'] = $pTransaction->foreign_amount;
// @codeCoverageIgnoreStart $preFilled['currency'] = $pTransaction->foreignCurrency;
$preFilled['amount'] = $journal->getMeta('foreign_amount');
$preFilled['currency'] = $this->currency->find(intval($journal->getMeta('foreign_currency_id')));
// @codeCoverageIgnoreEnd
} }
if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { if ($journal->isTransfer() && !is_null($pTransaction->foreign_amount)) {
$preFilled['destination_amount'] = $pTransaction->foreign_amount;
$preFilled['destination_currency'] = $pTransaction->foreignCurrency;
}
// fixes for cash accounts:
if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type === AccountType::CASH) {
$preFilled['destination_account_name'] = ''; $preFilled['destination_account_name'] = '';
} }
if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) { if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type === AccountType::CASH) {
$preFilled['source_account_name'] = ''; $preFilled['source_account_name'] = '';
} }
@ -319,6 +328,7 @@ class SingleController extends Controller
return redirect(route('transactions.create', [$request->input('what')]))->withInput(); return redirect(route('transactions.create', [$request->input('what')]))->withInput();
} }
/** @var array $files */ /** @var array $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null; $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
$this->attachments->saveAttachmentsForModel($journal, $files); $this->attachments->saveAttachmentsForModel($journal, $files);

View File

@ -93,7 +93,7 @@ class SplitController extends Controller
} }
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
$currencies = ExpandedForm::makeSelectList($this->currencies->get()); $currencies = $this->currencies->get();
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
@ -130,7 +130,6 @@ class SplitController extends Controller
*/ */
public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal) public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
{ {
if ($this->isOpeningBalance($journal)) { if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal); return $this->redirectToAccount($journal);
} }
@ -179,7 +178,6 @@ class SplitController extends Controller
'journal_source_account_id' => $request->get('journal_source_account_id'), 'journal_source_account_id' => $request->get('journal_source_account_id'),
'journal_source_account_name' => $request->get('journal_source_account_name'), 'journal_source_account_name' => $request->get('journal_source_account_name'),
'journal_destination_account_id' => $request->get('journal_destination_account_id'), 'journal_destination_account_id' => $request->get('journal_destination_account_id'),
'currency_id' => $request->get('currency_id'),
'what' => $request->get('what'), 'what' => $request->get('what'),
'date' => $request->get('date'), 'date' => $request->get('date'),
// all custom fields: // all custom fields:
@ -218,7 +216,6 @@ class SplitController extends Controller
'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id), 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id),
'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name), 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name),
'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id),
'currency_id' => $request->old('currency_id', $journal->transaction_currency_id),
'destinationAccounts' => $destinationAccounts, 'destinationAccounts' => $destinationAccounts,
'what' => strtolower($journal->transactionTypeStr()), 'what' => strtolower($journal->transactionTypeStr()),
'date' => $request->old('date', $journal->date), 'date' => $request->old('date', $journal->date),
@ -261,6 +258,14 @@ class SplitController extends Controller
'amount' => round($transaction['destination_amount'], 12), 'amount' => round($transaction['destination_amount'], 12),
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
'category' => $transaction['category'], 'category' => $transaction['category'],
'transaction_currency_id' => $transaction['transaction_currency_id'],
'transaction_currency_code' => $transaction['transaction_currency_code'],
'transaction_currency_symbol' => $transaction['transaction_currency_symbol'],
'foreign_amount' => round($transaction['foreign_destination_amount'], 12),
'foreign_currency_id' => $transaction['foreign_currency_id'],
'foreign_currency_code' => $transaction['foreign_currency_code'],
'foreign_currency_symbol' => $transaction['foreign_currency_symbol'],
]; ];
// set initial category and/or budget: // set initial category and/or budget:
@ -294,8 +299,12 @@ class SplitController extends Controller
'destination_account_id' => $transaction['destination_account_id'] ?? 0, 'destination_account_id' => $transaction['destination_account_id'] ?? 0,
'destination_account_name' => $transaction['destination_account_name'] ?? '', 'destination_account_name' => $transaction['destination_account_name'] ?? '',
'amount' => round($transaction['amount'] ?? 0, 12), 'amount' => round($transaction['amount'] ?? 0, 12),
'foreign_amount' => !isset($transaction['foreign_amount']) ? null : round($transaction['foreign_amount'] ?? 0, 12),
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
'category' => $transaction['category'] ?? '', 'category' => $transaction['category'] ?? '',
'transaction_currency_id' => intval($transaction['transaction_currency_id']),
'foreign_currency_id' => $transaction['foreign_currency_id'] ?? null,
]; ];
} }
Log::debug(sprintf('Found %d splits in request data.', count($return))); Log::debug(sprintf('Found %d splits in request data.', count($return)));

View File

@ -183,17 +183,8 @@ class TransactionController extends Controller
$transactions = $tasker->getTransactionsOverview($journal); $transactions = $tasker->getTransactionsOverview($journal);
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
$foreignCurrency = null;
if ($journal->hasMeta('foreign_currency_id')) { return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
// @codeCoverageIgnoreStart
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$foreignCurrency = $repository->find(intval($journal->getMeta('foreign_currency_id')));
// @codeCoverageIgnoreEnd
}
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'foreignCurrency'));
} }

View File

@ -291,6 +291,7 @@ class ImportStorage
'user_id' => $entry->user->id, 'user_id' => $entry->user->id,
'transaction_type_id' => $entry->fields['transaction-type']->id, 'transaction_type_id' => $entry->fields['transaction-type']->id,
'bill_id' => $billId, 'bill_id' => $billId,
// TODO update this transaction currency reference.
'transaction_currency_id' => $entry->fields['currency']->id, 'transaction_currency_id' => $entry->fields['currency']->id,
'description' => $entry->fields['description'], 'description' => $entry->fields['description'],
'date' => $entry->fields['date-transaction'], 'date' => $entry->fields['date-transaction'],

View File

@ -74,6 +74,7 @@ class ImportValidator
$entry = $this->setOpposingAccount($entry); $entry = $this->setOpposingAccount($entry);
$entry = $this->cleanDescription($entry); $entry = $this->cleanDescription($entry);
$entry = $this->setTransactionType($entry); $entry = $this->setTransactionType($entry);
// TODO update this transaction currency reference.
$entry = $this->setTransactionCurrency($entry); $entry = $this->setTransactionCurrency($entry);
$newCollection->put($index, $entry); $newCollection->put($index, $entry);
@ -383,6 +384,7 @@ class ImportValidator
*/ */
private function setTransactionCurrency(ImportEntry $entry): ImportEntry private function setTransactionCurrency(ImportEntry $entry): ImportEntry
{ {
// TODO update this transaction currency reference.
if (is_null($entry->fields['currency'])) { if (is_null($entry->fields['currency'])) {
/** @var CurrencyRepositoryInterface $repository */ /** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class); $repository = app(CurrencyRepositoryInterface::class);

View File

@ -26,7 +26,6 @@ use Watson\Validating\ValidatingTrait;
*/ */
class Transaction extends Model class Transaction extends Model
{ {
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.
* *
@ -42,16 +41,18 @@ class Transaction extends Model
'bill_name_encrypted' => 'boolean', 'bill_name_encrypted' => 'boolean',
]; ];
protected $dates = ['created_at', 'updated_at', 'deleted_at']; protected $dates = ['created_at', 'updated_at', 'deleted_at'];
protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier']; protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier', 'transaction_currency_id', 'foreign_currency_id','foreign_amount'];
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];
protected $rules protected $rules
= [ = [
'account_id' => 'required|exists:accounts,id', 'account_id' => 'required|exists:accounts,id',
'transaction_journal_id' => 'required|exists:transaction_journals,id', 'transaction_journal_id' => 'required|exists:transaction_journals,id',
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
//'foreign_currency_id' => 'exists:transaction_currencies,id',
'description' => 'between:0,1024', 'description' => 'between:0,1024',
'amount' => 'required|numeric', 'amount' => 'required|numeric',
//'foreign_amount' => 'numeric',
]; ];
use SoftDeletes, ValidatingTrait;
/** /**
* @param Builder $query * @param Builder $query
@ -74,6 +75,8 @@ class Transaction extends Model
return false; return false;
} }
use SoftDeletes, ValidatingTrait;
/** /**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
@ -160,6 +163,22 @@ class Transaction extends Model
$this->attributes['amount'] = strval(round($value, 12)); $this->attributes['amount'] = strval(round($value, 12));
} }
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function transactionCurrency()
{
return $this->belongsTo('FireflyIII\Models\TransactionCurrency');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function foreignCurrency()
{
return $this->belongsTo('FireflyIII\Models\TransactionCurrency','foreign_currency_id');
}
/** /**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */

View File

@ -68,7 +68,6 @@ class TransactionJournal extends Model
= [ = [
'user_id' => 'required|exists:users,id', 'user_id' => 'required|exists:users,id',
'transaction_type_id' => 'required|exists:transaction_types,id', 'transaction_type_id' => 'required|exists:transaction_types,id',
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
'description' => 'required|between:1,1024', 'description' => 'required|between:1,1024',
'completed' => 'required|boolean', 'completed' => 'required|boolean',
'date' => 'required|date', 'date' => 'required|date',
@ -299,46 +298,6 @@ class TransactionJournal extends Model
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00'));
} }
/**
* @param EloquentBuilder $query
*/
public function scopeExpanded(EloquentBuilder $query)
{
// left join transaction type:
if (!self::isJoined($query, 'transaction_types')) {
$query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
}
// left join transaction currency:
$query->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id');
// extend group by:
$query->groupBy(
[
'transaction_journals.id',
'transaction_journals.created_at',
'transaction_journals.updated_at',
'transaction_journals.deleted_at',
'transaction_journals.user_id',
'transaction_journals.transaction_type_id',
'transaction_journals.bill_id',
'transaction_journals.transaction_currency_id',
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.interest_date',
'transaction_journals.book_date',
'transaction_journals.process_date',
'transaction_journals.order',
'transaction_journals.tag_count',
'transaction_journals.encrypted',
'transaction_journals.completed',
'transaction_types.type',
'transaction_currencies.code',
]
);
$query->with(['categories', 'budgets', 'attachments', 'bill', 'transactions']);
}
/** /**
* @param EloquentBuilder $query * @param EloquentBuilder $query
*/ */

View File

@ -44,6 +44,7 @@ use FireflyIII\Support\Navigation;
use FireflyIII\Support\Preferences; use FireflyIII\Support\Preferences;
use FireflyIII\Support\Steam; use FireflyIII\Support\Steam;
use FireflyIII\Support\Twig\Account; use FireflyIII\Support\Twig\Account;
use FireflyIII\Support\Twig\AmountFormat;
use FireflyIII\Support\Twig\General; use FireflyIII\Support\Twig\General;
use FireflyIII\Support\Twig\Journal; use FireflyIII\Support\Twig\Journal;
use FireflyIII\Support\Twig\PiggyBank; use FireflyIII\Support\Twig\PiggyBank;
@ -79,7 +80,7 @@ class FireflyServiceProvider extends ServiceProvider
Twig::addExtension(new Translation); Twig::addExtension(new Translation);
Twig::addExtension(new Transaction); Twig::addExtension(new Transaction);
Twig::addExtension(new Rule); Twig::addExtension(new Rule);
Twig::addExtension(new Account); Twig::addExtension(new AmountFormat);
} }
/** /**

View File

@ -481,6 +481,7 @@ class AccountRepository implements AccountRepositoryInterface
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id, 'transaction_type_id' => $transactionType->id,
// TODO update this transaction currency reference.
'transaction_currency_id' => $currencyId, 'transaction_currency_id' => $currencyId,
'description' => 'Initial balance for "' . $account->name . '"', 'description' => 'Initial balance for "' . $account->name . '"',
'completed' => true, 'completed' => true,
@ -622,6 +623,7 @@ class AccountRepository implements AccountRepositoryInterface
// update date: // update date:
$journal->date = $date; $journal->date = $date;
// TODO update this transaction currency reference.
$journal->transaction_currency_id = $currencyId; $journal->transaction_currency_id = $currencyId;
$journal->save(); $journal->save();
// update transactions: // update transactions:

View File

@ -39,7 +39,6 @@ class JournalRepository implements JournalRepositoryInterface
{ {
/** @var User */ /** @var User */
private $user; private $user;
/** @var array */ /** @var array */
private $validMetaFields private $validMetaFields
= ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes', 'foreign_amount', = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes', 'foreign_amount',
@ -182,13 +181,12 @@ class JournalRepository implements JournalRepositoryInterface
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
$accounts = $this->storeAccounts($transactionType, $data); $accounts = $this->storeAccounts($transactionType, $data);
$data = $this->verifyNativeAmount($data, $accounts); $data = $this->verifyNativeAmount($data, $accounts);
$currencyId = $data['currency_id'];
$amount = strval($data['amount']); $amount = strval($data['amount']);
$journal = new TransactionJournal( $journal = new TransactionJournal(
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id, 'transaction_type_id' => $transactionType->id,
'transaction_currency_id' => $currencyId, 'transaction_currency_id' => $data['currency_id'], // no longer used.
'description' => $data['description'], 'description' => $data['description'],
'completed' => 0, 'completed' => 0,
'date' => $data['date'], 'date' => $data['date'],
@ -200,12 +198,14 @@ class JournalRepository implements JournalRepositoryInterface
$this->storeCategoryWithJournal($journal, $data['category']); $this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']); $this->storeBudgetWithJournal($journal, $data['budget_id']);
// store two transactions: // store two transactions:
$one = [ $one = [
'journal' => $journal, 'journal' => $journal,
'account' => $accounts['source'], 'account' => $accounts['source'],
'amount' => bcmul($amount, '-1'), 'amount' => bcmul($amount, '-1'),
'transaction_currency_id' => $data['currency_id'],
'foreign_amount' => is_null($data['foreign_amount']) ? null : bcmul(strval($data['foreign_amount']), '-1'),
'foreign_currency_id' => $data['foreign_currency_id'],
'description' => null, 'description' => null,
'category' => null, 'category' => null,
'budget' => null, 'budget' => null,
@ -217,6 +217,9 @@ class JournalRepository implements JournalRepositoryInterface
'journal' => $journal, 'journal' => $journal,
'account' => $accounts['destination'], 'account' => $accounts['destination'],
'amount' => $amount, 'amount' => $amount,
'transaction_currency_id' => $data['currency_id'],
'foreign_amount' => $data['foreign_amount'],
'foreign_currency_id' => $data['foreign_currency_id'],
'description' => null, 'description' => null,
'category' => null, 'category' => null,
'budget' => null, 'budget' => null,
@ -260,7 +263,10 @@ class JournalRepository implements JournalRepositoryInterface
$journal->date = $data['date']; $journal->date = $data['date'];
$accounts = $this->storeAccounts($journal->transactionType, $data); $accounts = $this->storeAccounts($journal->transactionType, $data);
$data = $this->verifyNativeAmount($data, $accounts); $data = $this->verifyNativeAmount($data, $accounts);
$amount = strval($data['amount']); $data['amount'] = strval($data['amount']);
$data['foreign_amount'] = is_null($data['foreign_amount']) ? null : strval($data['foreign_amount']);
var_dump($data);
// unlink all categories, recreate them: // unlink all categories, recreate them:
$journal->categories()->detach(); $journal->categories()->detach();
@ -269,9 +275,11 @@ class JournalRepository implements JournalRepositoryInterface
$this->storeCategoryWithJournal($journal, $data['category']); $this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']); $this->storeBudgetWithJournal($journal, $data['budget_id']);
// negative because source loses money.
$this->updateSourceTransaction($journal, $accounts['source'], $data);
$this->updateSourceTransaction($journal, $accounts['source'], bcmul($amount, '-1')); // negative because source loses money. // positive because destination gets money.
$this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money. $this->updateDestinationTransaction($journal, $accounts['destination'], $data);
$journal->save(); $journal->save();
@ -308,7 +316,6 @@ class JournalRepository implements JournalRepositoryInterface
public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal
{ {
// update actual journal: // update actual journal:
$journal->transaction_currency_id = $data['currency_id'];
$journal->description = $data['journal_description']; $journal->description = $data['journal_description'];
$journal->date = $data['date']; $journal->date = $data['date'];
$journal->save(); $journal->save();
@ -342,6 +349,7 @@ class JournalRepository implements JournalRepositoryInterface
// store each transaction. // store each transaction.
$identifier = 0; $identifier = 0;
Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions']))); Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions'])));
foreach ($data['transactions'] as $transaction) { foreach ($data['transactions'] as $transaction) {
Log::debug(sprintf('Split journal update split transaction %d', $identifier)); Log::debug(sprintf('Split journal update split transaction %d', $identifier));
$transaction = $this->appendTransactionData($transaction, $data); $transaction = $this->appendTransactionData($transaction, $data);
@ -564,11 +572,16 @@ class JournalRepository implements JournalRepositoryInterface
$accounts = $this->storeAccounts($journal->transactionType, $transaction); $accounts = $this->storeAccounts($journal->transactionType, $transaction);
// store transaction one way: // store transaction one way:
$amount = bcmul(strval($transaction['amount']), '-1');
$foreignAmount = is_null($transaction['foreign_amount']) ? null : bcmul(strval($transaction['foreign_amount']), '-1');
$one = $this->storeTransaction( $one = $this->storeTransaction(
[ [
'journal' => $journal, 'journal' => $journal,
'account' => $accounts['source'], 'account' => $accounts['source'],
'amount' => bcmul(strval($transaction['amount']), '-1'), 'amount' => $amount,
'transaction_currency_id' => $transaction['transaction_currency_id'],
'foreign_amount' => $foreignAmount,
'foreign_currency_id' => $transaction['foreign_currency_id'],
'description' => $transaction['description'], 'description' => $transaction['description'],
'category' => null, 'category' => null,
'budget' => null, 'budget' => null,
@ -579,11 +592,16 @@ class JournalRepository implements JournalRepositoryInterface
$this->storeBudgetWithTransaction($one, $transaction['budget_id']); $this->storeBudgetWithTransaction($one, $transaction['budget_id']);
// and the other way: // and the other way:
$amount = strval($transaction['amount']);
$foreignAmount = is_null($transaction['foreign_amount']) ? null : strval($transaction['foreign_amount']);
$two = $this->storeTransaction( $two = $this->storeTransaction(
[ [
'journal' => $journal, 'journal' => $journal,
'account' => $accounts['destination'], 'account' => $accounts['destination'],
'amount' => strval($transaction['amount']), 'amount' => $amount,
'transaction_currency_id' => $transaction['transaction_currency_id'],
'foreign_amount' => $foreignAmount,
'foreign_currency_id' => $transaction['foreign_currency_id'],
'description' => $transaction['description'], 'description' => $transaction['description'],
'category' => null, 'category' => null,
'budget' => null, 'budget' => null,
@ -603,16 +621,27 @@ class JournalRepository implements JournalRepositoryInterface
*/ */
private function storeTransaction(array $data): Transaction private function storeTransaction(array $data): Transaction
{ {
/** @var Transaction $transaction */ $fields = [
$transaction = Transaction::create(
[
'transaction_journal_id' => $data['journal']->id, 'transaction_journal_id' => $data['journal']->id,
'account_id' => $data['account']->id, 'account_id' => $data['account']->id,
'amount' => $data['amount'], 'amount' => $data['amount'],
'foreign_amount' => $data['foreign_amount'],
'transaction_currency_id' => $data['transaction_currency_id'],
'foreign_currency_id' => $data['foreign_currency_id'],
'description' => $data['description'], 'description' => $data['description'],
'identifier' => $data['identifier'], 'identifier' => $data['identifier'],
] ];
);
if (is_null($data['foreign_currency_id'])) {
unset($fields['foreign_currency_id']);
}
if (is_null($data['foreign_amount'])) {
unset($fields['foreign_amount']);
}
/** @var Transaction $transaction */
$transaction = Transaction::create($fields);
Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id)); Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id));
@ -675,22 +704,23 @@ class JournalRepository implements JournalRepositoryInterface
/** /**
* @param TransactionJournal $journal * @param TransactionJournal $journal
* @param Account $account * @param Account $account
* @param string $amount * @param array $data
* *
* @throws FireflyException * @throws FireflyException
*/ */
private function updateDestinationTransaction(TransactionJournal $journal, Account $account, string $amount) private function updateDestinationTransaction(TransactionJournal $journal, Account $account, array $data)
{ {
// should be one:
$set = $journal->transactions()->where('amount', '>', 0)->get(); $set = $journal->transactions()->where('amount', '>', 0)->get();
if ($set->count() != 1) { if ($set->count() != 1) {
throw new FireflyException( throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount more than zero.', $journal->id, $set->count())
);
} }
/** @var Transaction $transaction */ /** @var Transaction $transaction */
$transaction = $set->first(); $transaction = $set->first();
$transaction->amount = $amount; $transaction->amount = app('steam')->positive($data['amount']);
$transaction->transaction_currency_id = $data['currency_id'];
$transaction->foreign_amount = is_null($data['foreign_amount']) ? null : app('steam')->positive($data['foreign_amount']);
$transaction->foreign_currency_id = $data['foreign_currency_id'];
$transaction->account_id = $account->id; $transaction->account_id = $account->id;
$transaction->save(); $transaction->save();
@ -699,26 +729,24 @@ class JournalRepository implements JournalRepositoryInterface
/** /**
* @param TransactionJournal $journal * @param TransactionJournal $journal
* @param Account $account * @param Account $account
* @param string $amount * @param array $data
* *
* @throws FireflyException * @throws FireflyException
*/ */
private function updateSourceTransaction(TransactionJournal $journal, Account $account, string $amount) private function updateSourceTransaction(TransactionJournal $journal, Account $account, array $data)
{ {
// should be one: // should be one:
$set = $journal->transactions()->where('amount', '<', 0)->get(); $set = $journal->transactions()->where('amount', '<', 0)->get();
if ($set->count() != 1) { if ($set->count() != 1) {
throw new FireflyException( throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount less than zero.', $journal->id, $set->count())
);
} }
/** @var Transaction $transaction */ /** @var Transaction $transaction */
$transaction = $set->first(); $transaction = $set->first();
$transaction->amount = $amount; $transaction->amount = bcmul(app('steam')->positive($data['amount']), '-1');
$transaction->account_id = $account->id; $transaction->transaction_currency_id = $data['currency_id'];
$transaction->foreign_amount = is_null($data['foreign_amount']) ? null : bcmul(app('steam')->positive($data['foreign_amount']), '-1');
$transaction->foreign_currency_id = $data['foreign_currency_id'];
$transaction->save(); $transaction->save();
} }
/** /**
@ -779,6 +807,8 @@ class JournalRepository implements JournalRepositoryInterface
/** @var TransactionType $transactionType */ /** @var TransactionType $transactionType */
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
$submittedCurrencyId = $data['currency_id']; $submittedCurrencyId = $data['currency_id'];
$data['foreign_amount'] = null;
$data['foreign_currency_id'] = null;
// which account to check for what the native currency is? // which account to check for what the native currency is?
$check = 'source'; $check = 'source';
@ -803,11 +833,17 @@ class JournalRepository implements JournalRepositoryInterface
} }
break; break;
case TransactionType::TRANSFER: case TransactionType::TRANSFER:
// source gets the original amount. $sourceCurrencyId = intval($accounts['source']->getMeta('currency_id'));
$destinationCurrencyId = intval($accounts['destination']->getMeta('currency_id'));
$data['amount'] = strval($data['source_amount']); $data['amount'] = strval($data['source_amount']);
$data['currency_id'] = intval($accounts['source']->getMeta('currency_id')); $data['currency_id'] = intval($accounts['source']->getMeta('currency_id'));
if ($sourceCurrencyId !== $destinationCurrencyId) {
// accounts have different id's, save this info:
$data['foreign_amount'] = strval($data['destination_amount']); $data['foreign_amount'] = strval($data['destination_amount']);
$data['foreign_currency_id'] = intval($accounts['destination']->getMeta('currency_id')); $data['foreign_currency_id'] = $destinationCurrencyId;
}
break; break;
default: default:
throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type)); throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type));

View File

@ -81,6 +81,8 @@ class JournalTasker implements JournalTaskerInterface
->leftJoin('account_types as source_account_types', 'source_accounts.account_type_id', '=', 'source_account_types.id') ->leftJoin('account_types as source_account_types', 'source_accounts.account_type_id', '=', 'source_account_types.id')
->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id') ->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
->leftJoin('account_types as destination_account_types', 'destination_accounts.account_type_id', '=', 'destination_account_types.id') ->leftJoin('account_types as destination_account_types', 'destination_accounts.account_type_id', '=', 'destination_account_types.id')
->leftJoin('transaction_currencies as native_currencies', 'transactions.transaction_currency_id', '=', 'native_currencies.id')
->leftJoin('transaction_currencies as foreign_currencies', 'transactions.foreign_currency_id', '=', 'foreign_currencies.id')
->where('transactions.amount', '<', 0) ->where('transactions.amount', '<', 0)
->whereNull('transactions.deleted_at') ->whereNull('transactions.deleted_at')
->get( ->get(
@ -91,12 +93,21 @@ class JournalTasker implements JournalTaskerInterface
'source_accounts.encrypted as account_encrypted', 'source_accounts.encrypted as account_encrypted',
'source_account_types.type as account_type', 'source_account_types.type as account_type',
'transactions.amount', 'transactions.amount',
'transactions.foreign_amount',
'transactions.description', 'transactions.description',
'destination.id as destination_id', 'destination.id as destination_id',
'destination.account_id as destination_account_id', 'destination.account_id as destination_account_id',
'destination_accounts.name as destination_account_name', 'destination_accounts.name as destination_account_name',
'destination_accounts.encrypted as destination_account_encrypted', 'destination_accounts.encrypted as destination_account_encrypted',
'destination_account_types.type as destination_account_type', 'destination_account_types.type as destination_account_type',
'native_currencies.id as transaction_currency_id',
'native_currencies.code as transaction_currency_code',
'native_currencies.symbol as transaction_currency_symbol',
'foreign_currencies.id as foreign_currency_id',
'foreign_currencies.code as foreign_currency_code',
'foreign_currencies.symbol as foreign_currency_symbol',
] ]
); );
@ -111,6 +122,7 @@ class JournalTasker implements JournalTaskerInterface
$transaction = [ $transaction = [
'source_id' => $entry->id, 'source_id' => $entry->id,
'source_amount' => $entry->amount, 'source_amount' => $entry->amount,
'foreign_source_amount' => $entry->foreign_amount,
'description' => $entry->description, 'description' => $entry->description,
'source_account_id' => $entry->account_id, 'source_account_id' => $entry->account_id,
'source_account_name' => Steam::decrypt(intval($entry->account_encrypted), $entry->account_name), 'source_account_name' => Steam::decrypt(intval($entry->account_encrypted), $entry->account_name),
@ -119,6 +131,7 @@ class JournalTasker implements JournalTaskerInterface
'source_account_after' => bcadd($sourceBalance, $entry->amount), 'source_account_after' => bcadd($sourceBalance, $entry->amount),
'destination_id' => $entry->destination_id, 'destination_id' => $entry->destination_id,
'destination_amount' => bcmul($entry->amount, '-1'), 'destination_amount' => bcmul($entry->amount, '-1'),
'foreign_destination_amount' => is_null($entry->foreign_amount) ? null : bcmul($entry->foreign_amount, '-1'),
'destination_account_id' => $entry->destination_account_id, 'destination_account_id' => $entry->destination_account_id,
'destination_account_type' => $entry->destination_account_type, 'destination_account_type' => $entry->destination_account_type,
'destination_account_name' => Steam::decrypt(intval($entry->destination_account_encrypted), $entry->destination_account_name), 'destination_account_name' => Steam::decrypt(intval($entry->destination_account_encrypted), $entry->destination_account_name),
@ -126,6 +139,12 @@ class JournalTasker implements JournalTaskerInterface
'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')), 'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')),
'budget_id' => is_null($budget) ? 0 : $budget->id, 'budget_id' => is_null($budget) ? 0 : $budget->id,
'category' => is_null($category) ? '' : $category->name, 'category' => is_null($category) ? '' : $category->name,
'transaction_currency_id' => $entry->transaction_currency_id,
'transaction_currency_code' => $entry->transaction_currency_code,
'transaction_currency_symbol' => $entry->transaction_currency_symbol,
'foreign_currency_id' => $entry->foreign_currency_id,
'foreign_currency_code' => $entry->foreign_currency_code,
'foreign_currency_symbol' => $entry->foreign_currency_symbol,
]; ];
if ($entry->destination_account_type === AccountType::CASH) { if ($entry->destination_account_type === AccountType::CASH) {
$transaction['destination_account_name'] = ''; $transaction['destination_account_name'] = '';

View File

@ -17,6 +17,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Preferences as Prefs; use Preferences as Prefs;
@ -101,17 +102,6 @@ class Amount
return $format; return $format;
} }
/**
* @param string $amount
* @param bool $coloured
*
* @return string
*/
public function format(string $amount, bool $coloured = true): string
{
return $this->formatAnything($this->getDefaultCurrency(), $amount, $coloured);
}
/** /**
* This method will properly format the given number, in color or "black and white", * This method will properly format the given number, in color or "black and white",
* as a currency, given two things: the currency required and the current locale. * as a currency, given two things: the currency required and the current locale.
@ -159,49 +149,6 @@ class Amount
return $result; return $result;
} }
/**
* Used in many places (unfortunately).
*
* @param string $currencyCode
* @param string $amount
* @param bool $coloured
*
* @return string
*/
public function formatByCode(string $currencyCode, string $amount, bool $coloured = true): string
{
$currency = TransactionCurrency::where('code', $currencyCode)->first();
return $this->formatAnything($currency, $amount, $coloured);
}
/**
*
* @param \FireflyIII\Models\TransactionJournal $journal
* @param bool $coloured
*
* @return string
*/
public function formatJournal(TransactionJournal $journal, bool $coloured = true): string
{
$currency = $journal->transactionCurrency;
return $this->formatAnything($currency, $journal->amount(), $coloured);
}
/**
* @param Transaction $transaction
* @param bool $coloured
*
* @return string
*/
public function formatTransaction(Transaction $transaction, bool $coloured = true)
{
$currency = $transaction->transactionJournal->transactionCurrency;
return $this->formatAnything($currency, strval($transaction->amount), $coloured);
}
/** /**
* @return Collection * @return Collection
*/ */

View File

@ -37,15 +37,8 @@ class JournalList implements BinderInterface
$ids = explode(',', $value); $ids = explode(',', $value);
/** @var \Illuminate\Support\Collection $object */ /** @var \Illuminate\Support\Collection $object */
$object = TransactionJournal::whereIn('transaction_journals.id', $ids) $object = TransactionJournal::whereIn('transaction_journals.id', $ids)
->expanded()
->where('transaction_journals.user_id', auth()->user()->id) ->where('transaction_journals.user_id', auth()->user()->id)
->get( ->get(['transaction_journals.*',]);
[
'transaction_journals.*',
'transaction_types.type AS transaction_type_type',
'transaction_currencies.code AS transaction_currency_code',
]
);
if ($object->count() > 0) { if ($object->count() > 0) {
return $object; return $object;

View File

@ -213,6 +213,14 @@ trait TransactionJournalTrait
return 0; return 0;
} }
/**
* @return Transaction
*/
public function positiveTransaction(): Transaction
{
return $this->transactions()->where('amount', '>', 0)->first();
}
/** /**
* @return Collection * @return Collection
*/ */

View File

@ -1,63 +0,0 @@
<?php
/**
* Account.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Twig;
use FireflyIII\Models\Account as AccountModel;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount as AmountFacade;
use Twig_Extension;
use Twig_SimpleFunction;
/**
* Class Account
*
* @package FireflyIII\Support\Twig
*/
class Account extends Twig_Extension
{
/**
* {@inheritDoc}
*/
public function getFunctions(): array
{
return [
$this->formatAmountByAccount(),
];
}
/**
* Will return "active" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
*
* @return Twig_SimpleFunction
*/
protected function formatAmountByAccount(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string {
$currencyId = intval($account->getMeta('currency_id'));
if ($currencyId === 0) {
// Format using default currency:
return AmountFacade::format($amount, $coloured);
}
$currency = TransactionCurrency::find($currencyId);
return AmountFacade::formatAnything($currency, $amount, $coloured);
}, ['is_safe' => ['html']]
);
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* AmountFormat.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Twig;
use FireflyIII\Models\Account as AccountModel;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use Twig_Extension;
use Twig_SimpleFilter;
use Twig_SimpleFunction;
/**
* Contains all amount formatting routines.
*
* @package FireflyIII\Support\Twig
*/
class AmountFormat extends Twig_Extension
{
/**
* {@inheritDoc}
*/
public function getFilters(): array
{
return [
$this->formatAmount(),
$this->formatAmountPlain(),
];
}
/**
* {@inheritDoc}
*/
public function getFunctions(): array
{
return [
$this->formatAmountByAccount(),
];
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName(): string
{
return 'FireflyIII\Support\Twig\AmountFormat';
}
/**
*
* @return Twig_SimpleFilter
*/
protected function formatAmount(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
'formatAmount', function (string $string): string {
return app('amount')->format($string);
}, ['is_safe' => ['html']]
);
}
/**
* Will format the amount by the currency related to the given account.
*
* @return Twig_SimpleFunction
*/
protected function formatAmountByAccount(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string {
$currencyId = intval($account->getMeta('currency_id'));
if ($currencyId === 0) {
// Format using default currency:
return app('amount')->format($amount, $coloured);
}
$currency = TransactionCurrency::find($currencyId);
return app('amount')->formatAnything($currency, $amount, $coloured);
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFilter
*/
protected function formatAmountPlain(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
'formatAmountPlain', function (string $string): string {
return app('amount')->format($string, false);
}, ['is_safe' => ['html']]
);
}
}

View File

@ -38,9 +38,6 @@ class General extends Twig_Extension
public function getFilters(): array public function getFilters(): array
{ {
return [ return [
$this->formatAmount(),
$this->formatAmountPlain(),
$this->formatJournal(),
$this->balance(), $this->balance(),
$this->formatFilesize(), $this->formatFilesize(),
$this->mimeIcon(), $this->mimeIcon(),
@ -173,33 +170,6 @@ class General extends Twig_Extension
); );
} }
/**
*
* @return Twig_SimpleFilter
*/
protected function formatAmount(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
'formatAmount', function (string $string): string {
return app('amount')->format($string);
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFilter
*/
protected function formatAmountPlain(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
'formatAmountPlain', function (string $string): string {
return app('amount')->format($string, false);
}, ['is_safe' => ['html']]
);
}
/** /**
* @return Twig_SimpleFilter * @return Twig_SimpleFilter
*/ */
@ -223,17 +193,6 @@ class General extends Twig_Extension
); );
} }
/**
* @return Twig_SimpleFilter
*/
protected function formatJournal(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
'formatJournal', function (TransactionJournal $journal): string {
return app('amount')->formatJournal($journal);
}, ['is_safe' => ['html']]
);
}
/** /**
* @return Twig_SimpleFunction * @return Twig_SimpleFunction

View File

@ -16,7 +16,6 @@ namespace FireflyIII\Support\Twig;
use Amount; use Amount;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction as TransactionModel; use FireflyIII\Models\Transaction as TransactionModel;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use Steam; use Steam;
use Twig_Extension; use Twig_Extension;
@ -30,49 +29,6 @@ use Twig_SimpleFunction;
*/ */
class Transaction extends Twig_Extension class Transaction extends Twig_Extension
{ {
/**
* @return Twig_SimpleFunction
*/
public function formatAnything(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'formatAnything', function (TransactionCurrency $currency, string $amount): string {
return Amount::formatAnything($currency, $amount, true);
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFunction
*/
public function formatAnythingPlain(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'formatAnythingPlain', function (TransactionCurrency $currency, string $amount): string {
return Amount::formatAnything($currency, $amount, false);
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFunction
*/
public function formatByCode(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'formatByCode', function (string $currencyCode, string $amount): string {
return Amount::formatByCode($currencyCode, $amount, true);
}, ['is_safe' => ['html']]
);
}
/** /**
* @return array * @return array
*/ */
@ -91,17 +47,13 @@ class Transaction extends Twig_Extension
public function getFunctions(): array public function getFunctions(): array
{ {
$functions = [ $functions = [
$this->formatAnything(),
$this->formatAnythingPlain(),
$this->transactionSourceAccount(), $this->transactionSourceAccount(),
$this->transactionDestinationAccount(), $this->transactionDestinationAccount(),
$this->optionalJournalAmount(),
$this->transactionBudgets(), $this->transactionBudgets(),
$this->transactionIdBudgets(), $this->transactionIdBudgets(),
$this->transactionCategories(), $this->transactionCategories(),
$this->transactionIdCategories(), $this->transactionIdCategories(),
$this->splitJournalIndicator(), $this->splitJournalIndicator(),
$this->formatByCode(),
]; ];
return $functions; return $functions;
@ -117,33 +69,6 @@ class Transaction extends Twig_Extension
return 'transaction'; return 'transaction';
} }
/**
* @return Twig_SimpleFunction
*/
public function optionalJournalAmount(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'optionalJournalAmount', function (int $journalId, string $transactionAmount, string $code, string $type): string {
// get amount of journal:
$amount = strval(TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->where('amount', '<', 0)->sum('amount'));
// display deposit and transfer positive
if ($type === TransactionType::DEPOSIT || $type === TransactionType::TRANSFER) {
$amount = bcmul($amount, '-1');
}
// not equal to transaction amount?
if (bccomp($amount, $transactionAmount) !== 0 && bccomp($amount, bcmul($transactionAmount, '-1')) !== 0) {
//$currency =
return sprintf(' (%s)', Amount::formatByCode($code, $amount, true));
}
return '';
}, ['is_safe' => ['html']]
);
}
/** /**
* @return Twig_SimpleFunction * @return Twig_SimpleFunction
*/ */

View File

@ -89,6 +89,7 @@ $factory->define(
'user_id' => 1, 'user_id' => 1,
'transaction_type_id' => 1, 'transaction_type_id' => 1,
'bill_id' => null, 'bill_id' => null,
// TODO update this transaction currency reference.
'transaction_currency_id' => 1, 'transaction_currency_id' => 1,
'description' => $faker->words(3, true), 'description' => $faker->words(3, true),
'date' => '2017-01-01', 'date' => '2017-01-01',

View File

@ -38,11 +38,12 @@ function updateInitialPage() {
$('#native_amount_holder').hide(); $('#native_amount_holder').hide();
$('#amount_holder').hide(); $('#amount_holder').hide();
if (journalData.native_currency.id === journalData.currency.id) {
if (journalData.native_currency.id === journalData.destination_currency.id) {
$('#exchange_rate_instruction_holder').hide(); $('#exchange_rate_instruction_holder').hide();
$('#destination_amount_holder').hide(); $('#destination_amount_holder').hide();
} }
if (journalData.native_currency.id !== journalData.currency.id) { if (journalData.native_currency.id !== journalData.destination_currency.id) {
$('#exchange_rate_instruction_holder').show().find('p').text(getTransferExchangeInstructions()); $('#exchange_rate_instruction_holder').show().find('p').text(getTransferExchangeInstructions());
} }

View File

@ -166,11 +166,31 @@ function resetSplits() {
var input = $(v); var input = $(v);
input.attr('name', 'transactions[' + i + '][amount]'); input.attr('name', 'transactions[' + i + '][amount]');
}); });
// ends with ][foreign_amount]
$.each($('input[name$="][foreign_amount]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][foreign_amount]');
});
// ends with ][transaction_currency_id]
$.each($('input[name$="][transaction_currency_id]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][transaction_currency_id]');
});
// ends with ][foreign_currency_id]
$.each($('input[name$="][foreign_currency_id]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transactions[' + i + '][foreign_currency_id]');
});
// ends with ][budget_id] // ends with ][budget_id]
$.each($('select[name$="][budget_id]"]'), function (i, v) { $.each($('select[name$="][budget_id]"]'), function (i, v) {
var input = $(v); var input = $(v);
input.attr('name', 'transactions[' + i + '][budget_id]'); input.attr('name', 'transactions[' + i + '][budget_id]');
}); });
// ends with ][category] // ends with ][category]
$.each($('input[name$="][category]"]'), function (i, v) { $.each($('input[name$="][category]"]'), function (i, v) {
var input = $(v); var input = $(v);

View File

@ -962,6 +962,7 @@ return [
'split_this_transfer' => 'Split this transfer', 'split_this_transfer' => 'Split this transfer',
'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.',
'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.',
'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.',
'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.',
// import // import

View File

@ -14,10 +14,8 @@
{{ transaction.description }} {{ transaction.description }}
{% endif %} {% endif %}
<span class="pull-right small"> <span class="pull-right small">
<!-- format amount of transaction --> {# TODO replace with new format code #}
{{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} XX.XX
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }}
</span> </span>
</a> </a>
{% endfor %} {% endfor %}

View File

@ -64,14 +64,11 @@
<span style="margin-right:5px;"> <span style="margin-right:5px;">
{% if transaction.transaction_type_type == 'Transfer' %} {% if transaction.transaction_type_type == 'Transfer' %}
<!-- format amount of transaction --> <!-- format amount of transaction -->
{{ formatByCode(transaction.transaction_currency_code, steam_positive(transaction.transaction_amount)) }} {# TODO format amount of transaction. #}
<!-- and then amount of journal itself. --> {# TODO format: Amount of transaction (amount in foreign) / total (total foreign) #}
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} XX.XX
{% else %} {% else %}
<!-- format amount of transaction --> XX.XX
{{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }}
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }}
{% endif %} {% endif %}
</span> </span>

View File

@ -45,10 +45,8 @@
</a> </a>
</td> </td>
<td> <td>
<!-- format amount of transaction --> {# TODO replace with new format code #}
{{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} XX.XX
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }}
</td> </td>
<td class="hidden-sm hidden-xs"> <td class="hidden-sm hidden-xs">
{{ transaction.date.formatLocalized(monthAndDayFormat) }} {{ transaction.date.formatLocalized(monthAndDayFormat) }}

View File

@ -59,10 +59,8 @@
</td> </td>
<td class="hide-balance_before" style="text-align: right;">{{ transaction.before|formatAmount }}</td> <td class="hide-balance_before" style="text-align: right;">{{ transaction.before|formatAmount }}</td>
<td class="hide-amount" style="text-align: right;"> <td class="hide-amount" style="text-align: right;">
<!-- format amount of transaction --> {# TODO replace with new format code #}
{{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} XX.XX
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }}
</td> </td>
<td class="hide-balance_after" style="text-align: right;">{{ transaction.after|formatAmount }}</td> <td class="hide-balance_after" style="text-align: right;">{{ transaction.after|formatAmount }}</td>

View File

@ -62,17 +62,8 @@
</td> </td>
<td style="text-align: right;"> <td style="text-align: right;">
<span style="margin-right:5px;"> <span style="margin-right:5px;">
{% if transaction.transaction_type_type == 'Transfer' %} {# TODO replace with new format code #}
<!-- format amount of transaction --> XX.XX
{{ formatByCode(transaction.transaction_currency_code, steam_positive(transaction.transaction_amount)) }}
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }}
{% else %}
<!-- format amount of transaction -->
{{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }}
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }}
{% endif %}
</span> </span>
</td> </td>

View File

@ -39,10 +39,8 @@
</td> </td>
<td data-value="{{ transaction.transaction_amount }}"> <td data-value="{{ transaction.transaction_amount }}">
<!-- format amount of transaction --> {# TODO replace with new format code #}
{{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} XX.XX
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }}
</td> </td>

View File

@ -43,7 +43,8 @@
<a href="{{ route('transactions.show',journal.id) }}" title="{{ journal.description }}">{{ journal.description }}</a> <a href="{{ route('transactions.show',journal.id) }}" title="{{ journal.description }}">{{ journal.description }}</a>
</td> </td>
<td> <td>
{{ journal|formatJournal }} {# TODO fix amount display #}
XX.XX
</td> </td>
<td> <td>
{{ journal.date.formatLocalized(monthAndDayFormat) }} {{ journal.date.formatLocalized(monthAndDayFormat) }}

View File

@ -47,11 +47,20 @@
</td> </td>
<td> <td>
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<span class="input-group-addon">{{ journal.transactionCurrency.symbol }}</span> <span class="input-group-addon">{{ journal.currency_symbol }}</span>
<input name="amount[{{ journal.id }}]" class="form-control" autocomplete="off" <input name="amount[{{ journal.id }}]" class="form-control" autocomplete="off"
step="any" type="number" value="{{ journal.amount }}"> step="any" type="number" value="{{ journal.amount }}">
<input type="hidden" name="transaction_currency_id[{{ journal.id }}]" value="{{ journal.transaction_currency_id }}">
</div> </div>
{% if journal.foreign_amount %}
{# insert foreign data #}
<div class="input-group input-group-sm">
<span class="input-group-addon">{{ journal.foreign_currency.symbol }}</span>
<input name="foreign_amount[{{ journal.id }}]" class="form-control" autocomplete="off"
step="any" type="number" value="{{ journal.foreign_amount }}">
<input type="hidden" name="foreign_currency_id[{{ journal.id }}]" value="{{ journal.foreign_currency.id }}">
</div>
{% endif %}
</td> </td>
<td> <td>
{# DATE #} {# DATE #}

View File

@ -36,14 +36,9 @@
<!-- total amount --> <!-- total amount -->
<tr> <tr>
<td>{{ 'total_amount'|_ }}</td> <td>{{ 'total_amount'|_ }}</td>
<td>{{ journal|formatJournal }} <td>
{% if journal.hasMeta('foreign_amount') %} {# TODO fix amount display #}
{% if journal.transactiontype.type == 'Withdrawal' %} XX.XX
({{ formatAnything(foreignCurrency, journal.getMeta('foreign_amount')*-1) }})
{% else %}
({{ formatAnything(foreignCurrency, journal.getMeta('foreign_amount')) }})
{% endif %}
{% endif %}
</td> </td>
</tr> </tr>
<tr> <tr>
@ -304,9 +299,8 @@
</td> </td>
<td> <td>
{# TODO replace with new display: #}
{{ formatAnything(journal.transactionCurrency, transaction.source_account_before) }} XX.XX
&longrightarrow; {{ formatAnything(journal.transactionCurrency, transaction.source_account_after) }}
</td> </td>
<td> <td>
{% if transaction.destination_account_type == 'Cash account' %} {% if transaction.destination_account_type == 'Cash account' %}
@ -317,25 +311,27 @@
</td> </td>
<td> <td>
{# TODO replace with new format code #}
{{ formatAnything(journal.transactionCurrency, transaction.destination_account_before) }} XX.XX
&longrightarrow; {{ formatAnything(journal.transactionCurrency, transaction.destination_account_after) }}
</td> </td>
<td> <td>
{% if journal.transactiontype.type == 'Deposit' %} {% if journal.transactiontype.type == 'Deposit' %}
<!-- deposit, positive amount with correct currency --> <!-- deposit, positive amount with correct currency -->
{{ formatAnything(journal.transactionCurrency, transaction.destination_amount) }} {# TODO replace with new format code #}
XX.XX
{% endif %} {% endif %}
{% if journal.transactiontype.type == 'Withdrawal' %} {% if journal.transactiontype.type == 'Withdrawal' %}
<!-- withdrawal, negative amount with correct currency --> <!-- withdrawal, negative amount with correct currency -->
{{ formatAnything(journal.transactionCurrency, transaction.source_amount) }} {# TODO replace with new format code #}
XX.XX
{% endif %} {% endif %}
{% if journal.transactiontype.type == 'Transfer' %} {% if journal.transactiontype.type == 'Transfer' %}
<!-- transfer, positive amount in blue --> <!-- transfer, positive amount in blue -->
<span class="text-info"> <span class="text-info">
{{ formatAnythingPlain(journal.transactionCurrency, transaction.destination_amount) }} {# TODO replace with new format code #}
XX.XX
</span> </span>
{% endif %} {% endif %}
</td> </td>

View File

@ -64,9 +64,9 @@
{{ ExpandedForm.nonSelectableAmount('native_amount', data.native_amount, {currency: data.native_currency}) }} {{ ExpandedForm.nonSelectableAmount('native_amount', data.native_amount, {currency: data.native_currency}) }}
{{ ExpandedForm.nonSelectableAmount('source_amount', data.native_amount, {currency: data.native_currency }) }} {{ ExpandedForm.nonSelectableAmount('source_amount', data.source_amount, {currency: data.source_currency }) }}
{{ ExpandedForm.nonSelectableAmount('destination_amount', data.amount, {currency: data.currency }) }} {{ ExpandedForm.nonSelectableAmount('destination_amount', data.destination_amount, {currency: data.destination_currency }) }}
{# ALWAYS SHOW DATE #} {# ALWAYS SHOW DATE #}
{{ ExpandedForm.date('date',data['date']) }} {{ ExpandedForm.date('date',data['date']) }}

View File

@ -40,9 +40,6 @@
{# DESCRIPTION IS ALWAYS AVAILABLE #} {# DESCRIPTION IS ALWAYS AVAILABLE #}
{{ ExpandedForm.text('journal_description', journal.description) }} {{ ExpandedForm.text('journal_description', journal.description) }}
{# CURRENCY IS NEW FOR SPLIT JOURNALS #}
{{ ExpandedForm.select('currency_id', currencies, preFilled.currency_id) }}
{# show source if withdrawal or transfer #} {# show source if withdrawal or transfer #}
{% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %}
{{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }}
@ -59,6 +56,7 @@
{% endif %} {% endif %}
{# TOTAL AMOUNT IS STATIC TEXT #} {# TOTAL AMOUNT IS STATIC TEXT #}
{# TODO this does not reflect the actual currency (currencies) #}
{{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }}
<input type="hidden" name="journal_amount" value="{{ preFilled.journal_amount }}"/> <input type="hidden" name="journal_amount" value="{{ preFilled.journal_amount }}"/>
@ -204,7 +202,7 @@
<th>{{ trans('list.source') }}</th> <th>{{ trans('list.source') }}</th>
{% endif %} {% endif %}
<th>{{ trans('list.amount') }}</th> <th colspan="2">{{ trans('list.amount') }}</th>
{# only withdrawal has budget #} {# only withdrawal has budget #}
{% if preFilled.what == 'withdrawal' %} {% if preFilled.what == 'withdrawal' %}
@ -234,7 +232,7 @@
</td> </td>
{% endif %} {% endif %}
<!-- deposit has several source names --> {# deposit has several source names #}
{% if preFilled.what == 'deposit' %} {% if preFilled.what == 'deposit' %}
<td> <td>
<input type="text" name="transactions[{{ loop.index0 }}][source_account_name]" <input type="text" name="transactions[{{ loop.index0 }}][source_account_name]"
@ -243,10 +241,26 @@
</td> </td>
{% endif %} {% endif %}
{# two fields for amount #}
<td style="width:10%;"> <td style="width:10%;">
<div class="input-group">
<div class="input-group-addon">{{ transaction.transaction_currency_symbol }}</div>
<input type="number" name="transactions[{{ loop.index0 }}][amount]" value="{{ transaction.amount }}" <input type="number" name="transactions[{{ loop.index0 }}][amount]" value="{{ transaction.amount }}"
class="form-control" autocomplete="off" step="any" min="0.01"> class="form-control" autocomplete="off" step="any" min="0.01">
</div>
<input type="hidden" name="transactions[{{ loop.index0 }}][transaction_currency_id]" value="{{ transaction.transaction_currency_id }}">
</td>
{# foreign amount #}
<td style="width:10%;">
{% if transaction.foreign_amount != null %}
<div class="input-group">
<div class="input-group-addon">{{ transaction.foreign_currency_symbol }}</div>
<input type="number" name="transactions[{{ loop.index0 }}][foreign_amount]"
value="{{ transaction.foreign_amount }}"
class="form-control" autocomplete="off" step="any" min="0.01">
</div>
<input type="hidden" name="transactions[{{ loop.index0 }}][foreign_currency_id]" value="{{ transaction.foreign_currency_id }}">
{% endif %}
</td> </td>
{% if preFilled.what == 'withdrawal' %} {% if preFilled.what == 'withdrawal' %}