Refactored accountRepository::getJournals > accountTasker > getJournals

This commit is contained in:
James Cole
2016-10-09 21:36:03 +02:00
parent 5bb8c6a366
commit e94ae126fd
25 changed files with 843 additions and 167 deletions

View File

@@ -218,7 +218,7 @@ class AccountController extends Controller
$page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end);
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
@@ -269,13 +269,13 @@ class AccountController extends Controller
}
/**
* @param ARI $repository
* @param Account $account
* @param string $date
* @param AccountTaskerInterface $tasker
* @param Account $account
* @param string $date
*
* @return View
*/
public function showWithDate(ARI $repository, Account $account, string $date)
public function showWithDate(AccountTaskerInterface $tasker, Account $account, string $date)
{
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data;
@@ -286,7 +286,7 @@ class AccountController extends Controller
$page = $page === 0 ? 1 : $page;
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end);
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);

View File

@@ -249,7 +249,7 @@ class BudgetController extends Controller
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end);
$journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); // budget
$count = $journals->count();
$journals = $journals->slice($offset, $pageSize);
$list = new LengthAwarePaginator($journals, $count, $pageSize);
@@ -295,7 +295,7 @@ class BudgetController extends Controller
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end);
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); // budget
$count = $journals->count();
$journals = $journals->slice($offset, $pageSize);
$journals = new LengthAwarePaginator($journals, $count, $pageSize);
@@ -334,7 +334,7 @@ class BudgetController extends Controller
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end);
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); // budget
$count = $journals->count();
$journals = $journals->slice($offset, $pageSize);
$journals = new LengthAwarePaginator($journals, $count, $pageSize);

View File

@@ -149,7 +149,7 @@ class CategoryController extends Controller
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->startOfMonth());
$list = $repository->journalsInPeriodWithoutCategory(new Collection(), [], $start, $end);
$list = $repository->journalsInPeriodWithoutCategory(new Collection(), [], $start, $end); // category
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -176,7 +176,7 @@ class CategoryController extends Controller
$page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end);
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); // category
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$subTitle = $category->name;
@@ -247,7 +247,7 @@ class CategoryController extends Controller
$page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end);
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); // category
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);

View File

@@ -401,7 +401,7 @@ class BudgetController extends Controller
*/
private function spentInPeriodWithout(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end):array
{
$list = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end);
$list = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); // budget
$sum = '0';
/** @var TransactionJournal $entry */
foreach ($list as $entry) {

View File

@@ -19,6 +19,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@@ -26,7 +27,6 @@ use Log;
use Preferences;
use Route;
use Session;
use Steam;
/**
@@ -114,12 +114,13 @@ class HomeController extends Controller
}
/**
* @param ARI $repository
* @param AccountCrudInterface $crud
* @param ARI $repository
* @param AccountCrudInterface $crud
* @param AccountTaskerInterface $tasker
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*/
public function index(ARI $repository, AccountCrudInterface $crud)
public function index(ARI $repository, AccountCrudInterface $crud, AccountTaskerInterface $tasker)
{
$types = config('firefly.accountTypesByIdentifier.asset');
@@ -139,12 +140,12 @@ class HomeController extends Controller
/** @var Carbon $start */
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$showTour = Preferences::get('tour', true)->data;
$accounts = $crud->getAccountsById($frontPage->data);
$end = session('end', Carbon::now()->endOfMonth());
$showTour = Preferences::get('tour', true)->data;
$accounts = $crud->getAccountsById($frontPage->data);
foreach ($accounts as $account) {
$set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end);
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
$set = $set->splice(0, 10);
if (count($set) > 0) {
@@ -153,7 +154,7 @@ class HomeController extends Controller
}
return view(
'index', compact('count', 'showTour', 'title','subTitle', 'mainTitleIcon', 'transactions')
'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions')
);
}

View File

