Restucturing some code.

This commit is contained in:
James Cole
2016-10-15 12:39:34 +02:00
parent 2f9a4bb79a
commit 8e48e53f17
9 changed files with 708 additions and 280 deletions

View File

@@ -24,6 +24,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Preferences; use Preferences;
use Response; use Response;
@@ -270,17 +271,13 @@ class TransactionController extends Controller
* *
* @return View * @return View
*/ */
public function show(TransactionJournal $journal, JournalRepositoryInterface $repository) public function show(TransactionJournal $journal, JournalRepositoryInterface $repository, JournalTaskerInterface $tasker)
{ {
$events = $repository->getPiggyBankEvents($journal); $events = $repository->getPiggyBankEvents($journal);
$transactions = $repository->getTransactions($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) . '"';
if ($transactions->count() > 2) {
return view('split.journals.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
}
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions')); return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));

View File

@@ -41,6 +41,12 @@ class JournalServiceProvider extends ServiceProvider
* @return void * @return void
*/ */
public function register() public function register()
{
$this->registerRepository();
$this->registerTasker();
}
private function registerRepository()
{ {
$this->app->bind( $this->app->bind(
'FireflyIII\Repositories\Journal\JournalRepositoryInterface', 'FireflyIII\Repositories\Journal\JournalRepositoryInterface',
@@ -56,4 +62,21 @@ class JournalServiceProvider extends ServiceProvider
} }
); );
} }
private function registerTasker()
{
$this->app->bind(
'FireflyIII\Repositories\Journal\JournalTaskerInterface',
function (Application $app, array $arguments) {
if (!isset($arguments[0]) && $app->auth->check()) {
return app('FireflyIII\Repositories\Journal\JournalTasker', [auth()->user()]);
}
if (!isset($arguments[0]) && !$app->auth->check()) {
throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Journal\JournalTasker', $arguments);
}
);
}
} }

View File