@@ -19,9 +19,11 @@ use FireflyIII\Crud\Account\AccountCrudInterface;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collection\BalanceLine;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\Binder\AccountList;
@@ -185,22 +187,22 @@ class ReportController extends Controller
*/
private function expenseEntry(array $attributes): string
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$crud = app(AccountCrudInterface::class);
$account = $crud->find(intval($attributes['accountId']));
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$journals = $repository->journalsInPeriod($attributes['accounts'], $types, $attributes['startDate'], $attributes['endDate']);
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
$crud = app(AccountCrudInterface::class);
$account = $crud->find(intval($attributes['accountId']));
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$journals = $tasker->getJournalsInPeriod($attributes['accounts'], $types, $attributes['startDate'], $attributes['endDate']);
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
// filter for transfers and withdrawals TO the given $account
$journals = $journals->filter(
function (TransactionJournal $journal) use ($account) {
$destinations = TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray();
if (in_array($account->id, $destinations)) {
return true;
}
function (Transaction $transaction) use ($report) {
// get the destinations:
$destinations = TransactionJournal::destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray();
return false;
// do these intersect with the current list?
return !empty(array_intersect($report, $destinations));
}
);
@@ -219,27 +221,23 @@ class ReportController extends Controller
*/
private function incomeEntry(array $attributes): string
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
$account = $crud->find(intval($attributes['accountId']));
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$journals = $repository->journalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']);
$destinations = $attributes['accounts']->pluck('id')->toArray();
// filter for transfers and withdrawals FROM the given $account
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
/** @var AccountCrudInterface $crud */
$crud = app(AccountCrudInterface::class);
$account = $crud->find(intval($attributes['accountId']));
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$journals = $tasker->getJournalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']);
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
// filter the set so the destinations outside of $attributes['accounts'] are not included.
$journals = $journals->filter(
function (TransactionJournal $journal) use ($account, $destinations) {
$currentSources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
$currentDest = TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray();
if (
!empty(array_intersect([$account->id], $currentSources))
&& !empty(array_intersect($destinations, $currentDest))
) {
return true;
}
function (Transaction $transaction) use ($report) {
// get the destinations:
$destinations = TransactionJournal::destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray();
return false;
// do these intersect with the current list?
return !empty(array_intersect($report, $destinations));
}
);

View File

@@ -21,8 +21,9 @@ use FireflyIII\Helpers\Report\BudgetReportHelperInterface;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use Illuminate\Support\Collection;
@@ -121,7 +122,7 @@ class ReportController extends Controller
switch ($reportType) {
default:
throw new FireflyException('Unfortunately, reports of the type "' . e($reportType) . '" are not yet available. ');
throw new FireflyException('Unfortunately, reports of the type "' . e($reportType) . '" are not available at this time.');
case 'default':
// more than one year date difference means year report.
@@ -153,54 +154,36 @@ class ReportController extends Controller
private function auditReport(Carbon $start, Carbon $end, Collection $accounts)
{
/** @var ARI $repos */
$repos = app(ARI::class);
$repos = app(ARI::class);
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
$auditData = [];
$dayBefore = clone $start;
$dayBefore->subDay();
/** @var Account $account */
foreach ($accounts as $account) {
// balance the day before:
$id = $account->id;
$first = $repos->oldestJournalDate($account);
$last = $repos->newestJournalDate($account);
$exists = false;
$journals = new Collection;
$dayBeforeBalance = Steam::balance($account, $dayBefore);
/*
* Is there even activity on this account between the requested dates?
*/
if ($start->between($first, $last) || $end->between($first, $last)) {
$exists = true;
$journals = $repos->journalsInPeriod(new Collection([$account]), [], $start, $end);
$journals = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
$journals = $journals->reverse();
$startBalance = $dayBeforeBalance;
}
/*
* Reverse set, get balances.
*/
$journals = $journals->reverse();
$startBalance = $dayBeforeBalance;
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$journal->before = $startBalance;
$transactionAmount = $journal->source_amount;
// get currently relevant transaction:
$destinations = TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray();
if (in_array($account->id, $destinations)) {
$transactionAmount = TransactionJournal::amountPositive($journal);
}
$newBalance = bcadd($startBalance, $transactionAmount);
$journal->after = $newBalance;
$startBalance = $newBalance;
/** @var Transaction $journal */
foreach ($journals as $transaction) {
$transaction->before = $startBalance;
$transactionAmount = $transaction->transaction_amount;
$newBalance = bcadd($startBalance, $transactionAmount);
$transaction->after = $newBalance;
$startBalance = $newBalance;
}
/*
* Reverse set again.
*/
$auditData[$id]['journals'] = $journals->reverse();
$auditData[$id]['exists'] = $exists;
$auditData[$id]['exists'] = $journals->count() > 0;
$auditData[$id]['end'] = $end->formatLocalized(strval(trans('config.month_and_day')));
$auditData[$id]['endBalance'] = Steam::balance($account, $end);
$auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day')));

View File

@@ -245,6 +245,7 @@ class TransactionController extends Controller
$date = new Carbon($request->get('date'));
if (count($ids) > 0) {
$order = 0;
$ids = array_unique($ids);
foreach ($ids as $id) {
$journal = $repository->find(intval($id));
if ($journal && $journal->date->format('Y-m-d') == $date->format('Y-m-d')) {

View File

@@ -25,6 +25,7 @@ use FireflyIII\Support\Twig\Journal;
use FireflyIII\Support\Twig\PiggyBank;
use FireflyIII\Support\Twig\Rule;
use FireflyIII\Support\Twig\Translation;
use FireflyIII\Support\Twig\Transaction;
use FireflyIII\Validation\FireflyValidator;
use Illuminate\Support\ServiceProvider;
use Twig;
@@ -52,6 +53,7 @@ class FireflyServiceProvider extends ServiceProvider
Twig::addExtension(new Journal);
Twig::addExtension(new Budget);
Twig::addExtension(new Translation);
Twig::addExtension(new Transaction);
Twig::addExtension(new Rule);
}

View File

@@ -111,63 +111,6 @@ class AccountRepository implements AccountRepositoryInterface
return $transaction;
}
/**
* @param Collection $accounts
* @param array $types
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function journalsInPeriod(Collection $accounts, array $types, Carbon $start, Carbon $end): Collection
{
// first collect actual transaction journals (fairly easy)
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
if ($end >= $start) {
$query->before($end)->after($start);
}
if (count($types) > 0) {
$query->transactionTypes($types);
}
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->leftJoin(
'transactions as source', function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
}
);
$query->leftJoin(
'transactions as destination', function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
}
);
$query->where(
// source.account_id in accountIds XOR destination.account_id in accountIds
function (Builder $query) use ($accountIds) {
$query->where(
function (Builder $q1) use ($accountIds) {
$q1->whereIn('source.account_id', $accountIds)
->whereNotIn('destination.account_id', $accountIds);
}
)->orWhere(
function (Builder $q2) use ($accountIds) {
$q2->whereIn('destination.account_id', $accountIds)
->whereNotIn('source.account_id', $accountIds);
}
);
}
);
}
// that should do it:
$fields = TransactionJournal::queryFields();
$complete = $query->get($fields);
return $complete;
}
/**
*
* @param Account $account

View File

@@ -60,16 +60,6 @@ interface AccountRepositoryInterface
*/
public function getFirstTransaction(TransactionJournal $journal, Account $account): Transaction;
/**
* @param Collection $accounts
* @param array $types
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function journalsInPeriod(Collection $accounts, array $types, Carbon $start, Carbon $end): Collection;
/**
*
* @param Account $account

View File

@@ -190,6 +190,8 @@ class AccountTasker implements AccountTaskerInterface
}
/**
* It might be worth it to expand this query to include all account information required.
*
* @param Collection $accounts
* @param array $types
* @param Carbon $start
@@ -205,6 +207,8 @@ class AccountTasker implements AccountTaskerInterface
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->whereIn('transactions.account_id', $accountIds)
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
@@ -221,29 +225,35 @@ class AccountTasker implements AccountTaskerInterface
$set = $query->get(
[
'transaction_journals.id',
'transaction_journals.id as journal_id',
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.encrypted',
'transaction_journals.transaction_currency_id',
//'transaction_journals.transaction_currency_id',
'transaction_currencies.code as transaction_currency_code',
'transaction_currencies.symbol as transaction_currency_symbol',
'transaction_journals.transaction_type_id',
//'transaction_currencies.symbol as transaction_currency_symbol',
'transaction_types.type as transaction_type_type',
'transaction_journals.bill_id',
'bills.name as bill_name',
'transactions.id as transaction_id',
'transactions.id as id',
'transactions.amount as transaction_amount',
'transactions.description as transaction_description',
'transactions.account_id',
'transactions.identifier',
'transactions.transaction_journal_id',
'accounts.name as account_name',
'accounts.encrypted as account_encrypted',
'account_types.type as account_type',
]
);
// loop for decryption.
$set->each(
function (Transaction $transaction) {
$transaction->date = new Carbon($transaction->date);
$transaction->date = new Carbon($transaction->date);
$transaction->description = intval($transaction->encrypted) === 1 ? Crypt::decrypt($transaction->description) : $transaction->description;
$transaction->bill_name = !is_null($transaction->bill_name) ? Crypt::decrypt($transaction->bill_name) : '';
$transaction->bill_name = !is_null($transaction->bill_name) ? Crypt::decrypt($transaction->bill_name) : '';
}
);

View File

@@ -119,6 +119,41 @@ class Amount
return $this->formatAnything($currency, strval($transaction->amount), $coloured);
}
/**
* 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 currency code.
*
* @param string $code
* @param string $amount
* @param bool $coloured
*
* @return string
*/
public function formatWithCode(string $code, string $amount, bool $coloured = true): string
{
$locale = setlocale(LC_MONETARY, 0);
$float = round($amount, 2);
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
$result = $formatter->formatCurrency($float, $code);
if ($coloured === true) {
if ($amount > 0) {
return '<span class="text-success" title="' . e($float) . '">' . $result . '</span>';
} else {
if ($amount < 0) {
return '<span class="text-danger" title="' . e($float) . '">' . $result . '</span>';
}
}
return '<span style="color:#999" title="' . e($float) . '">' . $result . '</span>';
}
return $result;
}
/**
* @return Collection
*/

View File

@@ -20,6 +20,7 @@ use FireflyIII\Models\Budget as ModelBudget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\CacheProperties;
use Twig_Extension;
use Twig_SimpleFilter;
@@ -164,7 +165,9 @@ class Journal extends Twig_Extension
*/
public function getFilters(): array
{
$filters = [$this->typeIcon()];
$filters = [
$this->typeIcon(),
];
return $filters;
}
@@ -407,4 +410,5 @@ class Journal extends Twig_Extension
}, ['is_safe' => ['html']]
);
}
}

View File

@@ -0,0 +1,298 @@
<?php
/**
* Transaction.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\Support\Twig;
use Amount;
use Crypt;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction as TransactionModel;
use FireflyIII\Models\TransactionType;
use Twig_Extension;
use Twig_SimpleFilter;
use Twig_SimpleFunction;
/**
* Class Transaction
*
* @package FireflyIII\Support\Twig
*/
class Transaction extends Twig_Extension
{
/**
* @return Twig_SimpleFunction
*/
public function formatAmountWithCode(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'formatAmountWithCode', function (string $amount, string $code): string {
return Amount::formatWithCode($code, $amount, true);
}, ['is_safe' => ['html']]
);
}
/**
* @return array
*/
public function getFilters(): array
{
$filters = [
$this->typeIconTransaction(),
];
return $filters;
}
/**
* @return array
*/
public function getFunctions(): array
{
$functions = [
$this->formatAmountWithCode(),
$this->transactionSourceAccount(),
$this->transactionDestinationAccount(),
$this->optionalJournalAmount(),
$this->transactionBudgets(),
$this->transactionCategories(),
$this->splitJournalIndicator(),
];
return $functions;
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName(): string
{
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 {
$amount = strval(
TransactionModel
::where('transaction_journal_id', $journalId)
->whereNull('deleted_at')
->where('amount', '<', 0)
->sum('amount')
);
if ($type === TransactionType::DEPOSIT || $type === TransactionType::TRANSFER) {
$amount = bcmul($amount, '-1');
}
if (
bccomp($amount, $transactionAmount) !== 0
&& bccomp($amount, bcmul($transactionAmount, '-1')) !== 0
) {
// not equal?
return ' (' . Amount::formatWithCode($code, $amount, true) . ')';
}
return '';
}, ['is_safe' => ['html']]
);
}
public function splitJournalIndicator(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'splitJournalIndicator', function (int $journalId) {
$count = TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->count();
if ($count > 2) {
return '<i class="fa fa-fw fa-share-alt-square" aria-hidden="true"></i>';
}
return '';
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFunction
*/
public function transactionBudgets(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'transactionBudgets', function (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 '';
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFunction
*/
public function transactionCategories(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'transactionCategories', function (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 '';
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFunction
*/
public function transactionDestinationAccount(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'transactionDestinationAccount', function (TransactionModel $transaction): string {
$name = intval($transaction->account_encrypted) === 1 ? Crypt::decrypt($transaction->account_name) : $transaction->account_name;
$id = intval($transaction->account_id);
$type = $transaction->account_type;
// if the amount is positive, assume that the current account (the one in $transaction) is indeed the destination account.
if (bccomp($transaction->transaction_amount, '0') === -1) {
// if the amount is negative, find the opposing account and use that one:
$journalId = $transaction->journal_id;
/** @var TransactionModel $other */
$other = TransactionModel
::where('transaction_journal_id', $journalId)->where('transactions.id', '!=', $transaction->id)
->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where('identifier', $transaction->identifier)
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']);
$name = intval($other->encrypted) === 1 ? Crypt::decrypt($other->name) : $other->name;
$id = $other->account_id;
$type = $other->type;
}
if ($type === AccountType::CASH) {
return '<span class="text-success">(cash)</span>';
}
return '<a title="' . e($name) . '" href="' . route('accounts.show', $id) . '">' . e($name) . '</a>';
}, ['is_safe' => ['html']]
);
}
/**
* @return Twig_SimpleFunction
*/
public function transactionSourceAccount(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
'transactionSourceAccount', function (TransactionModel $transaction): string {
$name = intval($transaction->account_encrypted) === 1 ? Crypt::decrypt($transaction->account_name) : $transaction->account_name;
$id = intval($transaction->account_id);
$type = $transaction->account_type;
// if the amount is negative, assume that the current account (the one in $transaction) is indeed the source account.
if (bccomp($transaction->transaction_amount, '0') === 1) {
// if the amount is positive, find the opposing account and use that one:
$journalId = $transaction->journal_id;
/** @var TransactionModel $other */
$other = TransactionModel
::where('transaction_journal_id', $journalId)->where('transactions.id', '!=', $transaction->id)
->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where('identifier', $transaction->identifier)
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']);
$name = intval($other->encrypted) === 1 ? Crypt::decrypt($other->name) : $other->name;
$id = $other->account_id;
$type = $other->type;
}
if ($type === AccountType::CASH) {
return '<span class="text-success">(cash)</span>';
}
return '<a title="' . e($name) . '" href="' . route('accounts.show', $id) . '">' . e($name) . '</a>';
}, ['is_safe' => ['html']]
);
}
/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 5.
*
* @return Twig_SimpleFilter
*/
public function typeIconTransaction(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
'typeIconTransaction', function (TransactionModel $transaction): string {
switch ($transaction->transaction_type_type) {
case TransactionType::WITHDRAWAL:
$txt = '<i class="fa fa-long-arrow-left fa-fw" title="' . trans('firefly.withdrawal') . '"></i>';
break;
case TransactionType::DEPOSIT:
$txt = '<i class="fa fa-long-arrow-right fa-fw" title="' . trans('firefly.deposit') . '"></i>';
break;
case TransactionType::TRANSFER:
$txt = '<i class="fa fa-fw fa-exchange" title="' . trans('firefly.transfer') . '"></i>';
break;
case TransactionType::OPENING_BALANCE:
$txt = '<i class="fa-fw fa fa-ban" title="' . trans('firefly.openingBalance') . '"></i>';
break;
default:
$txt = '';
break;
}
return $txt;
}, ['is_safe' => ['html']]
);
}
}

View File

@@ -37,7 +37,7 @@
<h3 class="box-title">{{ 'transactions'|_ }}</h3>
</div>
<div class="box-body">
{% include 'list.journals' with {sorting:true, accountPerspective: account} %}
{% include 'list.journals-tasker' with {sorting:true} %}
</div>
</div>
</div>

View File

@@ -39,7 +39,7 @@
<h3 class="box-title">{{ 'transactions'|_ }}</h3>
</div>
<div class="box-body">
{% include 'list.journals' with {sorting:true} %}
{% include 'list.journals-tasker' with {sorting:true} %}
</div>
</div>
</div>

View File

@@ -79,7 +79,7 @@
</div>
<div class="box-body no-padding">
{% include 'list.journals-tiny' with {'transactions': data[0],'account': data[1]} %}
{% include 'list.journals-tiny-tasker' with {'transactions': data[0],'account': data[1]} %}
</div>
<div class="box-footer clearfix">
<a class="btn btn-sm btn-default btn-flat pull-right"

View File

@@ -0,0 +1,134 @@
{{ journals.render|raw }}
<table class="table table-hover table-compressed {% if sorting %}sortable-table{% endif %}">
<thead>
<tr class="ignore">
<th class="hidden-xs no_select_boxes" colspan="2">&nbsp;</th>
<th class="hidden-xs select_boxes" colspan="2" style="display: none;"><input name="select_all" class="select_all" type="checkbox"/></th>
<th>{{ trans('list.description') }}</th>
<th>{{ trans('list.amount') }}</th>
<th class="hidden-sm hidden-xs">{{ trans('list.date') }}</th>
<th class="hidden-xs">{{ trans('list.from') }}</th>
<th class="hidden-xs">{{ trans('list.to') }}</th>
<!-- Hide budgets? -->
{% if not hideBudgets %}
<th class="hidden-xs"><i class="fa fa-tasks fa-fw" title="{{ trans('list.budget') }}"></i></th>
{% endif %}
<!-- Hide categories? -->
{% if not hideCategories %}
<th class="hidden-xs"><i class="fa fa-bar-chart fa-fw" title="{{ trans('list.category') }}"></i></th>
{% endif %}
<!-- Hide bills? -->
{% if not hideBills %}
<th class="hidden-xs"><i class="fa fa-fw fa-rotate-right" title="{{ trans('list.bill') }}"></i></th>
{% endif %}
</tr>
</thead>
<!-- to be fixed:
SORTING
ATTACHMENT COUNT
SPLIT JOURNAL INDICATOR
-->
<tbody>
{% for transaction in journals %}
<tr class="drag" data-date="{{ transaction.date.format('Y-m-d') }}" data-id="{{ transaction.journal_id }}">
<td class="hidden-xs">
<div class="select_single" style="display:none;">
<input name="select_all_single[]" class="select_all_single" value="{{ transaction.journal_id }}" type="checkbox"/>
</div>
<div class="btn-group btn-group-xs edit_buttons">
{% if sorting %}
<a href="#" class="handle btn btn-default btn-xs"><i class="fa fa-fw fa-arrows-v"></i></a>
{% endif %}
<a href="{{ route('transactions.edit',transaction.journal_id) }}" class="btn btn-xs btn-default"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{{ route('transactions.delete',transaction.journal_id) }}" class="btn btn-xs btn-danger"><i class="fa fa-fw fa-trash-o"></i></a>
</div>
</td>
<td class="hidden-xs">
{{ transaction|typeIconTransaction }}
</td>
<td>
<a href="{{ route('transactions.show',transaction.journal_id) }}">
{% if transaction.transaction_description|length > 0 %}
{{ transaction.transaction_description }} ({{ transaction.description }})
{% else %}
{{ transaction.description }}
{% endif %}
</a>
{{ splitJournalIndicator(transaction.journal_id) }}
</td>
<td>
<!-- format amount of transaction -->
{{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }}
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount,
transaction.transaction_currency_code, transaction.transaction_type_type) }}
</td>
<td class="hidden-sm hidden-xs">
{{ transaction.date.formatLocalized(monthAndDayFormat) }}
</td>
<td class="hidden-xs">
{{ transactionSourceAccount(transaction) }}
</td>
<td class="hidden-xs">
{{ transactionDestinationAccount(transaction) }}
</td>
<!-- Do NOT hide the budget? -->
{% if not hideBudgets %}
<td class="hidden-xs">
{{ transactionBudgets(transaction) }}
</td>
{% endif %}
<!-- Do NOT hide the category? -->
{% if not hideCategories %}
<td class="hidden-xs">
{{ transactionCategories(transaction) }}
</td>
{% endif %}
<!-- Do NOT hide the bill? -->
{% if not hideBills %}
<td class="hidden-xs">
{% if transaction.bill_id %}
<a href="{{ route('bills.show',transaction.bill_id) }}">{{ transaction.bill_name }}</a>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="row mass_edit_all" style="display: none;">
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="mass_button_options btn-group btn-group" style="display:none;">
<a href="#" class="btn btn-default mass_edit"><i class="fa fa-fw fa-pencil"></i> <span>{{ 'edit_selected'|_ }}</span></a>
<a href="#" class="btn btn-danger mass_delete"><i class="fa fa-fw fa-trash"></i> <span>{{ 'delete_selected'|_ }}</span></a>
</div>
</div>
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="mass_buttons btn-group btn-group pull-right">
<a href="#" class="btn btn-default mass_select"><i class="fa fa-fw fa-check-square-o"></i> {{ 'select_transactions'|_ }}</a>
<a href="#" class="btn btn-default mass_stop_select" style="display:none;"><i class="fa faw-fw fa-square-o"
aria-hidden="true"></i> {{ 'stop_selection'|_ }}</a>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
{{ journals.render|raw }}
</div>
</div>
<script type="text/javascript">
var edit_selected_txt = "{{ 'edit_selected'|_ }}";
var delete_selected_txt = "{{ 'delete_selected'|_ }}";
</script>

View File

@@ -0,0 +1,25 @@
<div class="list-group">
{% for transaction in transactions %}
<a class="list-group-item" title="{{ transaction.date.formatLocalized(trans('config.month_and_day')) }}"
{% if transaction.transaction_type_type == 'Opening balance' %}
href="#"
{% else %}
href="{{ route('transactions.show',transaction.journal_id) }}"
{% endif %}
>
{{ transaction|typeIconTransaction }}
{% if transaction.transaction_description|length > 0 %}
{{ transaction.transaction_description }} ({{ transaction.description }})
{% else %}
{{ transaction.description }}
{% endif %}
<span class="pull-right small">
<!-- format amount of transaction -->
{{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }}
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount,
transaction.transaction_currency_code, transaction.transaction_type_type) }}
</span>
</a>
{% endfor %}
</div>

View File

@@ -0,0 +1,92 @@
<table class="table table-hover table-condensed">
<thead>
<tr>
<th>&nbsp;</th>
<th>{{ trans('list.description') }}</th>
<th>{{ trans('list.amount') }}</th>
<th class="hidden-sm hidden-xs">{{ trans('list.date') }}</th>
{% if not hideSource %}
<th class="hidden-xs">{{ trans('list.from') }}</th>
{% endif %}
{% if not hideDestination %}
<th class="hidden-xs">{{ trans('list.to') }}</th>
{% endif %}
<!-- Hide budgets? -->
{% if not hideBudget %}
<th class="hidden-xs"><i class="fa fa-tasks fa-fw" title="{{ trans('list.budget') }}"></i></th>
{% endif %}
<!-- Hide categories? -->
{% if not hideCategory %}
<th class="hidden-xs"><i class="fa fa-bar-chart fa-fw" title="{{ trans('list.category') }}"></i></th>
{% endif %}
</tr>
</thead>
<tbody>
<!--
Make sum:
{% set sum = 0 %}
-->
{% for transaction in journals %}
<!-- add to sum
{% set sum = (sum + transaction.transaction_amount) %}
-->
<tr class="drag" data-date="{{ transaction.date.format('Y-m-d') }}" data-id="{{ transaction.journal_id }}">
<td class="hidden-xs">
{{ transaction|typeIconTransaction }}
</td>
<td>
<a href="{{ route('transactions.show',transaction.journal_id) }}">
{% if transaction.transaction_description|length > 0 %}
{{ transaction.transaction_description }} ({{ transaction.description }})
{% else %}
{{ transaction.description }}
{% endif %}
</a>
</td>
<td>
<!-- format amount of transaction -->
{{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }}
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount,
transaction.transaction_currency_code, transaction.transaction_type_type) }}
</td>
<td class="hidden-sm hidden-xs">
{{ transaction.date.formatLocalized(monthAndDayFormat) }}
</td>
{% if not hideSource %}
<td class="hidden-xs">
{{ transactionSourceAccount(transaction) }}
</td>
{% endif %}
{% if not hideDestination %}
<td class="hidden-xs">
{{ transactionDestinationAccount(transaction) }}
</td>
{% endif %}
<!-- Do NOT hide the budget? -->
{% if not hideBudget %}
<td class="hidden-xs">
{{ transactionBudgets(transaction) }}
</td>
{% endif %}
<!-- Do NOT hide the category? -->
{% if not hideCategory %}
<td class="hidden-xs">
{{ transactionCategories(transaction) }}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="2" style="text-align: right;"><em>{{ 'sum'|_ }}:</em></td>
<td>{{ sum|formatAmount }}</td>
</tr>
</tfoot>
</table>
{{ journals.render|raw }}