@@ -26,6 +26,7 @@ use Illuminate\Support\Collection;
*/ */
interface JournalRepositoryInterface interface JournalRepositoryInterface
{ {
/** /**
* Returns the amount in the account before the specified transaction took place. * Returns the amount in the account before the specified transaction took place.
* *

View File

@@ -0,0 +1,231 @@
<?php
/**
* JournalTasker.php
* Copyright (C) 2016 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\Repositories\Journal;
use Crypt;
use DB;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
/**
* Class JournalTasker
*
* @package FireflyIII\Repositories\Journal
*/
class JournalTasker implements JournalTaskerInterface
{
/** @var User */
private $user;
/**
* JournalRepository constructor.
*
* @param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get an overview of the transactions of a journal, tailored to the view
* that shows a transaction (transaction/show/xx).
*
* @param TransactionJournal $journal
*
* @return array
*/
public function getTransactionsOverview(TransactionJournal $journal): array
{
// get all transaction data + the opposite site in one list.
/**
* select
*
* source.id,
* source.account_id,
* source_accounts.name as account_name,
* source_accounts.encrypted as account_encrypted,
* source.amount,
* source.description,
*
* destination.id as destination_id,
* destination.account_id as destination_account_id,
* destination_accounts.name as destination_account_name,
* destination_accounts.encrypted as destination_account_encrypted
*
*
* from transactions as source
*
* left join transactions as destination ON source.transaction_journal_id = destination.transaction_journal_id AND source.amount = destination.amount * -1 AND source.identifier = destination.identifier
* -- left join source account name:
* left join accounts as source_accounts ON source.account_id = source_accounts.id
* left join accounts as destination_accounts ON destination.account_id = destination_accounts.id
*
* where source.transaction_journal_id = 6600
* and source.amount < 0
* and source.deleted_at is null
*/
$set = $journal
->transactions()// "source"
->leftJoin(
'transactions as destination', function (JoinClause $join) {
$join
->on('transactions.transaction_journal_id', '=', 'destination.transaction_journal_id')
->where('transactions.amount', '=', DB::raw('destination.amount * -1'))
->where('transactions.identifier', '=', DB::raw('destination.identifier'));
}
)
->leftJoin('accounts as source_accounts', 'transactions.account_id', '=', 'source_accounts.id')
->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
->where('transactions.amount', '<', 0)
->get(
[
'transactions.id',
'transactions.account_id',
'source_accounts.name as account_name',
'source_accounts.encrypted as account_encrypted',
'transactions.amount',
'transactions.description',
'destination.id as destination_id',
'destination.account_id as destination_account_id',
'destination_accounts.name as destination_account_name',
'destination_accounts.encrypted as destination_account_encrypted',
]
);
$transactions = [];
/** @var Transaction $entry */
foreach ($set as $entry) {
$sourceBalance = $this->getBalance($entry->id);
$destinationBalance = $this->getBalance($entry->destination_id);
$transaction = [
'source_id' => $entry->id,
'source_amount' => $entry->amount,
'description' => $entry->description,
'source_account_id' => $entry->account_id,
'source_account_name' => intval($entry->account_encrypted) === 1 ? Crypt::decrypt($entry->account_name) : $entry->account_name,
'source_account_before' => $sourceBalance,
'source_account_after' => bcadd($sourceBalance, $entry->amount),
'destination_id' => $entry->destination_id,
'destination_amount' => bcmul($entry->amount, '-1'),
'destination_account_id' => $entry->destination_account_id,
'destination_account_name' =>
intval($entry->destination_account_encrypted) === 1 ? Crypt::decrypt($entry->destination_account_name) : $entry->destination_account_name,
'destination_account_before' => $destinationBalance,
'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')),
];
$transactions[] = $transaction;
}
return $transactions;
}
/**
* Collect the balance of an account before the given transaction has hit. This is tricky, because
* the balance does not depend on the transaction itself but the journal it's part of. And of course
* the order of transactions within the journal. So the query is pretty complex:
*
* @param int $transactionId
*
* @return string
*/
private function getBalance(int $transactionId): string
{
/*
select
-- transactions.*, transaction_journals.date, transaction_journals.order, transaction_journals.id, transactions.identifier
sum(transactions.amount)
from transactions
and (
-- first things first: remove all transaction journals that are newer by selecting only those that are earlier:
or
-- date is 03 but sorted lower: (fucntion 1)
(
transaction_journals.date = "2016-09-20"
and transaction_journals.order > 2)
or
-- date is 03 and sort is the same but id is higher (func 2)
(transaction_journals.date = "2016-09-20"
and transaction_journals.order = 2
and transaction_journals.id < 6966
)
-- date is 03 and sort is the same, and id is the same but identifier is 1 and not 0.(func 3)
or
(transaction_journals.date = "2016-09-20"
and transaction_journals.order = 2
and transaction_journals.id = 6966
and transactions.identifier > 1
)
) -- 14048
and transactions.id != 14048 -- just in case
order by transaction_journals.date DESC, transaction_journals.order ASC, transaction_journals.id DESC, transactions.identifier ASC
*/
// find the transaction first:
$transaction = Transaction::find($transactionId);
$date = $transaction->transactionJournal->date->format('Y-m-d');
$order = intval($transaction->transactionJournal->order);
$journalId = intval($transaction->transaction_journal_id);
$identifier = intval($transaction->identifier);
// go!
$sum
= Transaction
::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('account_id', $transaction->account_id)
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->where('transactions.id', '!=', $transactionId)
->where(
function (Builder $q1) use ($date, $order, $journalId, $identifier) {
$q1->where('transaction_journals.date', '<', $date); // date
$q1->orWhere(
function (Builder $q2) use ($date, $order) { // function 1
$q2->where('transaction_journals.date', $date);
$q2->where('transaction_journals.order', '>', $order);
}
);
$q1->orWhere(
function (Builder $q3) use ($date, $order, $journalId) { // function 2
$q3->where('transaction_journals.date', $date);
$q3->where('transaction_journals.order', $order);
$q3->where('transaction_journals.id', '<', $journalId);
}
);
$q1->orWhere(
function (Builder $q4) use ($date, $order, $journalId, $identifier) { // function 3
$q4->where('transaction_journals.date', $date);
$q4->where('transaction_journals.order', $order);
$q4->where('transaction_journals.id', $journalId);
$q4->where('transactions.identifier', '>', $identifier);
}
);
}
)->sum('transactions.amount');
return strval($sum);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* JournalTaskerInterface.php
* Copyright (C) 2016 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\Repositories\Journal;
use FireflyIII\Models\TransactionJournal;
/**
* Interface JournalTaskerInterface
*
* @package FireflyIII\Repositories\Journal
*/
interface JournalTaskerInterface
{
/**
* Get an overview of the transactions of a journal, tailored to the view
* that shows a transaction (transaction/show/xx).
*
* @param TransactionJournal $journal
*
* @return array
*/
public function getTransactionsOverview(TransactionJournal $journal): array;
}

View File

@@ -29,6 +29,20 @@ use Twig_SimpleFunction;
*/ */
class Transaction extends Twig_Extension class Transaction extends Twig_Extension
{ {
/**
* @return Twig_SimpleFunction
*/
public function formatAmountPlainWithCode(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'formatAmountPlainWithCode', function (string $amount, string $code): string {
return Amount::formatWithCode($code, $amount, false);
}, ['is_safe' => ['html']]
);
}
/** /**
* @return Twig_SimpleFunction * @return Twig_SimpleFunction
*/ */
@@ -62,11 +76,14 @@ class Transaction extends Twig_Extension
{ {
$functions = [ $functions = [
$this->formatAmountWithCode(), $this->formatAmountWithCode(),
$this->formatAmountPlainWithCode(),
$this->transactionSourceAccount(), $this->transactionSourceAccount(),
$this->transactionDestinationAccount(), $this->transactionDestinationAccount(),
$this->optionalJournalAmount(), $this->optionalJournalAmount(),
$this->transactionBudgets(), $this->transactionBudgets(),
$this->transactionIdBudgets(),
$this->transactionCategories(), $this->transactionCategories(),
$this->transactionIdCategories(),
$this->splitJournalIndicator(), $this->splitJournalIndicator(),
]; ];
@@ -144,22 +161,7 @@ class Transaction extends Twig_Extension
{ {
return new Twig_SimpleFunction( return new Twig_SimpleFunction(
'transactionBudgets', function (TransactionModel $transaction): string { 'transactionBudgets', function (TransactionModel $transaction): string {
// see if the transaction has a budget: return $this->getTransactionBudgets($transaction);
$budgets = $transaction->budgets()->get();
if ($budgets->count() === 0) {
$budgets = $transaction->transactionJournal()->first()->budgets()->get();
}
if ($budgets->count() > 0) {
$str = [];
foreach ($budgets as $budget) {
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$budget->id]), $budget->name, $budget->name);
}
return join(', ', $str);
}
return '';
}, ['is_safe' => ['html']] }, ['is_safe' => ['html']]
); );
} }
@@ -171,21 +173,7 @@ class Transaction extends Twig_Extension
{ {
return new Twig_SimpleFunction( return new Twig_SimpleFunction(
'transactionCategories', function (TransactionModel $transaction): string { 'transactionCategories', function (TransactionModel $transaction): string {
// see if the transaction has a category: return $this->getTransactionCategories($transaction);
$categories = $transaction->categories()->get();
if ($categories->count() === 0) {
$categories = $transaction->transactionJournal()->first()->categories()->get();
}
if ($categories->count() > 0) {
$str = [];
foreach ($categories as $category) {
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$category->id]), $category->name, $category->name);
}
return join(', ', $str);
}
return '';
}, ['is_safe' => ['html']] }, ['is_safe' => ['html']]
); );
} }
@@ -228,6 +216,34 @@ class Transaction extends Twig_Extension
); );
} }
/**
* @return Twig_SimpleFunction
*/
public function transactionIdBudgets(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'transactionIdBudgets', function (int $transactionId): string {
$transaction = TransactionModel::find($transactionId);
return $this->getTransactionBudgets($transaction);
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFunction
*/
public function transactionIdCategories(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'transactionIdCategories', function (int $transactionId): string {
$transaction = TransactionModel::find($transactionId);
return $this->getTransactionCategories($transaction);
}, ['is_safe' => ['html']]
);
}
/** /**
* @return Twig_SimpleFunction * @return Twig_SimpleFunction
*/ */
@@ -298,4 +314,53 @@ class Transaction extends Twig_Extension
}, ['is_safe' => ['html']] }, ['is_safe' => ['html']]
); );
} }
/**
* @param TransactionModel $transaction
*
* @return string
*/
private function getTransactionBudgets(TransactionModel $transaction): string
{
// see if the transaction has a budget:
$budgets = $transaction->budgets()->get();
if ($budgets->count() === 0) {
$budgets = $transaction->transactionJournal()->first()->budgets()->get();
}
if ($budgets->count() > 0) {
$str = [];
foreach ($budgets as $budget) {
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$budget->id]), $budget->name, $budget->name);
}
return join(', ', $str);
}
return '';
}
/**
* @param TransactionModel $transaction
*
* @return string
*/
private function getTransactionCategories(TransactionModel $transaction): string
{
// see if the transaction has a category:
$categories = $transaction->categories()->get();
if ($categories->count() === 0) {
$categories = $transaction->transactionJournal()->first()->categories()->get();
}
if ($categories->count() > 0) {
$str = [];
foreach ($categories as $category) {
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$category->id]), $category->name, $category->name);
}
return join(', ', $str);
}
return '';
}
} }