View File

@@ -8,7 +8,7 @@
</div>
<div class="modal-body">
{% set hideDestination = true %}
{% include 'popup/list/journals.twig' %}
{% include 'popup/list/journals-tasker.twig' %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'close'|_ }}</button>

View File

@@ -10,7 +10,7 @@
{% set hideBudget = true %}
{% set hideSource = true %}
{% set accountPerspective = account %}
{% include 'popup/list/journals.twig' %}
{% include 'popup/list/journals-tasker.twig' %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'close'|_ }}</button>

View File

@@ -62,7 +62,7 @@
balance: auditData[account.id].endBalance|formatAmount
})|raw }}
</p>
{% include 'reports/partials/journals-audit.twig' with {'journals': auditData[account.id].journals,'account':account} %}
{% include 'reports/partials/journals-audit-tasker.twig' with {'journals': auditData[account.id].journals,'account':account} %}
<p style="padding:10px;">
{{ trans('firefly.audit_end_balance',
{

View File

@@ -0,0 +1,160 @@
{{ journals.render|raw }}
<table class="table table-hover table-compressed">
<thead>
<tr class="ignore">
<th class="hide-buttons">&nbsp;</th>
<th class="hide-icon">&nbsp;</th>
<th class="hide-description">{{ trans('list.description') }}</th>
<th class="hide-balance_before">{{ trans('list.balance_before') }}</th>
<th class="hide-amount">{{ trans('list.amount') }}</th>
<th class="hide-balance_after">{{ trans('list.balance_after') }}</th>
<th class="hide-date">{{ trans('list.date') }}</th>
<th class="hide-book_date">{{ trans('list.book_date') }}</th>
<th class="hide-process_date">{{ trans('list.process_date') }}</th>
<th class="hide-interest_date">{{ trans('list.interest_date') }}</th>
<!-- new optional fields (3x) -->
<th class="hide-interest_date">{{ trans('list.due_date') }}</th>
<th class="hide-payment_date">{{ trans('list.payment_date') }}</th>
<th class="hide-invoice_date">{{ trans('list.invoice_date') }}</th>
<th class="hide-from">{{ trans('list.from') }}</th>
<th class="hide-to">{{ trans('list.to') }}</th>
<th class="hide-budget"><i class="fa fa-tasks fa-fw" title="{{ trans('list.budget') }}"></i></th>
<th class="hide-category"><i class="fa fa-bar-chart fa-fw" title="{{ trans('list.category') }}"></i></th>
<th class="hide-bill">{{ trans('list.bill') }}</th>
<!-- more optional fields (2x) -->
<th class="hide-internal_reference">{{ trans('list.internal_reference') }}</th>
<th class="hide-notes">{{ trans('list.notes') }}</th>
<th class="hide-create_date">{{ trans('list.create_date') }}</th>
<th class="hide-update_date">{{ trans('list.update_date') }}</th>
</tr>
</thead>
<tbody>
{% for transaction in journals %}
<tr data-date="{{ transaction.date.format('Y-m-d') }}" data-id="{{ journal.id }}">
<td class="hide-buttons">
<div class="btn-group btn-group-xs">
<a href="{{ route('transactions.edit',transaction.journal_id) }}" class="btn btn-xs btn-default"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{{ route('transactions.delete',transaction.journal_id) }}" class="btn btn-xs btn-danger"><i class="fa fa-fw fa-trash-o"></i></a></div>
</td>
<td class="hide-icon">{{ transaction|typeIconTransaction }}</td>
<td class="hide-description">
<a href="{{ route('transactions.show',transaction.journal_id) }}">
{% if transaction.transaction_description|length > 0 %}
{{ transaction.transaction_description }} ({{ transaction.description }})
{% else %}
{{ transaction.description }}
{% endif %}
</a>
</td>
<td class="hide-balance_before">{{ transaction.before|formatAmount }}</td>
<td class="hide-amount">
<!-- format amount of transaction -->
{{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }}
<!-- and then amount of journal itself. -->
{{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount,
transaction.transaction_currency_code, transaction.transaction_type_type) }}
</td>
<td class="hide-balance_after">{{ transaction.after|formatAmount }}</td>
<td class="hide-date">{{ transaction.date.formatLocalized(monthAndDayFormat) }}</td>
<td class="hide-book_date">
{% if transaction.transactionJournal.hasMeta('book_date') %}
{{ transaction.transactionJournal.getMeta('book_date').formatLocalized(monthAndDayFormat) }}
{% endif %}
</td>
<td class="hide-process_date">
{% if transaction.transactionJournal.hasMeta('process_date') %}
{{ transaction.transactionJournal.getMeta('process_date').formatLocalized(monthAndDayFormat) }}
{% endif %}
</td>
<td class="hide-interest_date">
{% if transaction.transactionJournal.hasMeta('interest_date') %}
{{ transaction.transactionJournal.getMeta('interest_date').formatLocalized(monthAndDayFormat) }}
{% endif %}
</td>
<!-- new optional fields (3x) -->
<td class="hide-due_date">
{% if transaction.transactionJournal.hasMeta('due_date') %}
{{ transaction.transactionJournal.getMeta('due_date').formatLocalized(monthAndDayFormat) }}
{% endif %}
</td>
<td class="hide-payment_date">
{% if transaction.transactionJournal.hasMeta('payment_date') %}
{{ transaction.transactionJournal.getMeta('payment_date').formatLocalized(monthAndDayFormat) }}
{% endif %}
</td>
<td class="hide-invoice_date">
{% if transaction.transactionJournal.hasMeta('invoice_date') %}
{{ transaction.transactionJournal.getMeta('invoice_date').formatLocalized(monthAndDayFormat) }}
{% endif %}
</td>
<td class="hide-from">
{{ transactionSourceAccount(transaction) }}
</td>
<td class="hide-to">
{{ transactionDestinationAccount(transaction) }}
</td>
<td class="hide-budget">
{{ transactionBudgets(transaction) }}
</td>
<td class="hide-category">
{{ transactionCategories(transaction) }}
</td>
{% if transaction.bill_id %}
<td class="hide-bill">
<i class="fa fa-fw fa-rotate-right" title="{{ trans('list.bill') }}"></i>&nbsp
<a href="{{ route('bills.show',transaction.bill_id) }}">{{ transaction.bill_name }}</a>
</td>
{% else %}
<td class="hide-bill">&nbsp;</td>
{% endif %}
<!-- new optional fields (2x) -->
<td class="hide-internal_reference">
{% if transaction.transactionJournal.hasMeta('internal_reference') %}
{{ transaction.transactionJournal.getMeta('internal_reference') }}
{% endif %}
</td>
<td class="hide-notes">
{% if transaction.transactionJournal.hasMeta('notes') %}
{{ transaction.transactionJournal.getMeta('notes')|nl2br }}
{% endif %}
</td>
<td class="hide-create_date">
{{ transaction.transactionJournal.created_at.formatLocalized(dateTimeFormat) }}
</td>
<td class="hide-update_date">
{{ transaction.transactionJournal.updated_at.formatLocalized(dateTimeFormat) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ journals.render|raw }}