View File

@@ -712,111 +712,113 @@ return [
'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.',
'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.',
'transaction_journal_information' => 'Transaction information',
'transaction_journal_meta' => 'Meta information',
// administration // administration
'administration' => 'Administration', 'administration' => 'Administration',
'user_administration' => 'User administration', 'user_administration' => 'User administration',
'list_all_users' => 'All users', 'list_all_users' => 'All users',
'all_users' => 'All users', 'all_users' => 'All users',
'all_blocked_domains' => 'All blocked domains', 'all_blocked_domains' => 'All blocked domains',
'blocked_domains' => 'Blocked domains', 'blocked_domains' => 'Blocked domains',
'no_domains_banned' => 'No domains blocked', 'no_domains_banned' => 'No domains blocked',
'all_user_domains' => 'All user email address domains', 'all_user_domains' => 'All user email address domains',
'all_domains_is_filtered' => 'This list does not include already blocked domains.', 'all_domains_is_filtered' => 'This list does not include already blocked domains.',
'domain_now_blocked' => 'Domain :domain is now blocked', 'domain_now_blocked' => 'Domain :domain is now blocked',
'domain_now_unblocked' => 'Domain :domain is now unblocked', 'domain_now_unblocked' => 'Domain :domain is now unblocked',
'manual_block_domain' => 'Block a domain by hand', 'manual_block_domain' => 'Block a domain by hand',
'block_domain' => 'Block domain', 'block_domain' => 'Block domain',
'no_domain_filled_in' => 'No domain filled in', 'no_domain_filled_in' => 'No domain filled in',
'domain_already_blocked' => 'Domain :domain is already blocked', 'domain_already_blocked' => 'Domain :domain is already blocked',
'domain_is_now_blocked' => 'Domain :domain is now blocked', 'domain_is_now_blocked' => 'Domain :domain is now blocked',
'instance_configuration' => 'Configuration', 'instance_configuration' => 'Configuration',
'firefly_instance_configuration' => 'Configuration options for Firefly III', 'firefly_instance_configuration' => 'Configuration options for Firefly III',
'setting_single_user_mode' => 'Single user mode', 'setting_single_user_mode' => 'Single user mode',
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Store configuration', 'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email', 'single_user_administration' => 'User administration for :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your <a href=":link">settings</a>.', 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your <a href=":link">settings</a>.',
'user_data_information' => 'User data', 'user_data_information' => 'User data',
'user_information' => 'User information', 'user_information' => 'User information',
'total_size' => 'total size', 'total_size' => 'total size',
'budget_or_budgets' => 'budget(s)', 'budget_or_budgets' => 'budget(s)',
'budgets_with_limits' => 'budget(s) with configured amount', 'budgets_with_limits' => 'budget(s) with configured amount',
'rule_or_rules' => 'rule(s)', 'rule_or_rules' => 'rule(s)',
'rulegroup_or_groups' => 'rule group(s)', 'rulegroup_or_groups' => 'rule group(s)',
// split a transaction: // split a transaction:
'transaction_meta_data' => 'Transaction meta-data', 'transaction_meta_data' => 'Transaction meta-data',
'transaction_dates' => 'Transaction dates', 'transaction_dates' => 'Transaction dates',
'splits' => 'Splits', 'splits' => 'Splits',
'split_title_withdrawal' => 'Split your new withdrawal', 'split_title_withdrawal' => 'Split your new withdrawal',
'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
'store_splitted_withdrawal' => 'Store splitted withdrawal', 'store_splitted_withdrawal' => 'Store splitted withdrawal',
'update_splitted_withdrawal' => 'Update splitted withdrawal', 'update_splitted_withdrawal' => 'Update splitted withdrawal',
'split_title_deposit' => 'Split your new deposit', 'split_title_deposit' => 'Split your new deposit',
'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.',
'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.',
'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.',
'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
'store_splitted_deposit' => 'Store splitted deposit', 'store_splitted_deposit' => 'Store splitted deposit',
'split_title_transfer' => 'Split your new transfer', 'split_title_transfer' => 'Split your new transfer',
'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
'store_splitted_transfer' => 'Store splitted transfer', 'store_splitted_transfer' => 'Store splitted transfer',
'add_another_split' => 'Add another split', 'add_another_split' => 'Add another split',
'split-transactions' => 'Split transactions', 'split-transactions' => 'Split transactions',
'split-new-transaction' => 'Split a new transaction', 'split-new-transaction' => 'Split a new transaction',
'do_split' => 'Do a split', 'do_split' => 'Do a split',
'split_this_withdrawal' => 'Split this withdrawal', 'split_this_withdrawal' => 'Split this withdrawal',
'split_this_deposit' => 'Split this deposit', 'split_this_deposit' => 'Split this deposit',
'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.',
'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
'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their <a href="https://github.com/firefly-iii/import-configurations/wiki">configuration file</a>.', 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their <a href="https://github.com/firefly-iii/import-configurations/wiki">configuration file</a>.',
'import_data_index' => 'Index', 'import_data_index' => 'Index',
'import_file_type_csv' => 'CSV (comma separated values)', 'import_file_type_csv' => 'CSV (comma separated values)',
'import_file_type_help' => 'Select the type of file you will upload', 'import_file_type_help' => 'Select the type of file you will upload',
'import_start' => 'Start the import', 'import_start' => 'Start the import',
'configure_import' => 'Further configure your import', 'configure_import' => 'Further configure your import',
'import_finish_configuration' => 'Finish configuration', 'import_finish_configuration' => 'Finish configuration',
'settings_for_import' => 'Settings', 'settings_for_import' => 'Settings',
'import_status' => 'Import status', 'import_status' => 'Import status',
'import_status_text' => 'The import is currently running, or will start momentarily.', 'import_status_text' => 'The import is currently running, or will start momentarily.',
'import_complete' => 'Import configuration complete!', 'import_complete' => 'Import configuration complete!',
'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.',
'import_download_config' => 'Download configuration', 'import_download_config' => 'Download configuration',
'import_start_import' => 'Start import', 'import_start_import' => 'Start import',
'import_intro_beta' => 'The import function of Firefly III is in beta. Many users of Firefly III have tried many different files. Although each individual compontent of this import routine works (really), the combination might break. If your file cannot be imported by Firefly, please read <a href="https://github.com/JC5/firefly-iii/wiki/Submit-issues-with-sensitive-data-in-them">this wiki page</a> so I can fix the problem you have run into.', 'import_intro_beta' => 'The import function of Firefly III is in beta. Many users of Firefly III have tried many different files. Although each individual compontent of this import routine works (really), the combination might break. If your file cannot be imported by Firefly, please read <a href="https://github.com/JC5/firefly-iii/wiki/Submit-issues-with-sensitive-data-in-them">this wiki page</a> so I can fix the problem you have run into.',
'import_data' => 'Import data', 'import_data' => 'Import data',
'import_data_full' => 'Import data into Firefly III', 'import_data_full' => 'Import data into Firefly III',
'import' => 'Import', 'import' => 'Import',
'import_intro_what_it_does' => 'This page allows you to import data into Firefly III. To do so, export data from your bank, or from another financial management system. Upload that file here. Firefly III will convert the data. You need to give it some directions. Please select a file and follow the instructions.', 'import_intro_what_it_does' => 'This page allows you to import data into Firefly III. To do so, export data from your bank, or from another financial management system. Upload that file here. Firefly III will convert the data. You need to give it some directions. Please select a file and follow the instructions.',
'import_intro_import_conf_title' => 'Import "configuration"', 'import_intro_import_conf_title' => 'Import "configuration"',
'import_intro_beta_warning' => 'Warning', 'import_intro_beta_warning' => 'Warning',
'import_intro_import_conf_text' => 'As you will discover over the next few pages, this import routine has a lot of settings. These settings are mainly dependent on the bank (or financial management software) your file comes from. There is a good chance somebody else already imported such a file and has shared their <em>configuration file</em>. Please visit the <strong><a href="https://github.com/firefly-iii/import-configurations/wiki">import configuration center</a></strong> to see if there already is a configuration available for your bank or system. If there is, you should download this configuration file and upload it here as well. It will save you a lot of time!', 'import_intro_import_conf_text' => 'As you will discover over the next few pages, this import routine has a lot of settings. These settings are mainly dependent on the bank (or financial management software) your file comes from. There is a good chance somebody else already imported such a file and has shared their <em>configuration file</em>. Please visit the <strong><a href="https://github.com/firefly-iii/import-configurations/wiki">import configuration center</a></strong> to see if there already is a configuration available for your bank or system. If there is, you should download this configuration file and upload it here as well. It will save you a lot of time!',
'import_file_help' => 'Select your file', 'import_file_help' => 'Select your file',
'import_status_settings_complete' => 'The import is ready to start.', 'import_status_settings_complete' => 'The import is ready to start.',
'import_status_import_complete' => 'The import has completed.', 'import_status_import_complete' => 'The import has completed.',
'import_status_import_running' => 'The import is currently running. Please be patient.', 'import_status_import_running' => 'The import is currently running. Please be patient.',
'import_status_header' => 'Import status and progress', 'import_status_header' => 'Import status and progress',
'import_status_errors' => 'Import errors', 'import_status_errors' => 'Import errors',
'import_status_report' => 'Import report', 'import_status_report' => 'Import report',
'import_finished' => 'Import has finished', 'import_finished' => 'Import has finished',
'import_error_single' => 'An error has occured during the import.', 'import_error_single' => 'An error has occured during the import.',
'import_error_multi' => 'Some errors occured during the import.', 'import_error_multi' => 'Some errors occured during the import.',
'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:',
'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.',
'import_double' => 'Row #:row: This row has been imported before, and is stored in <a href=":link">:description</a>.', 'import_double' => 'Row #:row: This row has been imported before, and is stored in <a href=":link">:description</a>.',
'import_finished_all' => 'The import has finished. Please check out the results below.', 'import_finished_all' => 'The import has finished. Please check out the results below.',
'import_with_key' => 'Import with key \':key\'', 'import_with_key' => 'Import with key \':key\'',
'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the <strong><a href="https://github.com/firefly-iii/import-configurations/wiki">import configuration center</a></strong>. This will allow other users of Firefly III to import their files more easily.', 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the <strong><a href="https://github.com/firefly-iii/import-configurations/wiki">import configuration center</a></strong>. This will allow other users of Firefly III to import their files more easily.',

View File

@@ -72,6 +72,8 @@ return [
'blocked_code' => 'Block code', 'blocked_code' => 'Block code',
'domain' => 'Domain', 'domain' => 'Domain',
'registration_attempts' => 'Registration attempts', 'registration_attempts' => 'Registration attempts',
'source_account' => 'Source account',
'destination_account' => 'Destination account',
'accounts_count' => 'Number of accounts', 'accounts_count' => 'Number of accounts',
'journals_count' => 'Number of journals', 'journals_count' => 'Number of journals',

View File

@@ -9,86 +9,134 @@
<div class="col-lg-6 col-md-6 col-sm-12"> <div class="col-lg-6 col-md-6 col-sm-12">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">Metadata</h3> <h3 class="box-title">{{ 'transaction_journal_information'|_ }}</h3>
</div> </div>
<div class="box-body table-responsive no-padding"> <div class="box-body table-responsive no-padding">
<table class="table table-hover sortable"> <table class="table table-hover">
<tr> <tbody>
<td>{{ trans('list.amount') }}</td>
<td>{{ journal|formatJournal }}</td>
</tr>
<tr>
<td>{{ trans('list.date') }}</td>
<td>{{ journal.date.formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% if journal.getMeta('interest_date') %}
<tr>
<td>{{ trans('list.interest_date') }}</td>
<td>{{ journal.getMeta('interest_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.getMeta('book_date') %}
<tr>
<td>{{ trans('list.book_date') }}</td>
<td>{{ journal.getMeta('book_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.getMeta('process_date') %}
<tr>
<td>{{ trans('list.process_date') }}</td>
<td>{{ journal.getMeta('process_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.getMeta('due_date') %}
<tr>
<td>{{ trans('list.due_date') }}</td>
<td>{{ journal.getMeta('due_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.getMeta('payment_date') %}
<tr>
<td>{{ trans('list.payment_date') }}</td>
<td>{{ journal.getMeta('payment_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.getMeta('invoice_date') %}
<tr>
<td>{{ trans('list.invoice_date') }}</td>
<td>{{ journal.getMeta('invoice_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
<tr> <tr>
<td>{{ trans('list.type') }}</td> <td>{{ trans('list.type') }}</td>
<td>{{ journal.transactiontype.type|_ }}</td> <td>{{ journal.transactiontype.type|_ }}</td>
</tr> </tr>
<tr> <tr>
<td>{{ trans('list.completed') }}</td> <td>{{ trans('list.description') }}</td>
<td> <td>{{ journal.description }}</td>
{% if journal.completed %}
<span class="text-success">{{ 'yes'|_ }}</span>
{% else %}
<span class="text-danger">{{ 'no'|_ }}</span>
{% endif %}
</td>
</tr> </tr>
{% for budget in journal.budgets %} <!-- source(s) -->
<tr>
<td>{{ 'source_accounts'|_ }}</td>
<td>{{ sourceAccount(journal)|raw }}</td>
</tr>
<!-- destination(s) -->
<tr>
<td>{{ 'destination_accounts'|_ }}</td>
<td>{{ destinationAccount(journal)|raw }}</td>
</tr>
<!-- total amount -->
<tr>
<td>{{ 'total_amount'|_ }}</td>
<td>{{ journal|formatJournal }}</td>
</tr>
<tr>
<td style="width:30%;">{{ trans('list.date') }}</td>
<td>{{ journal.date.formatLocalized(monthAndDayFormat) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="box-footer">
<div class="pull-right">
<a class="btn btn-default" href="{{ route('transactions.edit',journal.id) }}"> {{ 'edit'|_ }}</a>
<a href="{{ route('transactions.delete',journal.id) }}" class="btn btn-danger"> {{ 'delete'|_ }}</a>
</div>
</div>
</div>
<!-- events, if present -->
{% if journal.piggyBankEvents|length > 0 %}
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'piggyBanks'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
{% include 'list/piggy-bank-events' with {'events': events, 'showPiggyBank':true} %}
</div>
</div>
{% endif %}
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transaction_journal_meta'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tbody>
<tr>
<td>{{ 'categories'|_ }}</td>
<td>{{ journalCategories(journal)|raw }}</td>
</tr>
<tr>
<td>{{ 'budgets'|_ }}</td>
<td>{{ journalBudgets(journal)|raw }}</td>
</tr>
{% if journal.hasMeta('book_date') %}
<tr> <tr>
<td>{{ 'budget'|_ }}</td> <td>{{ trans('list.book_date') }}</td>
<td><a href="{{ route('budgets.show',budget.id) }}">{{ budget.name }}</a></td> <td>{{ journal.getMeta('book_date').formatLocalized(monthAndDayFormat) }}</td>
</tr> </tr>
{% endfor %} {% endif %}
{% for category in journal.categories %} {% if journal.hasMeta('process_date') %}
<tr> <tr>
<td>{{ 'category'|_ }}</td> <td>{{ trans('list.process_date') }}</td>
<td><a href="{{ route('categories.show',category.id) }}">{{ category.name }}</a></td> <td>{{ journal.getMeta('process_date').formatLocalized(monthAndDayFormat) }}</td>
</tr> </tr>
{% endfor %}
{% endif %}
{% if journal.hasMeta('interest_date') %}
<tr>
<td>{{ trans('list.interest_date') }}</td>
<td>{{ journal.getMeta('interest_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.hasMeta('due_date') %}
<tr>
<td>{{ trans('list.due_date') }}</td>
<td>{{ journal.getMeta('due_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.hasMeta('payment_date') %}
<tr>
<td>{{ trans('list.payment_date') }}</td>
<td>{{ journal.getMeta('payment_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.hasMeta('invoice_date') %}
<tr>
<td>{{ trans('list.invoice_date') }}</td>
<td>{{ journal.getMeta('invoice_date').formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% endif %}
{% if journal.hasMeta('internal_reference') %}
<tr>
<td>{{ trans('list.internal_reference') }}</td>
<td>{{ journal.getMeta('internal_reference') }}</td>
</tr>
{% endif %}
{% if journal.hasMeta('notes') %}
<tr>
<td>{{ trans('list.notes') }}</td>
<td>{{ journal.getMeta('notes')|nl2br }}</td>
</tr>
{% endif %}
{% if journal.bill %} {% if journal.bill %}
<tr> <tr>
<td>{{ 'bill'|_ }}</td> <td>{{ 'bill'|_ }}</td>
@@ -117,32 +165,12 @@
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
</tbody>
{% if journal.getMeta('internal_reference') %}
<tr>
<td>{{ trans('list.interal_reference') }}</td>
<td>{{ journal.getMeta('internal_reference') }}</td>
</tr>
{% endif %}
{% if journal.getMeta('notes') %}
<tr>
<td>{{ trans('list.notes') }}</td>
<td>{{ journal.getMeta('notes')|nl2br }}</td>
</tr>
{% endif %}
</table> </table>
</div> </div>
<div class="box-footer">
<div class="pull-right">
<a class="btn btn-default" href="{{ route('transactions.edit',journal.id) }}">{{ 'edit'|_ }}</a>
<a href="{{ route('transactions.delete',journal.id) }}" class="btn btn-danger">{{ 'delete'|_ }}</a>
</div>
</div>
</div> </div>
<!-- attachments for unsplitted journals -->
{% if journal.attachments|length > 0 %} {% if journal.attachments|length > 0 %}
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
@@ -182,60 +210,103 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<!-- events, if present -->
{% if journal.piggyBankEvents|length > 0 %}
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'piggyBanks'|_ }}</h3>
</div>
<div class="box-body">
{% include 'list/piggy-bank-events' with {'events': events, 'showPiggyBank':true} %}
</div>
</div>
{% endif %}
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
{% for t in transactions %}
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ t.account.name }}</h3>
</div>
<table class="table table-bordered table-striped">
<tr>
<td style="width:30%;">{{ 'account'|_ }}</td>
<td><a href="{{ route('accounts.show',t.account.id) }}">{{ t.account.name }}</a></td>
</tr>
<tr>
<td>{{ 'account_type'|_ }}</td>
<td>{{ t.account.accounttype.type|_ }}</td>
</tr>
<tr>
<td>{{ 'balance'|_ }}</td>
<td>{{ t.before|formatAmount }} &rarr; {{ (t.before+t.amount)|formatAmount }}</td>
</tr>
{% if t.description %}
<tr>
<td>{{ trans('form.description') }}</td>
<td>{{ t.description }}</td>
</tr>
{% endif %}
<tr>
<td>{{ 'category'|_ }}</td>
<td>
{{ transactionCategories(t)|raw }}
</td>
</tr>
<tr>
<td>{{ 'budget'|_ }}</td>
<td>
{{ transactionBudgets(t)|raw }}
</td>
</tr>
</table>
</div>
{% endfor %}
</div> </div>
</div> </div>
<!-- more than two transactions-->
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Transactions</h3>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>{{ trans('list.description') }}</th>
<th>{{ trans('list.source_account') }}</th>
<th>&#916;</th>
<th>{{ trans('list.destination_account') }}</th>
<th>&#916;</th>
<th>{{ trans('list.amount') }}</th>
<th>{{ trans('list.budget') }}</th>
<th>{{ trans('list.category') }}</th>
</tr>
</thead>
<tbody>
{% for transaction in transactions %}
<tr>
<td>#{{ transaction.source_id }}
{% if transaction.description == "" %}
{{ journal.description }}
{% else %}
{{ transaction.description }}
{% endif %}
</td>
<td>
<a href="{{ route('accounts.show', transaction.source_account_id) }}">{{ transaction.source_account_name }}</a>
</td>
<td>
{{ formatAmountWithCode(transaction.source_account_before,journal.transactionCurrency.code) }}
&longrightarrow; {{ formatAmountWithCode(transaction.source_account_after,journal.transactionCurrency.code) }}
</td>
<td>
<a href="{{ route('accounts.show', transaction.destination_account_id) }}">{{ transaction.destination_account_name }}</a>
</td>
<td>
{{ formatAmountWithCode(transaction.destination_account_before,journal.transactionCurrency.code) }}
&longrightarrow; {{ formatAmountWithCode(transaction.destination_account_after,journal.transactionCurrency.code) }}
</td>
<td>
{% if journal.transactiontype.type == 'Deposit' %}
<!-- deposit, positive amount with correct currency -->
{{ formatAmountWithCode(transaction.destination_amount, journal.transactionCurrency.code) }}
{% endif %}
{% if journal.transactiontype.type == 'Withdrawal' %}
<!-- withdrawal, negative amount with correct currency -->
{{ formatAmountWithCode(transaction.source_amount, journal.transactionCurrency.code) }}
{% endif %}
{% if journal.transactiontype.type == 'Transfer' %}
<!-- transfer, positive amount in blue -->
<span class="text-info">{{ formatAmountPlainWithCode(transaction.destination_amount, journal.transactionCurrency.code) }}</span>
{% endif %}
</td>
<td>
{{ transactionIdBudgets(transaction.source_id) }}
</td>
<td>
{{ transactionIdCategories(transaction.source_id) }}
</td>
</tr>
{#
<tr>
<td>
{% if (index+1) != transactions|length or what == 'transfer' %}
{{ t.description }}
{% endif %}
</td>
<td><a href="{{ route('accounts.show',t.account.id) }}">{{ t.account.name }}</a> ({{ t.account.accounttype.type|_ }})</td>
<td>{{ t.sum|formatAmount }}</td>
<td>{{ t.before|formatAmount }} &rarr; {{ (t.sum+t.before)|formatAmount }}</td>
<td>
{% if (index+1) != transactions|length or what == 'transfer' %}
{{ transactionBudgets(t)|raw }}
{% endif %}
</td>
<td>
{% if (index+1) != transactions|length or what == 'transfer' %}
{{ transactionCategories(t)|raw }}
{% endif %}
</td>
#}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- end -->
{% endblock %} {% endblock %}