Merge branch 'release/3.5.4'

This commit is contained in:
James Cole 2015-12-18 08:13:28 +01:00
commit 53b3f7f821
95 changed files with 3777 additions and 1813 deletions

View File

@ -11,6 +11,8 @@ DB_PASSWORD=secret
CACHE_DRIVER=file CACHE_DRIVER=file
SESSION_DRIVER=file SESSION_DRIVER=file
DEFAULT_CURRENCY=EUR
EMAIL_SMTP= EMAIL_SMTP=
EMAIL_DRIVER=smtp EMAIL_DRIVER=smtp
EMAIL_USERNAME= EMAIL_USERNAME=
@ -21,4 +23,6 @@ RUNCLEANUP=true
SITE_OWNER=mail@example.com SITE_OWNER=mail@example.com
SENDGRID_USERNAME= SENDGRID_USERNAME=
SENDGRID_PASSWORD= SENDGRID_PASSWORD=
BLOCKED_DOMAINS=

2
.gitignore vendored
View File

@ -5,7 +5,7 @@ Thumbs.db
.idea/ .idea/
tests/_output/* tests/_output/*
_ide_helper.php _ide_helper.php
/build/logs/clover.xml /build/logs
index.html* index.html*
app/storage/firefly-export* app/storage/firefly-export*
.vagrant .vagrant

View File

@ -10,6 +10,9 @@ install:
- composer update - composer update
- php artisan env - php artisan env
- mv -v .env.testing .env - mv -v .env.testing .env
- touch storage/database/testing.db
- php artisan migrate --env=testing
- php artisan migrate --seed --env=testing
script: script:
- phpunit - phpunit

View File

@ -61,19 +61,19 @@ Everything is organised:
_Please note that everything in these screenshots is fictional and may not be realistic._ _Please note that everything in these screenshots is fictional and may not be realistic._
![Index](https://i.nder.be/c6hz06d3) ![Index](https://i.nder.be/hmp5mhw5)
![Accounts](https://i.nder.be/gzxxyz6n) ![Accounts](https://i.nder.be/hf5k02g9)
![Budgets](https://i.nder.be/hhu3krqk) ![Budgets](https://i.nder.be/gzv635mz)
![Reports 1](https://i.nder.be/cc3yspf6) ![Reports 1](https://i.nder.be/g0w698s3)
![Reports 2](https://i.nder.be/h6fp7xkb) ![Reports 2](https://i.nder.be/cr77nyxq)
![Bills](https://i.nder.be/c30zkcpv) ![Bills](https://i.nder.be/c7sugsz5)
![Piggy banks](https://i.nder.be/g20k0mdq) ![Piggy banks](https://i.nder.be/gy2nk0y4)
## Running and installing ## Running and installing
@ -82,7 +82,13 @@ If you're still interested please read [the installation guide](https://github.c
and the **[first use guide](https://github.com/JC5/firefly-iii/wiki/First-use)**. and the **[first use guide](https://github.com/JC5/firefly-iii/wiki/First-use)**.
If you want to try out Firefly III, you can do so on [this dedicated website](https://geld.nder.be/). If you want to try out Firefly III, you can do so on [this dedicated website](https://geld.nder.be/).
This site always runs the latest version of Firefly III. If you want to use it, please read the [privacy considerations](https://github.com/JC5/firefly-iii/wiki/Privacy-on-demo-site) for this demo-site. Accounts on the demo sites will stop working after one week. This site always runs the latest version of Firefly III. If you want to use it, please read the [privacy considerations](https://github.com/JC5/firefly-iii/wiki/Privacy-on-demo-site) for this demo-site. Accounts on the demo sites will stop working after one month. It's a trial.
## Security
You should always run Firefly III on a site with TLS enabled (https://). Please note that although some parts of the
database are encrypted (transaction descriptions, names, etc.) some parts are _not_ (amounts, dates, etc). If you need
more security, you must enable transparent database encryption or a comparable technology.
## Credits ## Credits

View File

@ -18,6 +18,13 @@ interface BudgetChartGenerator
*/ */
public function budget(Collection $entries); public function budget(Collection $entries);
/**
* @param Collection $entries
*
* @return array
*/
public function multiYear(Collection $entries);
/** /**
* @param Collection $entries * @param Collection $entries
* *

View File

@ -141,4 +141,37 @@ class ChartJsBudgetChartGenerator implements BudgetChartGenerator
return $data; return $data;
} }
/**
* @param Collection $entries
*
* @return array
*/
public function multiYear(Collection $entries)
{
// dataset:
$data = [
'count' => 0,
'labels' => [],
'datasets' => [],
];
// get labels from one of the budgets (assuming there's at least one):
$first = $entries->first();
foreach ($first['budgeted'] as $year => $noInterest) {
$data['labels'][] = strval($year);
}
// then, loop all entries and create datasets:
foreach ($entries as $entry) {
$name = $entry['name'];
$spent = $entry['spent'];
$budgeted = $entry['budgeted'];
$data['datasets'][] = ['label' => 'Spent on ' . $name, 'data' => array_values($spent)];
$data['datasets'][] = ['label' => 'Budgeted for ' . $name, 'data' => array_values($budgeted)];
}
$data['count'] = count($data['datasets']);
return $data;
}
} }

View File

@ -19,6 +19,13 @@ interface CategoryChartGenerator
*/ */
public function all(Collection $entries); public function all(Collection $entries);
/**
* @param Collection $entries
*
* @return array
*/
public function multiYear(Collection $entries);
/** /**
* @param Collection $entries * @param Collection $entries
* *

View File

@ -158,4 +158,41 @@ class ChartJsCategoryChartGenerator implements CategoryChartGenerator
return $data; return $data;
} }
/**
* @param Collection $entries
*
* @return array
*/
public function multiYear(Collection $entries)
{
// dataset:
$data = [
'count' => 0,
'labels' => [],
'datasets' => [],
];
// get labels from one of the categories (assuming there's at least one):
$first = $entries->first();
foreach ($first['spent'] as $year => $noInterest) {
$data['labels'][] = strval($year);
}
// then, loop all entries and create datasets:
foreach ($entries as $entry) {
$name = $entry['name'];
$spent = $entry['spent'];
$earned = $entry['earned'];
if (array_sum(array_values($spent)) != 0) {
$data['datasets'][] = ['label' => 'Spent in category ' . $name, 'data' => array_values($spent)];
}
if (array_sum(array_values($earned)) != 0) {
$data['datasets'][] = ['label' => 'Earned in category ' . $name, 'data' => array_values($earned)];
}
}
$data['count'] = count($data['datasets']);
return $data;
}
} }

View File

@ -49,6 +49,39 @@ class ChartJsReportChartGenerator implements ReportChartGenerator
return $data; return $data;
} }
/**
* Same as above but other translations.
*
* @param Collection $entries
*
* @return array
*/
public function multiYearInOut(Collection $entries)
{
$data = [
'count' => 2,
'labels' => [],
'datasets' => [
[
'label' => trans('firefly.income'),
'data' => []
],
[
'label' => trans('firefly.expenses'),
'data' => []
]
],
];
foreach ($entries as $entry) {
$data['labels'][] = $entry[0]->formatLocalized('%Y');
$data['datasets'][0]['data'][] = round($entry[1], 2);
$data['datasets'][1]['data'][] = round($entry[2], 2);
}
return $data;
}
/** /**
* @param string $income * @param string $income
* @param string $expense * @param string $expense
@ -80,4 +113,35 @@ class ChartJsReportChartGenerator implements ReportChartGenerator
return $data; return $data;
} }
/**
* @param string $income
* @param string $expense
* @param int $count
*
* @return array
*/
public function multiYearInOutSummarized($income, $expense, $count)
{
$data = [
'count' => 2,
'labels' => [trans('firefly.sum_of_years'), trans('firefly.average_of_years')],
'datasets' => [
[
'label' => trans('firefly.income'),
'data' => []
],
[
'label' => trans('firefly.expenses'),
'data' => []
]
],
];
$data['datasets'][0]['data'][] = round($income, 2);
$data['datasets'][1]['data'][] = round( $expense, 2);
$data['datasets'][0]['data'][] = round(($income / $count), 2);
$data['datasets'][1]['data'][] = round(( $expense / $count), 2);
return $data;
}
} }

View File

@ -12,12 +12,19 @@ use Illuminate\Support\Collection;
interface ReportChartGenerator interface ReportChartGenerator
{ {
/**
* @param Collection $entries
*
* @return array
*/
public function yearInOut(Collection $entries);
/** /**
* @param Collection $entries * @param Collection $entries
* *
* @return array * @return array
*/ */
public function yearInOut(Collection $entries); public function multiYearInOut(Collection $entries);
/** /**
* @param string $income * @param string $income
@ -28,4 +35,13 @@ interface ReportChartGenerator
*/ */
public function yearInOutSummarized($income, $expense, $count); public function yearInOutSummarized($income, $expense, $count);
/**
* @param string $income
* @param string $expense
* @param int $count
*
* @return array
*/
public function multiYearInOutSummarized($income, $expense, $count);
} }

View File

@ -150,24 +150,4 @@ class BalanceLine
{ {
$this->balanceEntries = $balanceEntries; $this->balanceEntries = $balanceEntries;
} }
/**
* If the BalanceEntries for a BalanceLine have a "left" value, the amount
* of money left in the entire BalanceLine is returned here:
*
* @return float
*/
public function sumOfLeft()
{
$sum = '0';
bcscale(2);
/** @var BalanceEntry $balanceEntry */
foreach ($this->getBalanceEntries() as $balanceEntry) {
$sum = bcadd($sum, $balanceEntry->getLeft());
}
return $sum;
}
} }

View File

@ -37,6 +37,7 @@ class Category
// spent is minus zero for an expense report: // spent is minus zero for an expense report:
if ($category->spent < 0) { if ($category->spent < 0) {
$this->categories->push($category); $this->categories->push($category);
$this->addTotal($category->spent);
} }
} }
@ -55,7 +56,7 @@ class Category
*/ */
public function getCategories() public function getCategories()
{ {
$set = $this->categories->sortByDesc( $set = $this->categories->sortBy(
function (CategoryModel $category) { function (CategoryModel $category) {
return $category->spent; return $category->spent;
} }

View File

@ -33,18 +33,24 @@ class Expense
*/ */
public function addOrCreateExpense(TransactionJournal $entry) public function addOrCreateExpense(TransactionJournal $entry)
{ {
bcscale(2);
$accountId = $entry->account_id; $accountId = $entry->account_id;
$amount = strval(round($entry->amount, 2));
if (bccomp('0', $amount) === -1) {
$amount = bcmul($amount, '-1');
}
if (!$this->expenses->has($accountId)) { if (!$this->expenses->has($accountId)) {
$newObject = new stdClass; $newObject = new stdClass;
$newObject->amount = strval(round($entry->amount_positive, 2)); $newObject->amount = $amount;
$newObject->name = $entry->name; $newObject->name = $entry->name;
$newObject->count = 1; $newObject->count = 1;
$newObject->id = $accountId; $newObject->id = $accountId;
$this->expenses->put($accountId, $newObject); $this->expenses->put($accountId, $newObject);
} else { } else {
bcscale(2);
$existing = $this->expenses->get($accountId); $existing = $this->expenses->get($accountId);
$existing->amount = bcadd($existing->amount, $entry->amount_positive); $existing->amount = bcadd($existing->amount, $amount);
$existing->count++; $existing->count++;
$this->expenses->put($accountId, $existing); $this->expenses->put($accountId, $existing);
} }
@ -55,8 +61,18 @@ class Expense
*/ */
public function addToTotal($add) public function addToTotal($add)
{ {
$add = strval(round($add, 2));
bcscale(2); bcscale(2);
$add = strval(round($add, 2));
if (bccomp('0', $add) === -1) {
$add = bcmul($add, '-1');
}
// if amount is positive, the original transaction
// was a transfer. But since this is an expense report,
// that amount must be negative.
$this->total = bcadd($this->total, $add); $this->total = bcadd($this->total, $add);
} }
@ -65,7 +81,7 @@ class Expense
*/ */
public function getExpenses() public function getExpenses()
{ {
$set = $this->expenses->sortByDesc( $set = $this->expenses->sortBy(
function (stdClass $object) { function (stdClass $object) {
return $object->amount; return $object->amount;
} }

View File

@ -296,7 +296,7 @@ class Importer
// some debug info: // some debug info:
$journalId = $journal->id; $journalId = $journal->id;
$type = $journal->transactionType->type; $type = $journal->getTransactionType();
/** @var Account $asset */ /** @var Account $asset */
$asset = $this->importData['asset-account-object']; $asset = $this->importData['asset-account-object'];
/** @var Account $opposing */ /** @var Account $opposing */
@ -314,13 +314,13 @@ class Importer
*/ */
protected function getTransactionType() protected function getTransactionType()
{ {
$transactionType = TransactionType::where('type', 'Deposit')->first(); $transactionType = TransactionType::where('type', TransactionType::DEPOSIT)->first();
if ($this->importData['amount'] < 0) { if ($this->importData['amount'] < 0) {
$transactionType = TransactionType::where('type', 'Withdrawal')->first(); $transactionType = TransactionType::where('type', TransactionType::WITHDRAWAL)->first();
} }
if (in_array($this->importData['opposing-account-object']->accountType->type, ['Asset account', 'Default account'])) { if (in_array($this->importData['opposing-account-object']->accountType->type, ['Asset account', 'Default account'])) {
$transactionType = TransactionType::where('type', 'Transfer')->first(); $transactionType = TransactionType::where('type', TransactionType::TRANSFER)->first();
} }
return $transactionType; return $transactionType;

View File

@ -24,7 +24,7 @@ class Currency implements PostProcessorInterface
// fix currency // fix currency
if (is_null($this->data['currency'])) { if (is_null($this->data['currency'])) {
$currencyPreference = Preferences::get('currencyPreference', 'EUR'); $currencyPreference = Preferences::get('currencyPreference', env('DEFAULT_CURRENCY', 'EUR'));
$this->data['currency'] = TransactionCurrency::whereCode($currencyPreference->data)->first(); $this->data['currency'] = TransactionCurrency::whereCode($currencyPreference->data)->first();
} }

View File

@ -19,6 +19,8 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\Bill; use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget as BudgetModel; use FireflyIII\Models\Budget as BudgetModel;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\LimitRepetition;
use Illuminate\Support\Collection;
use Steam;
/** /**
* Class ReportHelper * Class ReportHelper
@ -43,48 +45,113 @@ class ReportHelper implements ReportHelperInterface
} }
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return CategoryCollection
*/
public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts)
{
$object = new CategoryCollection;
/**
* GET CATEGORIES:
*/
/** @var \FireflyIII\Repositories\Category\CategoryRepositoryInterface $repository */
$repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface');
$set = $repository->getCategories();
foreach ($set as $category) {
$spent = $repository->balanceInPeriodForList($category, $start, $end, $accounts);
$category->spent = $spent;
$object->addCategory($category);
}
return $object;
}
/**
* @param Carbon $date
*
* @return array
*/
public function listOfMonths(Carbon $date)
{
$start = clone $date;
$end = Carbon::now();
$months = [];
while ($start <= $end) {
$year = $start->year;
if (!isset($months[$year])) {
$months[$year] = [
'start' => Carbon::createFromDate($year, 1, 1)->format('Y-m-d'),
'end' => Carbon::createFromDate($year, 12, 31)->format('Y-m-d'),
'months' => [],
];
}
$currentEnd = clone $start;
$currentEnd->endOfMonth();
$months[$year]['months'][] = [
'formatted' => $start->formatLocalized('%B %Y'),
'start' => $start->format('Y-m-d'),
'end' => $currentEnd->format('Y-m-d'),
'month' => $start->month,
'year' => $year,
];
$start->addMonth();
}
return $months;
}
/** /**
* This method generates a full report for the given period on all * This method generates a full report for the given period on all
* the users asset and cash accounts. * given accounts
* *
* @param Carbon $date * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param $shared * @param Collection $accounts
* *
* @return AccountCollection * @return AccountCollection
*/ */
public function getAccountReport(Carbon $date, Carbon $end, $shared) public function getAccountReport(Carbon $start, Carbon $end, Collection $accounts)
{ {
$startAmount = '0';
$endAmount = '0';
$accounts = $this->query->getAllAccounts($date, $end, $shared); $diff = '0';
$start = '0';
$end = '0';
$diff = '0';
bcscale(2); bcscale(2);
// remove cash account, if any: $accounts->each(
$accounts = $accounts->filter( function (Account $account) use ($start, $end) {
function (Account $account) { /**
if ($account->accountType->type != 'Cash account') { * The balance for today always incorporates transactions
return $account; * made on today. So to get todays "start" balance, we sub one
} * day.
*/
$yesterday = clone $start;
$yesterday->subDay();
return null; /** @noinspection PhpParamsInspection */
$account->startBalance = Steam::balance($account, $yesterday);
$account->endBalance = Steam::balance($account, $end);
} }
); );
// summarize: // summarize:
foreach ($accounts as $account) { foreach ($accounts as $account) {
$start = bcadd($start, $account->startBalance); $startAmount = bcadd($startAmount, $account->startBalance);
$end = bcadd($end, $account->endBalance); $endAmount = bcadd($endAmount, $account->endBalance);
$diff = bcadd($diff, bcsub($account->endBalance, $account->startBalance)); $diff = bcadd($diff, bcsub($account->endBalance, $account->startBalance));
} }
$object = new AccountCollection; $object = new AccountCollection;
$object->setStart($start); $object->setStart($startAmount);
$object->setEnd($end); $object->setEnd($endAmount);
$object->setDifference($diff); $object->setDifference($diff);
$object->setAccounts($accounts); $object->setAccounts($accounts);
@ -92,37 +159,136 @@ class ReportHelper implements ReportHelperInterface
} }
/** /**
* Get a full report on the users incomes during the period for the given accounts.
* *
* The balance report contains a Balance object which in turn contains: * @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* A BalanceHeader object which contains all relevant user asset accounts for the report. * @return Income
*/
public function getIncomeReport($start, $end, Collection $accounts)
{
$object = new Income;
$set = $this->query->incomeInPeriod($start, $end, $accounts);
foreach ($set as $entry) {
$object->addToTotal($entry->amount_positive);
$object->addOrCreateIncome($entry);
}
return $object;
}
/**
* Get a full report on the users expenses during the period for a list of accounts.
* *
* A number of BalanceLine objects, which hold: * @param Carbon $start
* - A budget * @param Carbon $end
* - A number of BalanceEntry objects. * @param Collection $accounts
* *
* The BalanceEntry object holds: * @return Expense
* - The same budget (again) */
* - A user asset account as mentioned in the BalanceHeader public function getExpenseReport($start, $end, Collection $accounts)
* - The amount of money spent on the budget by the user asset account {
$object = new Expense;
$set = $this->query->expenseInPeriod($start, $end, $accounts);
foreach ($set as $entry) {
$object->addToTotal($entry->amount); // can be positive, if it's a transfer
$object->addOrCreateExpense($entry);
}
return $object;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @param Carbon $start * @return BudgetCollection
* @param Carbon $end */
* @param boolean $shared public function getBudgetReport(Carbon $start, Carbon $end, Collection $accounts)
{
$object = new BudgetCollection;
/** @var \FireflyIII\Repositories\Budget\BudgetRepositoryInterface $repository */
$repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
$set = $repository->getBudgets();
bcscale(2);
foreach ($set as $budget) {
$repetitions = $repository->getBudgetLimitRepetitions($budget, $start, $end);
// no repetition(s) for this budget:
if ($repetitions->count() == 0) {
$spent = $repository->balanceInPeriodForList($budget, $start, $end, $accounts);
$budgetLine = new BudgetLine;
$budgetLine->setBudget($budget);
$budgetLine->setOverspent($spent);
$object->addOverspent($spent);
$object->addBudgetLine($budgetLine);
continue;
}
// one or more repetitions for budget:
/** @var LimitRepetition $repetition */
foreach ($repetitions as $repetition) {
$budgetLine = new BudgetLine;
$budgetLine->setBudget($budget);
$budgetLine->setRepetition($repetition);
$expenses = $repository->balanceInPeriodForList($budget, $start, $end, $accounts);
// 200 en -100 is 100, vergeleken met 0 === 1
// 200 en -200 is 0, vergeleken met 0 === 0
// 200 en -300 is -100, vergeleken met 0 === -1
$left = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : 0;
$spent = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0';
$overspent = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $repetition->amount);
$budgetLine->setLeft($left);
$budgetLine->setSpent($spent);
$budgetLine->setOverspent($overspent);
$budgetLine->setBudgeted($repetition->amount);
$object->addBudgeted($repetition->amount);
$object->addSpent($spent);
$object->addLeft($left);
$object->addOverspent($overspent);
$object->addBudgetLine($budgetLine);
}
}
// stuff outside of budgets:
$noBudget = $repository->getWithoutBudgetSum($start, $end);
$budgetLine = new BudgetLine;
$budgetLine->setOverspent($noBudget);
$budgetLine->setSpent($noBudget);
$object->addOverspent($noBudget);
$object->addBudgetLine($budgetLine);
return $object;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @return Balance * @return Balance
*/ */
public function getBalanceReport(Carbon $start, Carbon $end, $shared) public function getBalanceReport(Carbon $start, Carbon $end, Collection $accounts)
{ {
$repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
$tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface');
$balance = new Balance; $balance = new Balance;
// build a balance header: // build a balance header:
$header = new BalanceHeader; $header = new BalanceHeader;
$budgets = $repository->getBudgets();
$accounts = $this->query->getAllAccounts($start, $end, $shared);
$budgets = $repository->getBudgets();
foreach ($accounts as $account) { foreach ($accounts as $account) {
$header->addAccount($account); $header->addAccount($account);
} }
@ -134,6 +300,7 @@ class ReportHelper implements ReportHelperInterface
// get budget amount for current period: // get budget amount for current period:
$rep = $repository->getCurrentRepetition($budget, $start, $end); $rep = $repository->getCurrentRepetition($budget, $start, $end);
// could be null?
$line->setRepetition($rep); $line->setRepetition($rep);
// loop accounts: // loop accounts:
@ -142,7 +309,7 @@ class ReportHelper implements ReportHelperInterface
$balanceEntry->setAccount($account); $balanceEntry->setAccount($account);
// get spent: // get spent:
$spent = $this->query->spentInBudgetCorrected($account, $budget, $start, $end); // I think shared is irrelevant. $spent = $this->query->spentInBudget($account, $budget, $start, $end); // I think shared is irrelevant.
$balanceEntry->setSpent($spent); $balanceEntry->setSpent($spent);
$line->addBalanceEntry($balanceEntry); $line->addBalanceEntry($balanceEntry);
@ -153,6 +320,7 @@ class ReportHelper implements ReportHelperInterface
// then a new line for without budget. // then a new line for without budget.
// and one for the tags: // and one for the tags:
// and one for "left unbalanced".
$empty = new BalanceLine; $empty = new BalanceLine;
$tags = new BalanceLine; $tags = new BalanceLine;
$diffLine = new BalanceLine; $diffLine = new BalanceLine;
@ -164,7 +332,7 @@ class ReportHelper implements ReportHelperInterface
$spent = $this->query->spentNoBudget($account, $start, $end); $spent = $this->query->spentNoBudget($account, $start, $end);
$left = $tagRepository->coveredByBalancingActs($account, $start, $end); $left = $tagRepository->coveredByBalancingActs($account, $start, $end);
bcscale(2); bcscale(2);
$diff = bcsub($spent, $left); $diff = bcadd($spent, $left);
// budget // budget
$budgetEntry = new BalanceEntry; $budgetEntry = new BalanceEntry;
@ -199,16 +367,19 @@ class ReportHelper implements ReportHelperInterface
* This method generates a full report for the given period on all * This method generates a full report for the given period on all
* the users bills and their payments. * the users bills and their payments.
* *
* @param Carbon $start * Excludes bills which have not had a payment on the mentioned accounts.
* @param Carbon $end *
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @return BillCollection * @return BillCollection
*/ */
public function getBillReport(Carbon $start, Carbon $end) public function getBillReport(Carbon $start, Carbon $end, Collection $accounts)
{ {
/** @var \FireflyIII\Repositories\Bill\BillRepositoryInterface $repository */ /** @var \FireflyIII\Repositories\Bill\BillRepositoryInterface $repository */
$repository = app('FireflyIII\Repositories\Bill\BillRepositoryInterface'); $repository = app('FireflyIII\Repositories\Bill\BillRepositoryInterface');
$bills = $repository->getBills(); $bills = $repository->getBillsForAccounts($accounts);
$collection = new BillCollection; $collection = new BillCollection;
/** @var Bill $bill */ /** @var Bill $bill */
@ -238,168 +409,5 @@ class ReportHelper implements ReportHelperInterface
} }
return $collection; return $collection;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param boolean $shared
*
* @return BudgetCollection
*/
public function getBudgetReport(Carbon $start, Carbon $end, $shared)
{
$object = new BudgetCollection;
/** @var \FireflyIII\Repositories\Budget\BudgetRepositoryInterface $repository */
$repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
$set = $repository->getBudgets();
bcscale(2);
foreach ($set as $budget) {
$repetitions = $repository->getBudgetLimitRepetitions($budget, $start, $end);
// no repetition(s) for this budget:
if ($repetitions->count() == 0) {
$spent = $repository->balanceInPeriod($budget, $start, $end, $shared);
$budgetLine = new BudgetLine;
$budgetLine->setBudget($budget);
$budgetLine->setOverspent($spent);
$object->addOverspent($spent);
$object->addBudgetLine($budgetLine);
continue;
}
// one or more repetitions for budget:
/** @var LimitRepetition $repetition */
foreach ($repetitions as $repetition) {
$budgetLine = new BudgetLine;
$budgetLine->setBudget($budget);
$budgetLine->setRepetition($repetition);
$expenses = $repository->balanceInPeriod($budget, $repetition->startdate, $repetition->enddate, $shared);
$expenses = $expenses * -1;
$left = $expenses < $repetition->amount ? bcsub($repetition->amount, $expenses) : 0;
$spent = $expenses > $repetition->amount ? 0 : $expenses;
$overspent = $expenses > $repetition->amount ? bcsub($expenses, $repetition->amount) : 0;
$budgetLine->setLeft($left);
$budgetLine->setSpent($spent);
$budgetLine->setOverspent($overspent);
$budgetLine->setBudgeted($repetition->amount);
$object->addBudgeted($repetition->amount);
$object->addSpent($spent);
$object->addLeft($left);
$object->addOverspent($overspent);
$object->addBudgetLine($budgetLine);
}
}
// stuff outside of budgets:
$noBudget = $repository->getWithoutBudgetSum($start, $end);
$budgetLine = new BudgetLine;
$budgetLine->setOverspent($noBudget);
$object->addOverspent($noBudget);
$object->addBudgetLine($budgetLine);
return $object;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param boolean $shared
*
* @return CategoryCollection
*/
public function getCategoryReport(Carbon $start, Carbon $end, $shared)
{
$object = new CategoryCollection;
/**
* GET CATEGORIES:
*/
/** @var \FireflyIII\Repositories\Category\CategoryRepositoryInterface $repository */
$repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface');
$set = $repository->getCategories();
foreach ($set as $category) {
$spent = $repository->balanceInPeriod($category, $start, $end, $shared);
$category->spent = $spent;
$object->addCategory($category);
$object->addTotal($spent);
}
return $object;
}
/**
* Get a full report on the users expenses during the period.
*
* @param Carbon $start
* @param Carbon $end
* @param boolean $shared
*
* @return Expense
*/
public function getExpenseReport($start, $end, $shared)
{
$object = new Expense;
$set = $this->query->expenseInPeriodCorrected($start, $end, $shared);
foreach ($set as $entry) {
$object->addToTotal($entry->amount_positive);
$object->addOrCreateExpense($entry);
}
return $object;
}
/**
* Get a full report on the users incomes during the period.
*
* @param Carbon $start
* @param Carbon $end
* @param boolean $shared
*
* @return Income
*/
public function getIncomeReport($start, $end, $shared)
{
$object = new Income;
$set = $this->query->incomeInPeriodCorrected($start, $end, $shared);
foreach ($set as $entry) {
$object->addToTotal($entry->amount_positive);
$object->addOrCreateIncome($entry);
}
return $object;
}
/**
* @param Carbon $date
*
* @return array
*/
public function listOfMonths(Carbon $date)
{
$start = clone $date;
$end = Carbon::now();
$months = [];
while ($start <= $end) {
$year = $start->year;
$months[$year][] = [
'formatted' => $start->formatLocalized('%B %Y'),
'month' => $start->month,
'year' => $year,
];
$start->addMonth();
}
return $months;
} }
} }

View File

@ -10,6 +10,7 @@ use FireflyIII\Helpers\Collection\Budget as BudgetCollection;
use FireflyIII\Helpers\Collection\Category as CategoryCollection; use FireflyIII\Helpers\Collection\Category as CategoryCollection;
use FireflyIII\Helpers\Collection\Expense; use FireflyIII\Helpers\Collection\Expense;
use FireflyIII\Helpers\Collection\Income; use FireflyIII\Helpers\Collection\Income;
use Illuminate\Support\Collection;
/** /**
* Interface ReportHelperInterface * Interface ReportHelperInterface
@ -21,75 +22,78 @@ interface ReportHelperInterface
/** /**
* This method generates a full report for the given period on all * This method generates a full report for the given period on all
* the users asset and cash accounts. * given accounts
* *
* @param Carbon $date * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param boolean $shared * @param Collection $accounts
* *
* @return AccountCollection * @return AccountCollection
*/ */
public function getAccountReport(Carbon $date, Carbon $end, $shared); public function getAccountReport(Carbon $start, Carbon $end, Collection $accounts);
/** /**
* This method generates a full report for the given period on all * This method generates a full report for the given period on all
* the users bills and their payments. * the users bills and their payments.
* *
* @param Carbon $start * Excludes bills which have not had a payment on the mentioned accounts.
* @param Carbon $end *
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @return BillCollection * @return BillCollection
*/ */
public function getBillReport(Carbon $start, Carbon $end); public function getBillReport(Carbon $start, Carbon $end, Collection $accounts);
/** /**
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param boolean $shared * @param Collection $accounts
* *
* @return Balance * @return Balance
*/ */
public function getBalanceReport(Carbon $start, Carbon $end, $shared); public function getBalanceReport(Carbon $start, Carbon $end, Collection $accounts);
/** /**
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param boolean $shared * @param Collection $accounts
* *
* @return BudgetCollection * @return BudgetCollection
*/ */
public function getBudgetReport(Carbon $start, Carbon $end, $shared); public function getBudgetReport(Carbon $start, Carbon $end, Collection $accounts);
/** /**
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param boolean $shared * @param Collection $accounts
* *
* @return CategoryCollection * @return CategoryCollection
*/ */
public function getCategoryReport(Carbon $start, Carbon $end, $shared); public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts);
/** /**
* Get a full report on the users expenses during the period. * Get a full report on the users expenses during the period for a list of accounts.
* *
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param boolean $shared * @param Collection $accounts
* *
* @return Expense * @return Expense
*/ */
public function getExpenseReport($start, $end, $shared); public function getExpenseReport($start, $end, Collection $accounts);
/** /**
* Get a full report on the users incomes during the period. * Get a full report on the users incomes during the period for the given accounts.
* *
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param boolean $shared * @param Collection $accounts
* *
* @return Income * @return Income
*/ */
public function getIncomeReport($start, $end, $shared); public function getIncomeReport($start, $end, Collection $accounts);
/** /**
* @param Carbon $date * @param Carbon $date

View File

@ -8,10 +8,10 @@ use Crypt;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Steam;
/** /**
* Class ReportQuery * Class ReportQuery
@ -20,178 +20,6 @@ use Steam;
*/ */
class ReportQuery implements ReportQueryInterface class ReportQuery implements ReportQueryInterface
{ {
/**
* See ReportQueryInterface::incomeInPeriodCorrected.
*
* This method's length is caused mainly by the query build stuff. Therefor:
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*
* @param Carbon $start
* @param Carbon $end
* @param bool $includeShared
*
* @return Collection
*
*/
public function expenseInPeriodCorrected(Carbon $start, Carbon $end, $includeShared = false)
{
$query = $this->queryJournalsWithTransactions($start, $end);
if ($includeShared === false) {
$query->where(
function (Builder $query) {
$query->where(
function (Builder $q) { // only get withdrawals not from a shared account
$q->where('transaction_types.type', 'Withdrawal');
$q->where('acm_from.data', '!=', '"sharedAsset"');
}
);
$query->orWhere(
function (Builder $q) { // and transfers from a shared account.
$q->where('transaction_types.type', 'Transfer');
$q->where('acm_to.data', '=', '"sharedAsset"');
$q->where('acm_from.data', '!=', '"sharedAsset"');
}
);
}
);
} else {
$query->where('transaction_types.type', 'Withdrawal'); // any withdrawal is fine.
}
$query->orderBy('transaction_journals.date');
$data = $query->get( // get everything
['transaction_journals.*', 'transaction_types.type', 'ac_to.name as name', 'ac_to.id as account_id', 'ac_to.encrypted as account_encrypted']
);
$data->each(
function (TransactionJournal $journal) {
if (intval($journal->account_encrypted) == 1) {
$journal->name = Crypt::decrypt($journal->name);
}
}
);
return $data;
}
/**
* Get a users accounts combined with various meta-data related to the start and end date.
*
* @param Carbon $start
* @param Carbon $end
* @param bool $includeShared
*
* @return Collection
*/
public function getAllAccounts(Carbon $start, Carbon $end, $includeShared = false)
{
$query = Auth::user()->accounts()->orderBy('accounts.name', 'ASC')
->accountTypeIn(['Default account', 'Asset account', 'Cash account']);
if ($includeShared === false) {
$query->leftJoin(
'account_meta', function (JoinClause $join) {
$join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole');
}
)
->where(
function (Builder $query) {
$query->where('account_meta.data', '!=', '"sharedAsset"');
$query->orWhereNull('account_meta.data');
}
);
}
$set = $query->get(['accounts.*']);
$set->each(
function (Account $account) use ($start, $end) {
/**
* The balance for today always incorporates transactions
* made on today. So to get todays "start" balance, we sub one
* day.
*/
$yesterday = clone $start;
$yesterday->subDay();
/** @noinspection PhpParamsInspection */
$account->startBalance = Steam::balance($account, $yesterday);
$account->endBalance = Steam::balance($account, $end);
}
);
return $set;
}
/**
* This method works the same way as ReportQueryInterface::incomeInPeriod does, but instead of returning results
* will simply list the transaction journals only. This should allow any follow up counting to be accurate with
* regards to tags.
*
* This method returns all "income" journals in a certain period, which are both transfers from a shared account
* and "ordinary" deposits. The query used is almost equal to ReportQueryInterface::journalsByRevenueAccount but it does
* not group and returns different fields.
*
* @param Carbon $start
* @param Carbon $end
* @param bool $includeShared
*
* @return Collection
*/
public function incomeInPeriodCorrected(Carbon $start, Carbon $end, $includeShared = false)
{
$query = $this->queryJournalsWithTransactions($start, $end);
if ($includeShared === false) {
// only get deposits not to a shared account
// and transfers to a shared account.
$query->where(
function (Builder $query) {
$query->where(
function (Builder $q) {
$q->where('transaction_types.type', 'Deposit');
$q->where('acm_to.data', '!=', '"sharedAsset"');
}
);
$query->orWhere(
function (Builder $q) {
$q->where('transaction_types.type', 'Transfer');
$q->where('acm_from.data', '=', '"sharedAsset"');
$q->where('acm_to.data','!=','"sharedAsset"');
}
);
}
);
} else {
// any deposit is fine.
$query->where('transaction_types.type', 'Deposit');
}
$query->orderBy('transaction_journals.date');
// get everything
$data = $query->get(
['transaction_journals.*', 'transaction_types.type', 'ac_from.name as name', 'ac_from.id as account_id', 'ac_from.encrypted as account_encrypted']
);
$data->each(
function (TransactionJournal $journal) {
if (intval($journal->account_encrypted) == 1) {
$journal->name = Crypt::decrypt($journal->name);
}
}
);
$data = $data->filter(
function (TransactionJournal $journal) {
if ($journal->amount != 0) {
return $journal;
}
return null;
}
);
return $data;
}
/** /**
* Covers tags * Covers tags
* *
@ -202,22 +30,18 @@ class ReportQuery implements ReportQueryInterface
* *
* @return float * @return float
*/ */
public function spentInBudgetCorrected(Account $account, Budget $budget, Carbon $start, Carbon $end) public function spentInBudget(Account $account, Budget $budget, Carbon $start, Carbon $end)
{ {
bcscale(2); return Auth::user()->transactionjournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
return bcmul( ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
Auth::user()->transactionjournals() ->transactionTypes([TransactionType::WITHDRAWAL])
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.account_id', $account->id)
->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->before($end)
->transactionTypes(['Withdrawal']) ->after($start)
->where('transactions.account_id', $account->id) ->where('budget_transaction_journal.budget_id', $budget->id)
->before($end) ->get(['transaction_journals.*'])->sum('amount');
->after($start)
->where('budget_transaction_journal.budget_id', $budget->id)
->get(['transaction_journals.*'])->sum('amount'), -1
);
} }
/** /**
@ -233,7 +57,7 @@ class ReportQuery implements ReportQueryInterface
Auth::user()->transactionjournals() Auth::user()->transactionjournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->transactionTypes(['Withdrawal']) ->transactionTypes([TransactionType::WITHDRAWAL])
->where('transactions.account_id', $account->id) ->where('transactions.account_id', $account->id)
->before($end) ->before($end)
->after($start) ->after($start)
@ -276,4 +100,137 @@ class ReportQuery implements ReportQueryInterface
return $query; return $query;
} }
/**
* This method works the same way as ReportQueryInterface::incomeInPeriod does, but instead of returning results
* will simply list the transaction journals only. This should allow any follow up counting to be accurate with
* regards to tags. It will only get the incomes to the specified accounts.
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return Collection
*/
public function incomeInPeriod(Carbon $start, Carbon $end, Collection $accounts)
{
$query = $this->queryJournalsWithTransactions($start, $end);
$ids = [];
/** @var Account $account */
foreach ($accounts as $account) {
$ids[] = $account->id;
}
// OR is a deposit
// OR any transfer TO the accounts in $accounts, not FROM any of the accounts in $accounts.
$query->where(
function (Builder $query) use ($ids) {
$query->where(
function (Builder $q) {
$q->where('transaction_types.type', TransactionType::DEPOSIT);
}
);
$query->orWhere(
function (Builder $q) use ($ids) {
$q->where('transaction_types.type', TransactionType::TRANSFER);
$q->whereNotIn('ac_from.id',$ids);
$q->whereIn('ac_to.id', $ids);
}
);
}
);
// only include selected accounts.
$query->whereIn('ac_to.id', $ids);
$query->orderBy('transaction_journals.date');
// get everything
$data = $query->get(
['transaction_journals.*', 'transaction_types.type', 'ac_from.name as name', 'ac_from.id as account_id', 'ac_from.encrypted as account_encrypted']
);
$data->each(
function (TransactionJournal $journal) {
if (intval($journal->account_encrypted) == 1) {
$journal->name = Crypt::decrypt($journal->name);
}
}
);
$data = $data->filter(
function (TransactionJournal $journal) {
if ($journal->amount != 0) {
return $journal;
}
return null;
}
);
return $data;
}
/**
* See ReportQueryInterface::incomeInPeriod
*
* This method returns all "expense" journals in a certain period, which are both transfers to a shared account
* and "ordinary" withdrawals. The query used is almost equal to ReportQueryInterface::journalsByRevenueAccount but it does
* not group and returns different fields.
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return Collection
*
*/
public function expenseInPeriod(Carbon $start, Carbon $end, Collection $accounts)
{
$ids = [];
/** @var Account $account */
foreach ($accounts as $account) {
$ids[] = $account->id;
}
$query = $this->queryJournalsWithTransactions($start, $end);
// withdrawals from any account are an expense.
// transfers away, from an account in the list, to an account not in the list, are an expense.
$query->where(
function (Builder $query) use ($ids) {
$query->where(
function (Builder $q) {
$q->where('transaction_types.type', TransactionType::WITHDRAWAL);
}
);
$query->orWhere(
function (Builder $q) use ($ids) {
$q->where('transaction_types.type', TransactionType::TRANSFER);
$q->whereIn('ac_from.id', $ids);
$q->whereNotIn('ac_to.id', $ids);
}
);
}
);
// expense goes from the selected accounts:
$query->whereIn('ac_from.id', $ids);
$query->orderBy('transaction_journals.date');
$data = $query->get( // get everything
['transaction_journals.*', 'transaction_types.type', 'ac_to.name as name', 'ac_to.id as account_id', 'ac_to.encrypted as account_encrypted']
);
$data->each(
function (TransactionJournal $journal) {
if (intval($journal->account_encrypted) == 1) {
$journal->name = Crypt::decrypt($journal->name);
}
}
);
return $data;
}
} }

View File

@ -16,7 +16,7 @@ interface ReportQueryInterface
{ {
/** /**
* See ReportQueryInterface::incomeInPeriodCorrected * See ReportQueryInterface::incomeInPeriod
* *
* This method returns all "expense" journals in a certain period, which are both transfers to a shared account * This method returns all "expense" journals in a certain period, which are both transfers to a shared account
* and "ordinary" withdrawals. The query used is almost equal to ReportQueryInterface::journalsByRevenueAccount but it does * and "ordinary" withdrawals. The query used is almost equal to ReportQueryInterface::journalsByRevenueAccount but it does
@ -24,36 +24,25 @@ interface ReportQueryInterface
* *
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param bool $includeShared * @param Collection $accounts
* *
* @return Collection * @return Collection
* *
*/ */
public function expenseInPeriodCorrected(Carbon $start, Carbon $end, $includeShared = false); public function expenseInPeriod(Carbon $start, Carbon $end, Collection $accounts);
/**
* Get a users accounts combined with various meta-data related to the start and end date.
*
* @param Carbon $start
* @param Carbon $end
* @param bool $includeShared
*
* @return Collection
*/
public function getAllAccounts(Carbon $start, Carbon $end, $includeShared = false);
/** /**
* This method works the same way as ReportQueryInterface::incomeInPeriod does, but instead of returning results * This method works the same way as ReportQueryInterface::incomeInPeriod does, but instead of returning results
* will simply list the transaction journals only. This should allow any follow up counting to be accurate with * will simply list the transaction journals only. This should allow any follow up counting to be accurate with
* regards to tags. * regards to tags. It will only get the incomes to the specified accounts.
* *
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param bool $includeShared * @param Collection $accounts
* *
* @return Collection * @return Collection
*/ */
public function incomeInPeriodCorrected(Carbon $start, Carbon $end, $includeShared = false); public function incomeInPeriod(Carbon $start, Carbon $end, Collection $accounts);
/** /**
* Covers tags as well. * Covers tags as well.
@ -65,7 +54,7 @@ interface ReportQueryInterface
* *
* @return float * @return float
*/ */
public function spentInBudgetCorrected(Account $account, Budget $budget, Carbon $start, Carbon $end); public function spentInBudget(Account $account, Budget $budget, Carbon $start, Carbon $end);
/** /**
* @param Account $account * @param Account $account

View File

@ -1,6 +1,7 @@
<?php namespace FireflyIII\Http\Controllers\Auth; <?php namespace FireflyIII\Http\Controllers\Auth;
use Auth; use Auth;
use Config;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Role; use FireflyIII\Models\Role;
use FireflyIII\User; use FireflyIII\User;
@ -8,12 +9,12 @@ use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Foundation\Auth\ThrottlesLogins; use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Mail\Message; use Illuminate\Mail\Message;
use Log;
use Mail; use Mail;
use Request as Rq; use Request as Rq;
use Session; use Session;
use Twig; use Twig;
use Validator; use Validator;
use Log;
/** /**
* Class AuthController * Class AuthController
@ -32,9 +33,8 @@ class AuthController extends Controller
public function getLogout() public function getLogout()
{ {
Auth::logout(); Auth::logout();
Log::debug('Logout and redirect to root.');
return redirect('/login'); return redirect('/auth/login');
} }
/** /**
@ -88,8 +88,8 @@ class AuthController extends Controller
$foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first(); $foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first();
if (!is_null($foundUser)) { if (!is_null($foundUser)) {
// if it exists, show message: // if it exists, show message:
$code = $foundUser->blocked_code; $code = $foundUser->blocked_code;
if(strlen($code) == 0) { if (strlen($code) == 0) {
$code = 'general_blocked'; $code = 'general_blocked';
} }
$message = trans('firefly.' . $code . '_error', ['email' => $credentials['email']]); $message = trans('firefly.' . $code . '_error', ['email' => $credentials['email']]);
@ -160,21 +160,35 @@ class AuthController extends Controller
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
$data = $request->all(); $data = $request->all();
$data['password'] = bcrypt($data['password']); $data['password'] = bcrypt($data['password']);
// is user email domain blocked?
if ($this->isBlockedDomain($data['email'])) {
$validator->getMessageBag()->add('email', trans('validation.invalid_domain'));
$this->throwValidationException(
$request, $validator
);
}
Auth::login($this->create($data)); Auth::login($this->create($data));
// get the email address // get the email address
if (Auth::user() instanceof User) { if (Auth::user() instanceof User) {
$email = Auth::user()->email; $email = Auth::user()->email;
$address = route('index'); $address = route('index');
$ipAddress = $request->ip();
// send email. // send email.
Mail::send( try {
['emails.registered-html', 'emails.registered'], ['address' => $address], function (Message $message) use ($email) { Mail::send(
$message->to($email, $email)->subject('Welcome to Firefly III! '); ['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
$message->to($email, $email)->subject('Welcome to Firefly III! ');
}
);
} catch (\Swift_TransportException $e) {
Log::error($e->getMessage());
} }
);
// set flash message // set flash message
Session::flash('success', 'You have registered successfully!'); Session::flash('success', 'You have registered successfully!');
@ -197,6 +211,32 @@ class AuthController extends Controller
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
/**
* @return array
*/
protected function getBlockedDomains() {
$set = Config::get('mail.blocked_domains');
$domains = [];
foreach($set as $entry) {
$domain = trim($entry);
if(strlen($domain) > 0) {
$domains[] = $domain;
}
}
return $domains;
}
protected function isBlockedDomain($email)
{
$parts = explode('@', $email);
$blocked = $this->getBlockedDomains();
if (isset($parts[1]) && in_array($parts[1], $blocked)) {
return true;
}
return false;
}
/** /**
* Get a validator for an incoming registration request. * Get a validator for an incoming registration request.
* *

View File

@ -2,7 +2,12 @@
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
use FireflyIII\User;
use Illuminate\Http\Request;
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* Class PasswordController * Class PasswordController
* *
@ -41,4 +46,35 @@ class PasswordController extends Controller
$this->middleware('guest'); $this->middleware('guest');
} }
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
$user = User::whereEmail($request->get('email'))->first();
if (!is_null($user) && intval($user->blocked) === 1) {
$response = 'passwords.blocked';
} else {
$response = Password::sendResetLink($request->only('email'), function (Message $message) {
$message->subject($this->getEmailSubject());
});
}
switch ($response) {
case Password::RESET_LINK_SENT:
return redirect()->back()->with('status', trans($response));
case Password::INVALID_USER:
case 'passwords.blocked':
return redirect()->back()->withErrors(['email' => trans($response)]);
}
}
} }

View File

@ -6,6 +6,7 @@ use Carbon\Carbon;
use FireflyIII\Http\Requests\BudgetFormRequest; use FireflyIII\Http\Requests\BudgetFormRequest;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\LimitRepetition;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Input; use Input;
use Navigation; use Navigation;
@ -135,7 +136,7 @@ class BudgetController extends Controller
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function index(BudgetRepositoryInterface $repository) public function index(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository)
{ {
$budgets = $repository->getActiveBudgets(); $budgets = $repository->getActiveBudgets();
$inactive = $repository->getInactiveBudgets(); $inactive = $repository->getInactiveBudgets();
@ -147,6 +148,8 @@ class BudgetController extends Controller
$key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd');
$budgetIncomeTotal = Preferences::get($key, 1000)->data; $budgetIncomeTotal = Preferences::get($key, 1000)->data;
$period = Navigation::periodShow($start, $range); $period = Navigation::periodShow($start, $range);
$accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
bcscale(2); bcscale(2);
/** /**
* Do some cleanup: * Do some cleanup:
@ -156,7 +159,7 @@ class BudgetController extends Controller
// loop the budgets: // loop the budgets:
/** @var Budget $budget */ /** @var Budget $budget */
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
$budget->spent = $repository->balanceInPeriod($budget, $start, $end); $budget->spent = $repository->balanceInPeriodForList($budget, $start, $end, $accounts);
$budget->currentRep = $repository->getCurrentRepetition($budget, $start, $end); $budget->currentRep = $repository->getCurrentRepetition($budget, $start, $end);
if ($budget->currentRep) { if ($budget->currentRep) {
$budgeted = bcadd($budgeted, $budget->currentRep->amount); $budgeted = bcadd($budgeted, $budget->currentRep->amount);
@ -170,7 +173,7 @@ class BudgetController extends Controller
$defaultCurrency = Amount::getDefaultCurrency(); $defaultCurrency = Amount::getDefaultCurrency();
return view( return view(
'budgets.index', compact('budgetMaximum','period', 'range', 'budgetIncomeTotal', 'defaultCurrency', 'inactive', 'budgets', 'spent', 'budgeted') 'budgets.index', compact('budgetMaximum', 'period', 'range', 'budgetIncomeTotal', 'defaultCurrency', 'inactive', 'budgets', 'spent', 'budgeted')
); );
} }
@ -279,7 +282,7 @@ class BudgetController extends Controller
{ {
$budgetData = [ $budgetData = [
'name' => $request->input('name'), 'name' => $request->input('name'),
'active' => intval($request->input('active')) == 1 'active' => intval($request->input('active')) == 1,
]; ];
$repository->update($budget, $budgetData); $repository->update($budget, $budgetData);

View File

@ -79,6 +79,38 @@ class AccountController extends Controller
return Response::json($data); return Response::json($data);
} }
/**
* Shows the balances for a given set of dates and accounts.
*
* TODO fix parameters.
*
* @param AccountRepositoryInterface $repository
*
* @param $url
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function report($report_type, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('all');
$cache->addProperty('accounts');
$cache->addProperty('default');
$cache->addProperty($accounts);
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// make chart:
$data = $this->generator->all($accounts, $start, $end);
$cache->store($data);
return Response::json($data);
}
/** /**
* Shows the balances for all the user's expense accounts. * Shows the balances for all the user's expense accounts.
* *

View File

@ -6,6 +6,7 @@ use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\LimitRepetition;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -35,13 +36,86 @@ class BudgetController extends Controller
$this->generator = app('FireflyIII\Generator\Chart\Budget\BudgetChartGenerator'); $this->generator = app('FireflyIII\Generator\Chart\Budget\BudgetChartGenerator');
} }
/**
* @param BudgetRepositoryInterface $repository
* @param $report_type
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Collection $budgets
*/
public function multiYear(BudgetRepositoryInterface $repository, $report_type, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets)
{
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($report_type);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty($budgets);
$cache->addProperty('multiYearBudget');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/**
* budget
* year:
* spent: x
* budgeted: x
* year
* spent: x
* budgeted: x
*/
$entries = new Collection;
// go by budget, not by year.
foreach ($budgets as $budget) {
$entry = ['name' => '', 'spent' => [], 'budgeted' => []];
$currentStart = clone $start;
while ($currentStart < $end) {
// fix the date:
$currentEnd = clone $currentStart;
$currentEnd->endOfYear();
// get data:
if (is_null($budget->id)) {
$name = trans('firefly.noBudget');
$sum = $repository->getWithoutBudgetSum($currentStart, $currentEnd);
$budgeted = 0;
} else {
$name = $budget->name;
$sum = $repository->balanceInPeriodForList($budget, $currentStart, $currentEnd, $accounts);
$budgeted = $repository->getBudgetLimitRepetitions($budget, $currentStart, $currentEnd)->sum('amount');
}
// save to array:
$year = $currentStart->year;
$entry['name'] = $name;
$entry['spent'][$year] = ($sum * -1);
$entry['budgeted'][$year] = $budgeted;
// jump to next year.
$currentStart = clone $currentEnd;
$currentStart->addDay();
}
$entries->push($entry);
}
// generate chart with data:
$data = $this->generator->multiYear($entries);
return Response::json($data);
}
/** /**
* @param BudgetRepositoryInterface $repository * @param BudgetRepositoryInterface $repository
* @param Budget $budget * @param Budget $budget
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function budget(BudgetRepositoryInterface $repository, Budget $budget) public function budget(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget)
{ {
// dates and times // dates and times
@ -50,7 +124,9 @@ class BudgetController extends Controller
$last = Session::get('end', new Carbon); $last = Session::get('end', new Carbon);
$final = clone $last; $final = clone $last;
$final->addYears(2); $final->addYears(2);
$last = Navigation::endOfX($last, $range, $final); $last = Navigation::endOfX($last, $range, $final);
$accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties(); $cache = new CacheProperties();
@ -68,7 +144,7 @@ class BudgetController extends Controller
$end->subDay(); $end->subDay();
$chartDate = clone $end; $chartDate = clone $end;
$chartDate->startOfMonth(); $chartDate->startOfMonth();
$spent = $repository->balanceInPeriod($budget, $first, $end) * -1; $spent = $repository->balanceInPeriodForList($budget, $first, $end, $accounts) * -1;
$entries->push([$chartDate, $spent]); $entries->push([$chartDate, $spent]);
$first = Navigation::addPeriod($first, $range, 0); $first = Navigation::addPeriod($first, $range, 0);
} }
@ -113,7 +189,7 @@ class BudgetController extends Controller
/* /*
* Sum of expenses on this day: * Sum of expenses on this day:
*/ */
$sum = $repository->expensesOnDayCorrected($budget, $start); $sum = $repository->expensesOnDay($budget, $start);
$amount = bcadd($amount, $sum); $amount = bcadd($amount, $sum);
$entries->push([clone $start, $amount]); $entries->push([clone $start, $amount]);
$start->addDay(); $start->addDay();
@ -133,12 +209,13 @@ class BudgetController extends Controller
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function frontpage(BudgetRepositoryInterface $repository) public function frontpage(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository)
{ {
$budgets = $repository->getBudgets(); $budgets = $repository->getBudgets();
$start = Session::get('start', Carbon::now()->startOfMonth()); $start = Session::get('start', Carbon::now()->startOfMonth());
$end = Session::get('end', Carbon::now()->endOfMonth()); $end = Session::get('end', Carbon::now()->endOfMonth());
$allEntries = new Collection; $allEntries = new Collection;
$accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties(); $cache = new CacheProperties();
@ -156,13 +233,13 @@ class BudgetController extends Controller
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
$repetitions = $repository->getBudgetLimitRepetitions($budget, $start, $end); $repetitions = $repository->getBudgetLimitRepetitions($budget, $start, $end);
if ($repetitions->count() == 0) { if ($repetitions->count() == 0) {
$expenses = $repository->balanceInPeriod($budget, $start, $end, true) * -1; $expenses = $repository->balanceInPeriodForList($budget, $start, $end, $accounts) * -1;
$allEntries->push([$budget->name, 0, 0, $expenses, 0, 0]); $allEntries->push([$budget->name, 0, 0, $expenses, 0, 0]);
continue; continue;
} }
/** @var LimitRepetition $repetition */ /** @var LimitRepetition $repetition */
foreach ($repetitions as $repetition) { foreach ($repetitions as $repetition) {
$expenses = $repository->balanceInPeriod($budget, $repetition->startdate, $repetition->enddate, true) * -1; $expenses = $repository->balanceInPeriodForList($budget, $repetition->startdate, $repetition->enddate, $accounts) * -1;
// $left can be less than zero. // $left can be less than zero.
// $overspent can be more than zero ( = overspending) // $overspent can be more than zero ( = overspending)
@ -197,11 +274,8 @@ class BudgetController extends Controller
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function year(BudgetRepositoryInterface $repository, $year, $shared = false) public function year(BudgetRepositoryInterface $repository, $report_type, Carbon $start, Carbon $end, Collection $accounts)
{ {
$start = new Carbon($year . '-01-01');
$end = new Carbon($year . '-12-31');
$shared = $shared == 'shared' ? true : false;
$allBudgets = $repository->getBudgets(); $allBudgets = $repository->getBudgets();
$budgets = new Collection; $budgets = new Collection;
@ -218,7 +292,7 @@ class BudgetController extends Controller
// filter empty budgets: // filter empty budgets:
foreach ($allBudgets as $budget) { foreach ($allBudgets as $budget) {
$spent = $repository->balanceInPeriod($budget, $start, $end, $shared); $spent = $repository->balanceInPeriodForList($budget, $start, $end, $accounts);
if ($spent != 0) { if ($spent != 0) {
$budgets->push($budget); $budgets->push($budget);
} }
@ -234,7 +308,7 @@ class BudgetController extends Controller
// each budget, fill the row: // each budget, fill the row:
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
$spent = $repository->balanceInPeriod($budget, $start, $month, $shared); $spent = $repository->balanceInPeriodForList($budget, $start, $month, $accounts);
$row[] = $spent * -1; $row[] = $spent * -1;
} }
$entries->push($row); $entries->push($row);

View File

@ -107,7 +107,7 @@ class CategoryController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore return Response::json($cache->get()); // @codeCoverageIgnore
} }
$array = $repository->getCategoriesAndExpensesCorrected($start, $end); $array = $repository->getCategoriesAndExpenses($start, $end);
// sort by callback: // sort by callback:
uasort( uasort(
$array, $array,
@ -121,6 +121,84 @@ class CategoryController extends Controller
); );
$set = new Collection($array); $set = new Collection($array);
$data = $this->generator->frontpage($set); $data = $this->generator->frontpage($set);
$cache->store($data);
return Response::json($data);
}
/**
* @param CategoryRepositoryInterface $repository
* @param $report_type
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Collection $categories
*/
public function multiYear(CategoryRepositoryInterface $repository, $report_type, Carbon $start, Carbon $end, Collection $accounts, Collection $categories)
{
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($report_type);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty($categories);
$cache->addProperty('multiYearCategory');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/**
* category
* year:
* spent: x
* earned: x
* year
* spent: x
* earned: x
*/
$entries = new Collection;
// go by budget, not by year.
/** @var Category $category */
foreach ($categories as $category) {
$entry = ['name' => '', 'spent' => [], 'earned' => []];
$currentStart = clone $start;
while ($currentStart < $end) {
// fix the date:
$currentEnd = clone $currentStart;
$currentEnd->endOfYear();
// get data:
if (is_null($category->id)) {
$name = trans('firefly.noCategory');
$spent = $repository->spentNoCategoryForAccounts($accounts, $currentStart, $currentEnd);
$earned = $repository->earnedNoCategoryForAccounts($accounts, $currentStart, $currentEnd);
} else {
$name = $category->name;
$spent = $repository->spentInPeriodForAccounts($category, $accounts, $currentStart, $currentEnd);
$earned = $repository->earnedInPeriodForAccounts($category, $accounts, $currentStart, $currentEnd);
}
// save to array:
$year = $currentStart->year;
$entry['name'] = $name;
$entry['spent'][$year] = ($spent * -1);
$entry['earned'][$year] = $earned;
// jump to next year.
$currentStart = clone $currentEnd;
$currentStart->addDay();
}
$entries->push($entry);
}
// generate chart with data:
$data = $this->generator->multiYear($entries);
$cache->store($data);
return Response::json($data); return Response::json($data);
@ -151,8 +229,8 @@ class CategoryController extends Controller
while ($start <= $end) { while ($start <= $end) {
$spent = $repository->spentOnDaySumCorrected($category, $start); $spent = $repository->spentOnDaySum($category, $start);
$earned = $repository->earnedOnDaySumCorrected($category, $start); $earned = $repository->earnedOnDaySum($category, $start);
$date = Navigation::periodShow($start, '1D'); $date = Navigation::periodShow($start, '1D');
$entries->push([clone $start, $date, $spent, $earned]); $entries->push([clone $start, $date, $spent, $earned]);
$start->addDay(); $start->addDay();
@ -194,8 +272,8 @@ class CategoryController extends Controller
while ($start <= $end) { while ($start <= $end) {
$spent = $repository->spentOnDaySumCorrected($category, $start); $spent = $repository->spentOnDaySum($category, $start);
$earned = $repository->earnedOnDaySumCorrected($category, $start); $earned = $repository->earnedOnDaySum($category, $start);
$theDate = Navigation::periodShow($start, '1D'); $theDate = Navigation::periodShow($start, '1D');
$entries->push([clone $start, $theDate, $spent, $earned]); $entries->push([clone $start, $theDate, $spent, $earned]);
$start->addDay(); $start->addDay();
@ -213,15 +291,15 @@ class CategoryController extends Controller
* This chart will only show expenses. * This chart will only show expenses.
* *
* @param CategoryRepositoryInterface $repository * @param CategoryRepositoryInterface $repository
* @param $year * @param $report_type
* @param bool $shared * @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Illuminate\Http\JsonResponse
*/ */
public function spentInYear(CategoryRepositoryInterface $repository, $year, $shared = false) public function spentInYear(CategoryRepositoryInterface $repository, $report_type, Carbon $start, Carbon $end, Collection $accounts)
{ {
$start = new Carbon($year . '-01-01');
$end = new Carbon($year . '-12-31');
$cache = new CacheProperties; // chart properties for cache: $cache = new CacheProperties; // chart properties for cache:
$cache->addProperty($start); $cache->addProperty($start);
@ -232,12 +310,11 @@ class CategoryController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore return Response::json($cache->get()); // @codeCoverageIgnore
} }
$shared = $shared == 'shared' ? true : false;
$allCategories = $repository->getCategories(); $allCategories = $repository->getCategories();
$entries = new Collection; $entries = new Collection;
$categories = $allCategories->filter( $categories = $allCategories->filter(
function (Category $category) use ($repository, $start, $end, $shared) { function (Category $category) use ($repository, $start, $end, $accounts) {
$spent = $repository->balanceInPeriod($category, $start, $end, $shared); $spent = $repository->balanceInPeriodForList($category, $start, $end, $accounts);
if ($spent < 0) { if ($spent < 0) {
return $category; return $category;
} }
@ -252,7 +329,7 @@ class CategoryController extends Controller
$row = [clone $start]; // make a row: $row = [clone $start]; // make a row:
foreach ($categories as $category) { // each budget, fill the row foreach ($categories as $category) { // each budget, fill the row
$spent = $repository->balanceInPeriod($category, $start, $month, $shared); $spent = $repository->balanceInPeriodForList($category, $start, $month, $accounts);
if ($spent < 0) { if ($spent < 0) {
$row[] = $spent * -1; $row[] = $spent * -1;
} else { } else {
@ -272,16 +349,15 @@ class CategoryController extends Controller
* This chart will only show income. * This chart will only show income.
* *
* @param CategoryRepositoryInterface $repository * @param CategoryRepositoryInterface $repository
* @param $year * @param $report_type
* @param bool $shared * @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Illuminate\Http\JsonResponse
*/ */
public function earnedInYear(CategoryRepositoryInterface $repository, $year, $shared = false) public function earnedInYear(CategoryRepositoryInterface $repository, $report_type, Carbon $start, Carbon $end, Collection $accounts)
{ {
$start = new Carbon($year . '-01-01');
$end = new Carbon($year . '-12-31');
$cache = new CacheProperties; // chart properties for cache: $cache = new CacheProperties; // chart properties for cache:
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
@ -291,12 +367,11 @@ class CategoryController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore return Response::json($cache->get()); // @codeCoverageIgnore
} }
$shared = $shared == 'shared' ? true : false;
$allCategories = $repository->getCategories(); $allCategories = $repository->getCategories();
$allEntries = new Collection; $allEntries = new Collection;
$categories = $allCategories->filter( $categories = $allCategories->filter(
function (Category $category) use ($repository, $start, $end, $shared) { function (Category $category) use ($repository, $start, $end, $accounts) {
$spent = $repository->balanceInPeriod($category, $start, $end, $shared); $spent = $repository->balanceInPeriodForList($category, $start, $end, $accounts);
if ($spent > 0) { if ($spent > 0) {
return $category; return $category;
} }
@ -311,7 +386,7 @@ class CategoryController extends Controller
$row = [clone $start]; // make a row: $row = [clone $start]; // make a row:
foreach ($categories as $category) { // each budget, fill the row foreach ($categories as $category) { // each budget, fill the row
$spent = $repository->balanceInPeriod($category, $start, $month, $shared); $spent = $repository->balanceInPeriodForList($category, $start, $month, $accounts);
if ($spent > 0) { if ($spent > 0) {
$row[] = $spent; $row[] = $spent;
} else { } else {

View File

@ -9,7 +9,6 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Response; use Response;
use Log;
/** /**
* Class ReportController * Class ReportController
@ -37,41 +36,66 @@ class ReportController extends Controller
* Summarizes all income and expenses, per month, for a given year. * Summarizes all income and expenses, per month, for a given year.
* *
* @param ReportQueryInterface $query * @param ReportQueryInterface $query
* @param $year * @param $report_type
* @param bool $shared * @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Illuminate\Http\JsonResponse
*/ */
public function yearInOut(ReportQueryInterface $query, $year, $shared = false) public function yearInOut(ReportQueryInterface $query, $report_type, Carbon $start, Carbon $end, Collection $accounts)
{ {
// get start and end of year
$start = new Carbon($year . '-01-01');
$end = new Carbon($year . '-12-31');
$shared = $shared == 'shared' ? true : false;
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty('yearInOut'); $cache->addProperty('yearInOut');
$cache->addProperty($year); $cache->addProperty($start);
$cache->addProperty($shared); $cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore return Response::json($cache->get()); // @codeCoverageIgnore
} }
$entries = new Collection;
while ($start < $end) {
$month = clone $start;
$month->endOfMonth();
// total income and total expenses:
$incomeSum = $query->incomeInPeriodCorrected($start, $month, $shared)->sum('amount_positive');
$expenseSum = $query->expenseInPeriodCorrected($start, $month, $shared)->sum('amount_positive');
$entries->push([clone $start, $incomeSum, $expenseSum]); // per year?
$start->addMonth(); if ($start->diffInMonths($end) > 12) {
$entries = new Collection;
while ($start < $end) {
$startOfYear = clone $start;
$startOfYear->startOfYear();
$endOfYear = clone $startOfYear;
$endOfYear->endOfYear();
// total income and total expenses:
$incomeSum = $query->incomeInPeriod($startOfYear, $endOfYear, $accounts)->sum('amount_positive');
$expenseSum = $query->expenseInPeriod($startOfYear, $endOfYear, $accounts)->sum('amount_positive');
$entries->push([clone $start, $incomeSum, $expenseSum]);
$start->addYear();
}
$data = $this->generator->multiYearInOut($entries);
$cache->store($data);
} else {
// per month:
$entries = new Collection;
while ($start < $end) {
$month = clone $start;
$month->endOfMonth();
// total income and total expenses:
$incomeSum = $query->incomeInPeriod($start, $month, $accounts)->sum('amount_positive');
$expenseSum = $query->expenseInPeriod($start, $month, $accounts)->sum('amount_positive');
$entries->push([clone $start, $incomeSum, $expenseSum]);
$start->addMonth();
}
$data = $this->generator->yearInOut($entries);
$cache->store($data);
} }
$data = $this->generator->yearInOut($entries);
$cache->store($data);
return Response::json($data); return Response::json($data);
@ -81,54 +105,71 @@ class ReportController extends Controller
* Summarizes all income and expenses for a given year. Gives a total and an average. * Summarizes all income and expenses for a given year. Gives a total and an average.
* *
* @param ReportQueryInterface $query * @param ReportQueryInterface $query
* @param $year * @param $report_type
* @param bool $shared * @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Illuminate\Http\JsonResponse
*/ */
public function yearInOutSummarized(ReportQueryInterface $query, $year, $shared = false) public function yearInOutSummarized(ReportQueryInterface $query, $report_type, Carbon $start, Carbon $end, Collection $accounts)
{ {
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty('yearInOutSummarized'); $cache->addProperty('yearInOutSummarized');
$cache->addProperty($year); $cache->addProperty($start);
$cache->addProperty($shared); $cache->addProperty($end);
$cache->addProperty($accounts);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore return Response::json($cache->get()); // @codeCoverageIgnore
} }
$start = new Carbon($year . '-01-01');
$end = new Carbon($year . '-12-31');
$shared = $shared == 'shared' ? true : false;
$income = '0'; $income = '0';
$expense = '0'; $expense = '0';
$count = 0; $count = 0;
bcscale(2); bcscale(2);
while ($start < $end) { if ($start->diffInMonths($end) > 12) {
$month = clone $start; // per year
$month->endOfMonth(); while ($start < $end) {
// total income and total expenses: $startOfYear = clone $start;
$currentIncome = $query->incomeInPeriodCorrected($start, $month, $shared)->sum('amount_positive'); $startOfYear->startOfYear();
$currentExpense = $query->expenseInPeriodCorrected($start, $month, $shared)->sum('amount_positive'); $endOfYear = clone $startOfYear;
$endOfYear->endOfYear();
Log::debug('Date ['.$month->format('M Y').']: income = ['.$income.' + '.$currentIncome.'], out = ['.$expense.' + '.$currentExpense.']');
// total income and total expenses:
$income = bcadd($income, $currentIncome); $currentIncome = $query->incomeInPeriod($startOfYear, $endOfYear, $accounts)->sum('amount_positive');
$expense = bcadd($expense, $currentExpense); $currentExpense = $query->expenseInPeriod($startOfYear, $endOfYear, $accounts)->sum('amount_positive');
$income = bcadd($income, $currentIncome);
$expense = bcadd($expense, $currentExpense);
$count++;
$start->addYear();
$count++; }
$start->addMonth();
$data = $this->generator->multiYearInOutSummarized($income, $expense, $count);
$cache->store($data);
} else {
// per month!
while ($start < $end) {
$month = clone $start;
$month->endOfMonth();
// total income and total expenses:
$currentIncome = $query->incomeInPeriod($start, $month, $accounts)->sum('amount_positive');
$currentExpense = $query->expenseInPeriod($start, $month, $accounts)->sum('amount_positive');
$income = bcadd($income, $currentIncome);
$expense = bcadd($expense, $currentExpense);
$count++;
$start->addMonth();
}
$data = $this->generator->yearInOutSummarized($income, $expense, $count);
$cache->store($data);
} }
$data = $this->generator->yearInOutSummarized($income, $expense, $count);
$cache->store($data);
return Response::json($data); return Response::json($data);

View File

@ -147,7 +147,7 @@ class CurrencyController extends Controller
public function index(CurrencyRepositoryInterface $repository) public function index(CurrencyRepositoryInterface $repository)
{ {
$currencies = $repository->get(); $currencies = $repository->get();
$defaultCurrency = $repository->getCurrencyByPreference(Preferences::get('currencyPreference', 'EUR')); $defaultCurrency = $repository->getCurrencyByPreference(Preferences::get('currencyPreference', env('DEFAULT_CURRENCY','EUR')));
if (!Auth::user()->hasRole('owner')) { if (!Auth::user()->hasRole('owner')) {

View File

@ -178,7 +178,7 @@ class JsonController extends Controller
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function boxIn(ReportQueryInterface $reportQuery) public function boxIn(ReportQueryInterface $reportQuery, AccountRepositoryInterface $accountRepository)
{ {
$start = Session::get('start', Carbon::now()->startOfMonth()); $start = Session::get('start', Carbon::now()->startOfMonth());
$end = Session::get('end', Carbon::now()->endOfMonth()); $end = Session::get('end', Carbon::now()->endOfMonth());
@ -191,8 +191,8 @@ class JsonController extends Controller
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore return Response::json($cache->get()); // @codeCoverageIgnore
} }
$accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
$amount = $reportQuery->incomeInPeriodCorrected($start, $end, true)->sum('amount'); $amount = $reportQuery->incomeInPeriod($start, $end, $accounts)->sum('amount');
$data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
$cache->store($data); $cache->store($data);
@ -205,11 +205,12 @@ class JsonController extends Controller
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function boxOut(ReportQueryInterface $reportQuery) public function boxOut(ReportQueryInterface $reportQuery, AccountRepositoryInterface $accountRepository)
{ {
$start = Session::get('start', Carbon::now()->startOfMonth()); $start = Session::get('start', Carbon::now()->startOfMonth());
$end = Session::get('end', Carbon::now()->endOfMonth()); $end = Session::get('end', Carbon::now()->endOfMonth());
$accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
// works for json too! // works for json too!
$cache = new CacheProperties; $cache = new CacheProperties;
@ -220,7 +221,7 @@ class JsonController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore return Response::json($cache->get()); // @codeCoverageIgnore
} }
$amount = $reportQuery->expenseInPeriodCorrected($start, $end, true)->sum('amount'); $amount = $reportQuery->expenseInPeriod($start, $end, $accounts)->sum('amount');
$amount = $amount * -1; $amount = $amount * -1;
$data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];

View File

@ -4,6 +4,7 @@ use Carbon\Carbon;
use FireflyIII\Helpers\Report\ReportHelperInterface; use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Support\Collection;
use Session; use Session;
use View; use View;
@ -45,107 +46,176 @@ class ReportController extends Controller
$months = $this->helper->listOfMonths($start); $months = $this->helper->listOfMonths($start);
// does the user have shared accounts? // does the user have shared accounts?
$accounts = $repository->getAccounts(['Default account', 'Asset account']); $accounts = $repository->getAccounts(['Default account', 'Asset account']);
$hasShared = false; // get id's for quick links:
$accountIds = [];
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach($accounts as $account) {
if ($account->getMeta('accountRole') == 'sharedAsset') { $accountIds [] = $account->id;
$hasShared = true;
}
} }
$accountList = join(',',$accountIds);
return view('reports.index', compact('months', 'hasShared')); return view('reports.index', compact('months', 'accounts', 'start','accountList'));
} }
/** /**
* @param string $year * @param $report_type
* @param string $month * @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* *
* @param bool $shared * @return View
*
* @return \Illuminate\View\View
*/ */
public function month($year = '2014', $month = '1', $shared = false) public function defaultYear($report_type, Carbon $start, Carbon $end, Collection $accounts)
{ {
$start = new Carbon($year . '-' . $month . '-01');
$subTitle = trans('firefly.reportForMonth', ['month' => $start->formatLocalized($this->monthFormat)]);
$subTitleIcon = 'fa-calendar';
$end = clone $start;
$incomeTopLength = 8;
$expenseTopLength = 8;
if ($shared == 'shared') {
$shared = true;
$subTitle = trans('firefly.reportForMonthShared', ['month' => $start->formatLocalized($this->monthFormat)]);
}
$end->endOfMonth();
$accounts = $this->helper->getAccountReport($start, $end, $shared);
$incomes = $this->helper->getIncomeReport($start, $end, $shared);
$expenses = $this->helper->getExpenseReport($start, $end, $shared);
$budgets = $this->helper->getBudgetReport($start, $end, $shared);
$categories = $this->helper->getCategoryReport($start, $end, $shared);
$balance = $this->helper->getBalanceReport($start, $end, $shared);
$bills = $this->helper->getBillReport($start, $end);
Session::flash('gaEventCategory', 'report');
Session::flash('gaEventAction', 'month');
Session::flash('gaEventLabel', $start->format('F Y'));
return view(
'reports.month',
compact(
'start', 'shared',
'subTitle', 'subTitleIcon',
'accounts',
'incomes', 'incomeTopLength',
'expenses', 'expenseTopLength',
'budgets', 'balance',
'categories',
'bills'
)
);
}
/**
* @param $year
*
* @param bool $shared
*
* @return $this
*/
public function year($year, $shared = false)
{
$start = new Carbon('01-01-' . $year);
$end = clone $start;
$subTitle = trans('firefly.reportForYear', ['year' => $year]);
$subTitleIcon = 'fa-bar-chart';
$incomeTopLength = 8; $incomeTopLength = 8;
$expenseTopLength = 8; $expenseTopLength = 8;
if ($shared == 'shared') { $accountReport = $this->helper->getAccountReport($start, $end, $accounts);
$shared = true; $incomes = $this->helper->getIncomeReport($start, $end, $accounts);
$subTitle = trans('firefly.reportForYearShared', ['year' => $year]); $expenses = $this->helper->getExpenseReport($start, $end, $accounts);
}
$end->endOfYear();
$accounts = $this->helper->getAccountReport($start, $end, $shared);
$incomes = $this->helper->getIncomeReport($start, $end, $shared);
$expenses = $this->helper->getExpenseReport($start, $end, $shared);
Session::flash('gaEventCategory', 'report'); Session::flash('gaEventCategory', 'report');
Session::flash('gaEventAction', 'year'); Session::flash('gaEventAction', 'year');
Session::flash('gaEventLabel', $start->format('Y')); Session::flash('gaEventLabel', $start->format('Y'));
// and some id's, joined:
$accountIds = [];
/** @var Account $account */
foreach ($accounts as $account) {
$accountIds[] = $account->id;
}
$accountIds = join(',', $accountIds);
return view( return view(
'reports.year', 'reports.default.year',
compact('start', 'shared', 'accounts', 'incomes', 'expenses', 'subTitle', 'subTitleIcon', 'incomeTopLength', 'expenseTopLength') compact(
'start', 'accountReport', 'incomes', 'report_type', 'accountIds', 'end',
'expenses', 'incomeTopLength', 'expenseTopLength'
)
); );
} }
/**
* @param $report_type
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return View
*/
public function defaultMonth($report_type, Carbon $start, Carbon $end, Collection $accounts)
{
$incomeTopLength = 8;
$expenseTopLength = 8;
// get report stuff!
$accountReport = $this->helper->getAccountReport($start, $end, $accounts);
$incomes = $this->helper->getIncomeReport($start, $end, $accounts);
$expenses = $this->helper->getExpenseReport($start, $end, $accounts);
$budgets = $this->helper->getBudgetReport($start, $end, $accounts);
$categories = $this->helper->getCategoryReport($start, $end, $accounts);
$balance = $this->helper->getBalanceReport($start, $end, $accounts);
$bills = $this->helper->getBillReport($start, $end, $accounts);
// and some id's, joined:
$accountIds = [];
/** @var Account $account */
foreach ($accounts as $account) {
$accountIds[] = $account->id;
}
$accountIds = join(',', $accountIds);
// continue!
return view(
'reports.default.month',
compact(
'start', 'end', 'report_type',
'accountReport',
'incomes', 'incomeTopLength',
'expenses', 'expenseTopLength',
'budgets', 'balance',
'categories',
'bills',
'accountIds', 'report_type'
)
);
}
public function defaultMultiYear($report_type, $start, $end, $accounts)
{
// list of users stuff:
$budgets = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface')->getActiveBudgets();
$categories = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface')->getCategories();
// and some id's, joined:
$accountIds = [];
/** @var Account $account */
foreach ($accounts as $account) {
$accountIds[] = $account->id;
}
$accountIds = join(',', $accountIds);
return view(
'reports.default.multi-year', compact('budgets', 'accounts', 'categories', 'start', 'end', 'accountIds', 'report_type')
);
}
/**
* @param $report_type
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return View
*/
public function report($report_type, Carbon $start, Carbon $end, Collection $accounts)
{
// throw an error if necessary.
if ($end < $start) {
return view('error')->with('message', 'End date cannot be before start date, silly!');
}
// lower threshold
if ($start < Session::get('first')) {
$start = Session::get('first');
}
switch ($report_type) {
default:
case 'default':
View::share(
'subTitle', trans(
'firefly.report_default',
[
'start' => $start->formatLocalized($this->monthFormat),
'end' => $end->formatLocalized($this->monthFormat)
]
)
);
View::share('subTitleIcon', 'fa-calendar');
// more than one year date difference means year report.
if ($start->diffInMonths($end) > 12) {
// return view('error')->with('message', 'No report yet for this time period.');
return $this->defaultMultiYear($report_type, $start, $end, $accounts);
}
// more than two months date difference means year report.
if ($start->diffInMonths($end) > 1) {
return $this->defaultYear($report_type, $start, $end, $accounts);
}
return $this->defaultMonth($report_type, $start, $end, $accounts);
break;
}
}
} }

View File

@ -12,6 +12,7 @@ use FireflyIII\Http\Requests\JournalFormRequest;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Input; use Input;
@ -45,8 +46,9 @@ class TransactionController extends Controller
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function create(AccountRepositoryInterface $repository, $what = 'deposit') public function create(AccountRepositoryInterface $repository, $what = TransactionType::DEPOSIT)
{ {
$what = strtolower($what);
$maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
$maxPostSize = Steam::phpBytes(ini_get('post_max_size')); $maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
$uploadSize = min($maxFileSize, $maxPostSize); $uploadSize = min($maxFileSize, $maxPostSize);
@ -95,7 +97,7 @@ class TransactionController extends Controller
*/ */
public function delete(TransactionJournal $journal) public function delete(TransactionJournal $journal)
{ {
$what = strtolower($journal->transactionType->type); $what = strtolower($journal->getTransactionType());
$subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]); $subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]);
// put previous url in session // put previous url in session
@ -137,7 +139,7 @@ class TransactionController extends Controller
public function edit(AccountRepositoryInterface $repository, TransactionJournal $journal) public function edit(AccountRepositoryInterface $repository, TransactionJournal $journal)
{ {
// cannot edit opening balance // cannot edit opening balance
if ($journal->transactionType->type == 'Opening balance') { if ($journal->isOpeningBalance()) {
return view('error')->with('message', 'Cannot edit this transaction. Edit the account instead!'); return view('error')->with('message', 'Cannot edit this transaction. Edit the account instead!');
} }
@ -145,7 +147,7 @@ class TransactionController extends Controller
$maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
$maxPostSize = Steam::phpBytes(ini_get('post_max_size')); $maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
$uploadSize = min($maxFileSize, $maxPostSize); $uploadSize = min($maxFileSize, $maxPostSize);
$what = strtolower($journal->transactionType->type); $what = strtolower($journal->getTransactionType());
$accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account']));
$budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get()); $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get());
$budgets[0] = trans('form.noBudget'); $budgets[0] = trans('form.noBudget');
@ -181,7 +183,7 @@ class TransactionController extends Controller
$preFilled['amount'] = $journal->amount_positive; $preFilled['amount'] = $journal->amount_positive;
if ($journal->transactionType->type == 'Withdrawal') { if ($journal->isWithdrawal()) {
$preFilled['account_id'] = $journal->source_account->id; $preFilled['account_id'] = $journal->source_account->id;
$preFilled['expense_account'] = $journal->destination_account->name_for_editform; $preFilled['expense_account'] = $journal->destination_account->name_for_editform;
} else { } else {
@ -269,8 +271,8 @@ class TransactionController extends Controller
$t->after = bcadd($t->before, $t->amount); $t->after = bcadd($t->before, $t->amount);
} }
); );
$what = strtolower($journal->transactionType->type); $what = strtolower($journal->getTransactionType());
$subTitle = trans('firefly.' . $journal->transactionType->type) . ' "' . e($journal->description) . '"'; $subTitle = trans('firefly.' . $journal->getTransactionType()) . ' "' . e($journal->description) . '"';
return view('transactions.show', compact('journal', 'subTitle', 'what')); return view('transactions.show', compact('journal', 'subTitle', 'what'));
} }
@ -287,7 +289,7 @@ class TransactionController extends Controller
$journalData = $request->getJournalData(); $journalData = $request->getJournalData();
// if not withdrawal, unset budgetid. // if not withdrawal, unset budgetid.
if ($journalData['what'] != 'withdrawal') { if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) {
$journalData['budget_id'] = 0; $journalData['budget_id'] = 0;
} }
@ -308,7 +310,7 @@ class TransactionController extends Controller
// rescan journal, UpdateJournalConnection // rescan journal, UpdateJournalConnection
event(new JournalSaved($journal)); event(new JournalSaved($journal));
if ($journal->transactionType->type == 'Transfer' && intval($request->get('piggy_bank_id')) > 0) { if ($journal->isTransfer() && intval($request->get('piggy_bank_id')) > 0) {
event(new JournalCreated($journal, intval($request->get('piggy_bank_id')))); event(new JournalCreated($journal, intval($request->get('piggy_bank_id'))));
} }
@ -340,7 +342,7 @@ class TransactionController extends Controller
{ {
// cannot edit opening balance // cannot edit opening balance
if ($journal->transactionType->type == 'Opening balance') { if ($journal->isOpeningBalance()) {
return view('error')->with('message', 'Cannot edit this transaction. Edit the account instead!'); return view('error')->with('message', 'Cannot edit this transaction. Edit the account instead!');
} }

View File

@ -64,6 +64,7 @@ class Authenticate
Carbon::setLocale($pref->data); Carbon::setLocale($pref->data);
setlocale(LC_TIME, Config::get('firefly.locales.' . $pref->data)); setlocale(LC_TIME, Config::get('firefly.locales.' . $pref->data));
setlocale(LC_MONETARY, Config::get('firefly.locales.' . $pref->data));
return $next($request); return $next($request);
} }

View File

@ -5,6 +5,7 @@ namespace FireflyIII\Http\Requests;
use Auth; use Auth;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use FireflyIII\Models\TransactionType;
use Input; use Input;
/** /**
@ -65,7 +66,7 @@ class JournalFormRequest extends Request
]; ];
switch ($what) { switch ($what) {
case 'withdrawal': case strtolower(TransactionType::WITHDRAWAL):
$rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; $rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
$rules['expense_account'] = 'between:1,255'; $rules['expense_account'] = 'between:1,255';
$rules['category'] = 'between:1,255'; $rules['category'] = 'between:1,255';
@ -73,12 +74,12 @@ class JournalFormRequest extends Request
$rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets'; $rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets';
} }
break; break;
case 'deposit': case strtolower(TransactionType::DEPOSIT):
$rules['category'] = 'between:1,255'; $rules['category'] = 'between:1,255';
$rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; $rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
$rules['revenue_account'] = 'between:1,255'; $rules['revenue_account'] = 'between:1,255';
break; break;
case 'transfer': case strtolower(TransactionType::TRANSFER):
$rules['account_from_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_to_id'; $rules['account_from_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_to_id';
$rules['account_to_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_from_id'; $rules['account_to_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_from_id';
$rules['category'] = 'between:1,255'; $rules['category'] = 'between:1,255';

View File

@ -351,30 +351,14 @@ Breadcrumbs::register(
); );
Breadcrumbs::register( Breadcrumbs::register(
'reports.year', function (Generator $breadcrumbs, Carbon $date, $shared) { 'reports.report', function (Generator $breadcrumbs, Carbon $start, Carbon $end, $reportType, $accountIds) {
$breadcrumbs->parent('reports.index'); $breadcrumbs->parent('reports.index');
if ($shared) {
$title = trans('breadcrumbs.yearly_report_shared', ['date' => $date->year]);
} else {
$title = trans('breadcrumbs.yearly_report', ['date' => $date->year]);
}
$breadcrumbs->push($title, route('reports.year', [$date->year]));
}
);
Breadcrumbs::register( $pref = Preferences::get('language', 'en')->data;
'reports.month', function (Generator $breadcrumbs, Carbon $date, $shared) { $monthFormat = Config::get('firefly.monthAndDay.' . $pref);
$breadcrumbs->parent('reports.year', $date, $shared); $title = trans('firefly.report_default', ['start' => $start->formatLocalized($monthFormat), 'end' => $end->formatLocalized($monthFormat)]);
$language = Preferences::get('language', 'en')->data;
$format = Config::get('firefly.month.' . $language);
if ($shared) { $breadcrumbs->push($title, route('reports.report', ['url' => 'abcde']));
$title = trans('breadcrumbs.monthly_report_shared', ['date' => $date->formatLocalized($format)]);
} else {
$title = trans('breadcrumbs.monthly_report', ['date' => $date->formatLocalized($format)]);
}
$breadcrumbs->push($title, route('reports.month', [$date->year, $date->month]));
} }
); );
@ -416,7 +400,7 @@ Breadcrumbs::register(
Breadcrumbs::register( Breadcrumbs::register(
'transactions.show', function (Generator $breadcrumbs, TransactionJournal $journal) { 'transactions.show', function (Generator $breadcrumbs, TransactionJournal $journal) {
$breadcrumbs->parent('transactions.index', strtolower($journal->transactionType->type)); $breadcrumbs->parent('transactions.index', strtolower($journal->getTransactionType()));
$breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id]));
} }

View File

@ -1,4 +1,5 @@
<?php <?php
use Carbon\Carbon;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Attachment; use FireflyIII\Models\Attachment;
use FireflyIII\Models\Bill; use FireflyIII\Models\Bill;
@ -29,6 +30,111 @@ Route::bind(
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
); );
// account list! Yay!
Route::bind(
'accountList',
function ($value) {
if (Auth::check()) {
$ids = explode(',', $value);
/** @var \Illuminate\Support\Collection $object */
$object = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->where('account_types.editable', 1)
->whereIn('accounts.id', $ids)
->where('user_id', Auth::user()->id)
->get(['accounts.*']);
if ($object->count() > 0) {
return $object;
}
}
throw new NotFoundHttpException;
}
);
// budget list
Route::bind(
'budgetList',
function ($value) {
if (Auth::check()) {
$ids = explode(',', $value);
/** @var \Illuminate\Support\Collection $object */
$object = Budget::where('active', 1)
->whereIn('id', $ids)
->where('user_id', Auth::user()->id)
->get();
// add empty budget if applicable.
if (in_array('0', $ids)) {
$object->push(new Budget);
}
if ($object->count() > 0) {
return $object;
}
}
throw new NotFoundHttpException;
}
);
// category list
Route::bind(
'categoryList',
function ($value) {
if (Auth::check()) {
$ids = explode(',', $value);
/** @var \Illuminate\Support\Collection $object */
$object = Category::whereIn('id', $ids)
->where('user_id', Auth::user()->id)
->get();
// add empty budget if applicable.
if (in_array('0', $ids)) {
$object->push(new Category);
}
if ($object->count() > 0) {
return $object;
}
}
throw new NotFoundHttpException;
}
);
// Date
Route::bind(
'start_date',
function ($value) {
if (Auth::check()) {
try {
$date = new Carbon($value);
} catch (Exception $e) {
Log::error('Could not parse date "' . $value . '" for user #' . Auth::user()->id);
throw new NotFoundHttpException;
}
return $date;
}
throw new NotFoundHttpException;
}
);
// Date
Route::bind(
'end_date',
function ($value) {
if (Auth::check()) {
try {
$date = new Carbon($value);
} catch (Exception $e) {
Log::error('Could not parse date "' . $value . '" for user #' . Auth::user()->id);
throw new NotFoundHttpException;
}
return $date;
}
throw new NotFoundHttpException;
}
);
Route::bind( Route::bind(
'tj', function ($value) { 'tj', function ($value) {
@ -287,6 +393,7 @@ Route::group(
Route::get('/chart/account/month/{year}/{month}/{shared?}', ['uses' => 'Chart\AccountController@all'])->where( Route::get('/chart/account/month/{year}/{month}/{shared?}', ['uses' => 'Chart\AccountController@all'])->where(
['year' => '[0-9]{4}', 'month' => '[0-9]{1,2}', 'shared' => 'shared'] ['year' => '[0-9]{4}', 'month' => '[0-9]{1,2}', 'shared' => 'shared']
); );
Route::get('/chart/account/report/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\AccountController@report']);
Route::get('/chart/account/{account}', ['uses' => 'Chart\AccountController@single']); Route::get('/chart/account/{account}', ['uses' => 'Chart\AccountController@single']);
@ -296,18 +403,24 @@ Route::group(
// budgets: // budgets:
Route::get('/chart/budget/frontpage', ['uses' => 'Chart\BudgetController@frontpage']); Route::get('/chart/budget/frontpage', ['uses' => 'Chart\BudgetController@frontpage']);
Route::get('/chart/budget/year/{year}/{shared?}', ['uses' => 'Chart\BudgetController@year'])->where(['year' => '[0-9]{4}', 'shared' => 'shared']);
// this chart is used in reports:
Route::get('/chart/budget/year/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@year']);
Route::get('/chart/budget/multi-year/{report_type}/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']);
Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'Chart\BudgetController@budgetLimit']); Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'Chart\BudgetController@budgetLimit']);
Route::get('/chart/budget/{budget}', ['uses' => 'Chart\BudgetController@budget']); Route::get('/chart/budget/{budget}', ['uses' => 'Chart\BudgetController@budget']);
// categories: // categories:
Route::get('/chart/category/frontpage', ['uses' => 'Chart\CategoryController@frontpage']); Route::get('/chart/category/frontpage', ['uses' => 'Chart\CategoryController@frontpage']);
Route::get('/chart/category/spent-in-year/{year}/{shared?}', ['uses' => 'Chart\CategoryController@spentInYear'])->where(
['year' => '[0-9]{4}', 'shared' => 'shared'] // these three charts are for reports:
); Route::get('/chart/category/spent-in-year/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@spentInYear']);
Route::get('/chart/category/earned-in-year/{year}/{shared?}', ['uses' => 'Chart\CategoryController@earnedInYear'])->where( Route::get('/chart/category/earned-in-year/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@earnedInYear']);
['year' => '[0-9]{4}', 'shared' => 'shared'] Route::get(
'/chart/category/multi-year/{report_type}/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear']
); );
Route::get('/chart/category/{category}/period', ['uses' => 'Chart\CategoryController@currentPeriod']); Route::get('/chart/category/{category}/period', ['uses' => 'Chart\CategoryController@currentPeriod']);
Route::get('/chart/category/{category}/period/{date}', ['uses' => 'Chart\CategoryController@specificPeriod']); Route::get('/chart/category/{category}/period/{date}', ['uses' => 'Chart\CategoryController@specificPeriod']);
Route::get('/chart/category/{category}/all', ['uses' => 'Chart\CategoryController@all']); Route::get('/chart/category/{category}/all', ['uses' => 'Chart\CategoryController@all']);
@ -316,8 +429,10 @@ Route::group(
Route::get('/chart/piggyBank/{piggyBank}', ['uses' => 'Chart\PiggyBankController@history']); Route::get('/chart/piggyBank/{piggyBank}', ['uses' => 'Chart\PiggyBankController@history']);
// reports: // reports:
Route::get('/chart/report/in-out/{year}/{shared?}', ['uses' => 'Chart\ReportController@yearInOut'])->where(['year' => '[0-9]{4}', 'shared' => 'shared']); Route::get('/chart/report/in-out/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@yearInOut'])->where(
Route::get('/chart/report/in-out-sum/{year}/{shared?}', ['uses' => 'Chart\ReportController@yearInOutSummarized'])->where( ['year' => '[0-9]{4}', 'shared' => 'shared']
);
Route::get('/chart/report/in-out-sum/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@yearInOutSummarized'])->where(
['year' => '[0-9]{4}', 'shared' => 'shared'] ['year' => '[0-9]{4}', 'shared' => 'shared']
); );
@ -385,10 +500,10 @@ Route::group(
* Report Controller * Report Controller
*/ */
Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']); Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']);
Route::get('/reports/{year}/{shared?}', ['uses' => 'ReportController@year', 'as' => 'reports.year'])->where(['year' => '[0-9]{4}', 'shared' => 'shared']); // Route::post('/reports/select', ['uses' => 'ReportController@select', 'as' => 'reports.select']);
Route::get('/reports/{year}/{month}/{shared?}', ['uses' => 'ReportController@month', 'as' => 'reports.month'])->where( Route::get('/reports/report/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'ReportController@report', 'as' => 'reports.report']);
['year' => '[0-9]{4}', 'month' => '[0-9]{1,2}', 'shared' => 'shared'] // Route::get('/reports/{year}/{shared?}', ['uses' => 'ReportController@year', 'as' => 'reports.year'])->where(['year' => '[0-9]{4}', 'shared' => 'shared']);
); // Route::get('/reports/{year}/{month}/{shared?}', ['uses' => 'ReportController@month', 'as' => 'reports.month'])->where(['year' => '[0-9]{4}', 'month' => '[0-9]{1,2}', 'shared' => 'shared']);
// pop ups for budget report: // pop ups for budget report:

View File

@ -147,10 +147,9 @@ class TransactionJournal extends Model
} }
bcscale(2); bcscale(2);
$type = $this->transactionType->type;
$transaction = $this->transactions->sortByDesc('amount')->first(); $transaction = $this->transactions->sortByDesc('amount')->first();
$amount = $transaction->amount; $amount = $transaction->amount;
if ($type == 'Withdrawal') { if ($this->isWithdrawal()) {
$amount = $amount * -1; $amount = $amount * -1;
} }
$cache->store($amount); $cache->store($amount);
@ -167,15 +166,15 @@ class TransactionJournal extends Model
*/ */
protected function amountByTagAdvancePayment(Tag $tag, $amount) protected function amountByTagAdvancePayment(Tag $tag, $amount)
{ {
if ($this->transactionType->type == 'Withdrawal') { if ($this->isWithdrawal()) {
$others = $tag->transactionJournals()->transactionTypes(['Deposit'])->get(); $others = $tag->transactionJournals()->transactionTypes([TransactionType::DEPOSIT])->get();
foreach ($others as $other) { foreach ($others as $other) {
$amount = bcsub($amount, $other->amount_positive); $amount = bcsub($amount, $other->amount_positive);
} }
return $amount; return $amount;
} }
if ($this->transactionType->type == 'Deposit') { if ($this->isDeposit()) {
return '0'; return '0';
} }
@ -190,8 +189,8 @@ class TransactionJournal extends Model
*/ */
protected function amountByTagBalancingAct($tag, $amount) protected function amountByTagBalancingAct($tag, $amount)
{ {
if ($this->transactionType->type == 'Withdrawal') { if ($this->isWithdrawal()) {
$transfer = $tag->transactionJournals()->transactionTypes(['Transfer'])->first(); $transfer = $tag->transactionJournals()->transactionTypes([TransactionType::TRANSFER])->first();
if ($transfer) { if ($transfer) {
$amount = bcsub($amount, $transfer->amount_positive); $amount = bcsub($amount, $transfer->amount_positive);
@ -491,4 +490,43 @@ class TransactionJournal extends Model
return $this->belongsTo('FireflyIII\User'); return $this->belongsTo('FireflyIII\User');
} }
/**
* @return string
*/
public function getTransactionType()
{
return $this->transactionType->type;
}
/**
* @return bool
*/
public function isWithdrawal()
{
return $this->transactionType->isWithdrawal();
}
/**
* @return bool
*/
public function isDeposit()
{
return $this->transactionType->isDeposit();
}
/**
* @return bool
*/
public function isTransfer()
{
return $this->transactionType->isTransfer();
}
/**
* @return bool
*/
public function isOpeningBalance()
{
return $this->transactionType->isOpeningBalance();
}
} }

View File

@ -23,6 +23,11 @@ class TransactionType extends Model
{ {
use SoftDeletes; use SoftDeletes;
const WITHDRAWAL = 'Withdrawal';
const DEPOSIT = 'Deposit';
const TRANSFER = 'Transfer';
const OPENING_BALANCE = 'Opening balance';
/** /**
* @return array * @return array
*/ */
@ -38,4 +43,36 @@ class TransactionType extends Model
{ {
return $this->hasMany('FireflyIII\Models\TransactionJournal'); return $this->hasMany('FireflyIII\Models\TransactionJournal');
} }
/**
* @return bool
*/
public function isWithdrawal()
{
return $this->type === TransactionType::WITHDRAWAL;
}
/**
* @return bool
*/
public function isDeposit()
{
return $this->type === TransactionType::DEPOSIT;
}
/**
* @return bool
*/
public function isTransfer()
{
return $this->type === TransactionType::TRANSFER;
}
/**
* @return bool
*/
public function isOpeningBalance()
{
return $this->type === TransactionType::OPENING_BALANCE;
}
} }

View File

@ -234,7 +234,7 @@ class AccountRepository implements AccountRepositoryInterface
$ids = array_unique($ids); $ids = array_unique($ids);
if (count($ids) > 0) { if (count($ids) > 0) {
$accounts = Auth::user()->accounts()->whereIn('id', $ids)->get(); $accounts = Auth::user()->accounts()->whereIn('id', $ids)->where('accounts.active', 1)->get();
} }
bcscale(2); bcscale(2);
@ -273,6 +273,7 @@ class AccountRepository implements AccountRepositoryInterface
$accounts = Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC') $accounts = Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
->where('account_meta.name', 'accountRole') ->where('account_meta.name', 'accountRole')
->where('accounts.active', 1)
->where('account_meta.data', '"savingAsset"') ->where('account_meta.data', '"savingAsset"')
->get(['accounts.*']); ->get(['accounts.*']);
$start = clone Session::get('start', new Carbon); $start = clone Session::get('start', new Carbon);
@ -329,7 +330,7 @@ class AccountRepository implements AccountRepositoryInterface
->where('transaction_journals.user_id', Auth::user()->id) ->where('transaction_journals.user_id', Auth::user()->id)
->where('transaction_journals.date', '>=', $start->format('Y-m-d')) ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_types.type', 'Transfer'); ->where('transaction_types.type', TransactionType::TRANSFER);
} }
)->get(); )->get();
@ -375,7 +376,7 @@ class AccountRepository implements AccountRepositoryInterface
return TransactionJournal return TransactionJournal
::orderBy('transaction_journals.date', 'ASC') ::orderBy('transaction_journals.date', 'ASC')
->accountIs($account) ->accountIs($account)
->transactionTypes(['Opening balance']) ->transactionTypes([TransactionType::OPENING_BALANCE])
->orderBy('created_at', 'ASC') ->orderBy('created_at', 'ASC')
->first(['transaction_journals.*']); ->first(['transaction_journals.*']);
} }
@ -548,7 +549,7 @@ class AccountRepository implements AccountRepositoryInterface
*/ */
protected function storeInitialBalance(Account $account, Account $opposing, array $data) protected function storeInitialBalance(Account $account, Account $opposing, array $data)
{ {
$transactionType = TransactionType::whereType('Opening balance')->first(); $transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
$journal = TransactionJournal::create( $journal = TransactionJournal::create(
[ [
'user_id' => $data['user'], 'user_id' => $data['user'],
@ -642,4 +643,14 @@ class AccountRepository implements AccountRepositoryInterface
return $journal; return $journal;
} }
/**
* @param $accountId
*
* @return Account
*/
public function find($accountId)
{
return Auth::user()->accounts()->findOrNew($accountId);
}
} }

View File

@ -24,6 +24,13 @@ interface AccountRepositoryInterface
*/ */
public function countAccounts(array $types); public function countAccounts(array $types);
/**
* @param $accountId
*
* @return Account
*/
public function find($accountId);
/** /**
* @param Account $account * @param Account $account
* @param Account $moveTo * @param Account $moveTo

View File

@ -5,6 +5,7 @@ namespace FireflyIII\Repositories\Bill;
use Auth; use Auth;
use Carbon\Carbon; use Carbon\Carbon;
use DB; use DB;
use FireflyIII\Models\Account;
use FireflyIII\Models\Bill; use FireflyIII\Models\Bill;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
@ -108,6 +109,46 @@ class BillRepository implements BillRepositoryInterface
return $set; return $set;
} }
/**
* @param Collection $accounts
*
* @return Collection
*/
public function getBillsForAccounts(Collection $accounts)
{
/** @var Collection $set */
$set = Auth::user()->bills()->orderBy('name', 'ASC')->get();
$ids = [];
/** @var Account $account */
foreach ($accounts as $account) {
$ids[] = $account->id;
}
$set = $set->filter(
function (Bill $bill) use ($ids) {
// get transaction journals from or to any of the mentioned accounts.
// if zero, return null.
$journals = $bill->transactionjournals()->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereIn('transactions.account_id', $ids)->count();
return ($journals > 0);
}
);
$set = $set->sortBy(
function (Bill $bill) {
$int = $bill->active == 1 ? 0 : 1;
return $int . strtolower($bill->name);
}
);
return $set;
}
/** /**
* @param Bill $bill * @param Bill $bill
* *
@ -154,7 +195,7 @@ class BillRepository implements BillRepositoryInterface
} }
$journals = new Collection; $journals = new Collection;
if (count($ids) > 0) { if (count($ids) > 0) {
$journals = Auth::user()->transactionjournals()->transactionTypes(['Withdrawal'])->whereIn('transaction_journals.id', $ids)->get( $journals = Auth::user()->transactionjournals()->transactionTypes([TransactionType::WITHDRAWAL])->whereIn('transaction_journals.id', $ids)->get(
['transaction_journals.*'] ['transaction_journals.*']
); );
} }
@ -276,12 +317,21 @@ class BillRepository implements BillRepositoryInterface
*/ */
public function scan(Bill $bill, TransactionJournal $journal) public function scan(Bill $bill, TransactionJournal $journal)
{ {
/*
* Can only support withdrawals.
*/
if (false === $journal->isWithdrawal()) {
return false;
}
$matches = explode(',', $bill->match); $matches = explode(',', $bill->match);
$description = strtolower($journal->description) . ' ' . strtolower($journal->destination_account->name); $description = strtolower($journal->description) . ' ' . strtolower($journal->destination_account->name);
$wordMatch = $this->doWordMatch($matches, $description); $wordMatch = $this->doWordMatch($matches, $description);
$amountMatch = $this->doAmountMatch($journal->amount_positive, $bill->amount_min, $bill->amount_max); $amountMatch = $this->doAmountMatch($journal->amount_positive, $bill->amount_min, $bill->amount_max);
Log::debug('Journal #' . $journal->id . ' has description "' . $description . '"'); Log::debug('Journal #' . $journal->id . ' has description "' . $description . '"');
/* /*
* If both, update! * If both, update!
*/ */

View File

@ -77,6 +77,15 @@ interface BillRepositoryInterface
*/ */
public function getBills(); public function getBills();
/**
* Gets the bills which have some kind of relevance to the accounts mentioned.
*
* @param Collection $accounts
*
* @return Collection
*/
public function getBillsForAccounts(Collection $accounts);
/** /**
* @param Bill $bill * @param Bill $bill
* *

View File

@ -7,12 +7,14 @@ use Carbon\Carbon;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Shared\ComponentRepository; use FireflyIII\Repositories\Shared\ComponentRepository;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Input; use Input;
use Log;
/** /**
* Class BudgetRepository * Class BudgetRepository
@ -50,10 +52,10 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
* *
* @return float * @return float
*/ */
public function expensesOnDayCorrected(Budget $budget, Carbon $date) public function expensesOnDay(Budget $budget, Carbon $date)
{ {
bcscale(2); bcscale(2);
$sum = $budget->transactionjournals()->transactionTypes(['Withdrawal'])->onDate($date)->get(['transaction_journals.*'])->sum('amount'); $sum = $budget->transactionjournals()->transactionTypes([TransactionType::WITHDRAWAL])->onDate($date)->get(['transaction_journals.*'])->sum('amount');
return $sum; return $sum;
} }
@ -87,10 +89,10 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
/** @var Collection $repetitions */ /** @var Collection $repetitions */
return LimitRepetition:: return LimitRepetition::
leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id')
->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')) ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'))
->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')) ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.budget_id', $budget->id) ->where('budget_limits.budget_id', $budget->id)
->get(['limit_repetitions.*']); ->get(['limit_repetitions.*']);
} }
/** /**
@ -139,9 +141,11 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$data = $budget->limitrepetitions() $data = $budget->limitrepetitions()
->where('limit_repetitions.startdate', $start) ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00'))
->where('limit_repetitions.enddate', $end) ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00'))
->first(['limit_repetitions.*']); ->first(['limit_repetitions.*']);
//Log::debug('Looking for limit reps for budget #' . $budget->id . ' start [' . $start . '] and end [' . $end . '].');
//Log::debug(DB::getQueryLog())
$cache->store($data); $cache->store($data);
return $data; return $data;
@ -182,9 +186,9 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
/** /**
* Returns all the transaction journals for a limit, possibly limited by a limit repetition. * Returns all the transaction journals for a limit, possibly limited by a limit repetition.
* *
* @param Budget $budget * @param Budget $budget
* @param LimitRepetition $repetition * @param LimitRepetition $repetition
* @param int $take * @param int $take
* *
* @return LengthAwarePaginator * @return LengthAwarePaginator
*/ */
@ -201,11 +205,11 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0; $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0;
$setQuery = $budget->transactionJournals()->withRelevantData()->take($take)->offset($offset) $setQuery = $budget->transactionJournals()->withRelevantData()->take($take)->offset($offset)
->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC'); ->orderBy('transaction_journals.id', 'DESC');
$countQuery = $budget->transactionJournals(); $countQuery = $budget->transactionJournals();
@ -215,7 +219,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
} }
$set = $setQuery->get(['transaction_journals.*']); $set = $setQuery->get(['transaction_journals.*']);
$count = $countQuery->count(); $count = $countQuery->count();
@ -249,9 +253,9 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
public function getLimitAmountOnDate(Budget $budget, Carbon $date) public function getLimitAmountOnDate(Budget $budget, Carbon $date)
{ {
$repetition = LimitRepetition::leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') $repetition = LimitRepetition::leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id')
->where('limit_repetitions.startdate', $date->format('Y-m-d 00:00:00')) ->where('limit_repetitions.startdate', $date->format('Y-m-d 00:00:00'))
->where('budget_limits.budget_id', $budget->id) ->where('budget_limits.budget_id', $budget->id)
->first(['limit_repetitions.*']); ->first(['limit_repetitions.*']);
if ($repetition) { if ($repetition) {
return $repetition->amount; return $repetition->amount;
@ -269,15 +273,15 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
public function getWithoutBudget(Carbon $start, Carbon $end) public function getWithoutBudget(Carbon $start, Carbon $end)
{ {
return Auth::user() return Auth::user()
->transactionjournals() ->transactionjournals()
->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->whereNull('budget_transaction_journal.id') ->whereNull('budget_transaction_journal.id')
->before($end) ->before($end)
->after($start) ->after($start)
->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC') ->orderBy('transaction_journals.id', 'DESC')
->get(['transaction_journals.*']); ->get(['transaction_journals.*']);
} }
/** /**
@ -289,33 +293,44 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
public function getWithoutBudgetSum(Carbon $start, Carbon $end) public function getWithoutBudgetSum(Carbon $start, Carbon $end)
{ {
$noBudgetSet = Auth::user() $noBudgetSet = Auth::user()
->transactionjournals() ->transactionjournals()
->whereNotIn( ->whereNotIn(
'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) {
$query $query
->select('transaction_journals.id') ->select('transaction_journals.id')
->from('transaction_journals') ->from('transaction_journals')
->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00'))
->whereNotNull('budget_transaction_journal.budget_id'); ->whereNotNull('budget_transaction_journal.budget_id');
} }
) )
->after($start) ->after($start)
->before($end) ->before($end)
->transactionTypes(['Withdrawal']) ->transactionTypes([TransactionType::WITHDRAWAL])
->get(['transaction_journals.*'])->sum('amount'); ->get(['transaction_journals.*'])->sum('amount');
bcscale(2); return $noBudgetSet;
return bcmul($noBudgetSet, -1);
} }
/** /**
* @param Budget $budget * @param Budget $budget
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param bool $shared * @param Collection $accounts
*
* @return string
*/
public function balanceInPeriodForList(Budget $budget, Carbon $start, Carbon $end, Collection $accounts)
{
return $this->commonBalanceInPeriodForList($budget, $start, $end, $accounts);
}
/**
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param bool $shared
* *
* @return string * @return string
*/ */
@ -334,7 +349,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
$newBudget = new Budget( $newBudget = new Budget(
[ [
'user_id' => $data['user'], 'user_id' => $data['user'],
'name' => $data['name'], 'name' => $data['name'],
] ]
); );
$newBudget->save(); $newBudget->save();
@ -345,14 +360,14 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
/** /**
* @param Budget $budget * @param Budget $budget
* @param array $data * @param array $data
* *
* @return Budget * @return Budget
*/ */
public function update(Budget $budget, array $data) public function update(Budget $budget, array $data)
{ {
// update the account: // update the account:
$budget->name = $data['name']; $budget->name = $data['name'];
$budget->active = $data['active']; $budget->active = $data['active'];
$budget->save(); $budget->save();
@ -376,10 +391,10 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
// if not, create one! // if not, create one!
$limit = new BudgetLimit; $limit = new BudgetLimit;
$limit->budget()->associate($budget); $limit->budget()->associate($budget);
$limit->startdate = $date; $limit->startdate = $date;
$limit->amount = $amount; $limit->amount = $amount;
$limit->repeat_freq = 'monthly'; $limit->repeat_freq = 'monthly';
$limit->repeats = 0; $limit->repeats = 0;
$limit->save(); $limit->save();
// likewise, there should be a limit repetition to match the end date // likewise, there should be a limit repetition to match the end date

View File

@ -35,7 +35,7 @@ interface BudgetRepositoryInterface
* *
* @return float * @return float
*/ */
public function expensesOnDayCorrected(Budget $budget, Carbon $date); public function expensesOnDay(Budget $budget, Carbon $date);
/** /**
* @return Collection * @return Collection
@ -139,6 +139,19 @@ interface BudgetRepositoryInterface
*/ */
public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, $shared = true); public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, $shared = true);
/**
*
* Same as ::spentInPeriod but corrects journals for a set of accounts
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function balanceInPeriodForList(Budget $budget, Carbon $start, Carbon $end, Collection $accounts);
/** /**
* @param array $data * @param array $data
* *

View File

@ -7,6 +7,7 @@ use Carbon\Carbon;
use Crypt; use Crypt;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Shared\ComponentRepository; use FireflyIII\Repositories\Shared\ComponentRepository;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -58,6 +59,71 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
return $set; return $set;
} }
/**
* Returns the amount earned without category by accounts in period.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function earnedNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end)
{
$accountIds = [];
foreach ($accounts as $account) {
$accountIds[] = $account->id;
}
// is deposit AND account_from is in the list of $accounts
// not from any of the accounts in the list?
return Auth::user()
->transactionjournals()
->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->whereNull('category_transaction_journal.id')
->before($end)
->after($start)
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereIn('transactions.account_id', $accountIds)
->transactionTypes([TransactionType::DEPOSIT])
->get(['transaction_journals.*'])->sum('amount');
}
/**
* Returns the amount spent without category by accounts in period.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function spentNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end)
{
$accountIds = [];
foreach ($accounts as $account) {
$accountIds[] = $account->id;
}
// is withdrawal or transfer AND account_from is in the list of $accounts
return Auth::user()
->transactionjournals()
->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->whereNull('category_transaction_journal.id')
->before($end)
->after($start)
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereIn('transactions.account_id', $accountIds)
->transactionTypes([TransactionType::WITHDRAWAL])
->get(['transaction_journals.*'])->sum('amount');
}
/** /**
* *
@ -66,7 +132,7 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
* *
* @return array * @return array
*/ */
public function getCategoriesAndExpensesCorrected(Carbon $start, Carbon $end) public function getCategoriesAndExpenses(Carbon $start, Carbon $end)
{ {
$set = Auth::user()->transactionjournals() $set = Auth::user()->transactionjournals()
->leftJoin( ->leftJoin(
@ -76,7 +142,7 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
->before($end) ->before($end)
->where('categories.user_id', Auth::user()->id) ->where('categories.user_id', Auth::user()->id)
->after($start) ->after($start)
->transactionTypes(['Withdrawal']) ->transactionTypes([TransactionType::WITHDRAWAL])
->get(['categories.id as category_id', 'categories.encrypted as category_encrypted', 'categories.name', 'transaction_journals.*']); ->get(['categories.id as category_id', 'categories.encrypted as category_encrypted', 'categories.name', 'transaction_journals.*']);
bcscale(2); bcscale(2);
@ -190,6 +256,19 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
return $this->commonBalanceInPeriod($category, $start, $end, $shared); return $this->commonBalanceInPeriod($category, $start, $end, $shared);
} }
/**
* @param Category $category
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function balanceInPeriodForList(Category $category, Carbon $start, Carbon $end, Collection $accounts)
{
return $this->commonBalanceInPeriodForList($category, $start, $end, $accounts);
}
/** /**
* Corrected for tags * Corrected for tags
* *
@ -198,9 +277,9 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
* *
* @return string * @return string
*/ */
public function spentOnDaySumCorrected(Category $category, Carbon $date) public function spentOnDaySum(Category $category, Carbon $date)
{ {
return $category->transactionjournals()->transactionTypes(['Withdrawal'])->onDate($date)->get(['transaction_journals.*'])->sum('amount'); return $category->transactionjournals()->transactionTypes([TransactionType::WITHDRAWAL])->onDate($date)->get(['transaction_journals.*'])->sum('amount');
} }
/** /**
@ -285,9 +364,10 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$sum = $category->transactionjournals()->transactionTypes(['Withdrawal'])->before($end)->after($start)->get(['transaction_journals.*'])->sum( $sum = $category->transactionjournals()->transactionTypes([TransactionType::WITHDRAWAL])->before($end)->after($start)->get(['transaction_journals.*'])
'amount' ->sum(
); 'amount'
);
$cache->store($sum); $cache->store($sum);
@ -315,9 +395,10 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$sum = $category->transactionjournals()->transactionTypes(['Deposit'])->before($end)->after($start)->get(['transaction_journals.*'])->sum( $sum = $category->transactionjournals()->transactionTypes([TransactionType::DEPOSIT])->before($end)->after($start)->get(['transaction_journals.*'])
'amount' ->sum(
); 'amount'
);
$cache->store($sum); $cache->store($sum);
@ -365,8 +446,69 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito
* *
* @return float * @return float
*/ */
public function earnedOnDaySumCorrected(Category $category, Carbon $date) public function earnedOnDaySum(Category $category, Carbon $date)
{ {
return $category->transactionjournals()->transactionTypes(['Deposit'])->onDate($date)->get(['transaction_journals.*'])->sum('amount'); return $category->transactionjournals()->transactionTypes([TransactionType::DEPOSIT])->onDate($date)->get(['transaction_journals.*'])->sum('amount');
}
/**
* Calculates how much is spent in this period.
*
* @param Category $category
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function spentInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end)
{
$accountIds = [];
foreach ($accounts as $account) {
$accountIds[] = $account->id;
}
$sum
= $category
->transactionjournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->after($start)
->before($end)
->whereIn('transactions.account_id', $accountIds)
->transactionTypes([TransactionType::WITHDRAWAL])
->get(['transaction_journals.*'])
->sum('amount');
return $sum;
}
/**
* Calculate how much is earned in this period.
*
* @param Category $category
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function earnedInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end)
{
$accountIds = [];
foreach ($accounts as $account) {
$accountIds[] = $account->id;
}
$sum
= $category
->transactionjournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->before($end)
->whereIn('transactions.account_id', $accountIds)
->transactionTypes([TransactionType::DEPOSIT])
->after($start)
->get(['transaction_journals.*'])
->sum('amount');
return $sum;
} }
} }

View File

@ -39,6 +39,53 @@ interface CategoryRepositoryInterface
*/ */
public function getCategories(); public function getCategories();
/**
* Calculates how much is spent in this period.
*
* @param Category $category
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function spentInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end);
/**
* Calculate how much is earned in this period.
*
* @param Category $category
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function earnedInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end);
/**
* Returns the amount spent without category by accounts in period.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function spentNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end);
/**
* Returns the amount earned without category by accounts in period.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function earnedNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end);
/** /**
* Corrected for tags. * Corrected for tags.
* *
@ -47,7 +94,7 @@ interface CategoryRepositoryInterface
* *
* @return array * @return array
*/ */
public function getCategoriesAndExpensesCorrected(Carbon $start, Carbon $end); public function getCategoriesAndExpenses(Carbon $start, Carbon $end);
/** /**
* @param Category $category * @param Category $category
@ -77,8 +124,8 @@ interface CategoryRepositoryInterface
* limited by a start or end date. * limited by a start or end date.
* *
* @param Category $category * @param Category $category
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return string * @return string
*/ */
@ -112,6 +159,19 @@ interface CategoryRepositoryInterface
*/ */
public function balanceInPeriod(Category $category, Carbon $start, Carbon $end, $shared = false); public function balanceInPeriod(Category $category, Carbon $start, Carbon $end, $shared = false);
/**
* Corrected for tags.
*
* @param Category $category
* @param \Carbon\Carbon $start
* @param \Carbon\Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function balanceInPeriodForList(Category $category, Carbon $start, Carbon $end, Collection $accounts);
/** /**
* @param Category $category * @param Category $category
* @param \Carbon\Carbon $start * @param \Carbon\Carbon $start
@ -143,7 +203,7 @@ interface CategoryRepositoryInterface
* *
* @return float * @return float
*/ */
public function spentOnDaySumCorrected(Category $category, Carbon $date); public function spentOnDaySum(Category $category, Carbon $date);
/** /**
* *
@ -154,7 +214,7 @@ interface CategoryRepositoryInterface
* *
* @return float * @return float
*/ */
public function earnedOnDaySumCorrected(Category $category, Carbon $date); public function earnedOnDaySum(Category $category, Carbon $date);
/** /**
* @param array $data * @param array $data

View File

@ -327,15 +327,15 @@ class JournalRepository implements JournalRepositoryInterface
$fromAccount = null; $fromAccount = null;
$toAccount = null; $toAccount = null;
switch ($type->type) { switch ($type->type) {
case 'Withdrawal': case TransactionType::WITHDRAWAL:
list($fromAccount, $toAccount) = $this->storeWithdrawalAccounts($data); list($fromAccount, $toAccount) = $this->storeWithdrawalAccounts($data);
break; break;
case 'Deposit': case TransactionType::DEPOSIT:
list($fromAccount, $toAccount) = $this->storeDepositAccounts($data); list($fromAccount, $toAccount) = $this->storeDepositAccounts($data);
break; break;
case 'Transfer': case TransactionType::TRANSFER:
$fromAccount = Account::find($data['account_from_id']); $fromAccount = Account::find($data['account_from_id']);
$toAccount = Account::find($data['account_to_id']); $toAccount = Account::find($data['account_to_id']);
break; break;

View File

@ -3,8 +3,11 @@
namespace FireflyIII\Repositories\Shared; namespace FireflyIII\Repositories\Shared;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
/** /**
* Class ComponentRepository * Class ComponentRepository
@ -39,7 +42,10 @@ class ComponentRepository
} }
if ($shared === true) { // shared is true: always ignore transfers between accounts! if ($shared === true) { // shared is true: always ignore transfers between accounts!
$sum = $object->transactionjournals()->transactionTypes(['Withdrawal', 'Deposit', 'Opening balance'])->before($end)->after($start) $sum = $object->transactionjournals()
->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE])
->before($end)
->after($start)
->get(['transaction_journals.*'])->sum('amount'); ->get(['transaction_journals.*'])->sum('amount');
} else { } else {
// do something else, SEE budgets. // do something else, SEE budgets.
@ -47,7 +53,7 @@ class ComponentRepository
$sum = $object->transactionjournals()->before($end)->after($start) $sum = $object->transactionjournals()->before($end)->after($start)
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->transactionTypes(['Withdrawal', 'Deposit', 'Opening balance']) ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE])
->leftJoin( ->leftJoin(
'account_meta', function (JoinClause $join) { 'account_meta', function (JoinClause $join) {
$join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole');
@ -59,4 +65,46 @@ class ComponentRepository
return $sum; return $sum;
} }
/**
* @param $object
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
protected function commonBalanceInPeriodForList($object, Carbon $start, Carbon $end, Collection $accounts)
{
$cache = new CacheProperties; // we must cache this.
$cache->addProperty($object->id);
$cache->addProperty(get_class($object));
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty('balanceInPeriodList');
$ids = [];
/** @var Account $account */
foreach ($accounts as $account) {
$ids[] = $account->id;
}
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$sum = $object->transactionjournals()
->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE])
->before($end)
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->whereIn('accounts.id', $ids)
->after($start)
->get(['transaction_journals.*'])->sum('amount');
$cache->store($sum);
return $sum;
}
} }

View File

@ -77,7 +77,7 @@ class TagRepository implements TagRepositoryInterface
/** @var Tag $tag */ /** @var Tag $tag */
foreach ($tags as $tag) { foreach ($tags as $tag) {
$journals = $tag->transactionjournals()->after($start)->before($end)->transactionTypes(['Transfer'])->get(['transaction_journals.*']); $journals = $tag->transactionjournals()->after($start)->before($end)->transactionTypes([TransactionType::TRANSFER])->get(['transaction_journals.*']);
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
foreach ($journals as $journal) { foreach ($journals as $journal) {
@ -158,7 +158,7 @@ class TagRepository implements TagRepositoryInterface
if ($tag->tagMode == 'balancingAct' || $tag->tagMode == 'nothing') { if ($tag->tagMode == 'balancingAct' || $tag->tagMode == 'nothing') {
foreach ($tag->transactionjournals as $journal) { foreach ($tag->transactionjournals as $journal) {
if ($journal->transactionType->type == 'Transfer') { if ($journal->isTransfer()) {
return false; return false;
} }
} }
@ -169,7 +169,7 @@ class TagRepository implements TagRepositoryInterface
*/ */
$count = 0; $count = 0;
foreach ($tag->transactionjournals as $journal) { foreach ($tag->transactionjournals as $journal) {
if ($journal->transactionType->type == 'Withdrawal') { if ($journal->isWithdrawal()) {
$count++; $count++;
} }
} }
@ -201,7 +201,7 @@ class TagRepository implements TagRepositoryInterface
* If any transaction is a deposit, cannot become a balancing act. * If any transaction is a deposit, cannot become a balancing act.
*/ */
foreach ($tag->transactionjournals as $journal) { foreach ($tag->transactionjournals as $journal) {
if ($journal->transactionType->type == 'Deposit') { if ($journal->isDeposit()) {
return false; return false;
} }
} }
@ -239,10 +239,10 @@ class TagRepository implements TagRepositoryInterface
protected function connectBalancingAct(TransactionJournal $journal, Tag $tag) protected function connectBalancingAct(TransactionJournal $journal, Tag $tag)
{ {
/** @var TransactionType $withdrawal */ /** @var TransactionType $withdrawal */
$withdrawal = TransactionType::whereType('Withdrawal')->first(); $withdrawal = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
$withdrawals = $tag->transactionjournals()->where('transaction_type_id', $withdrawal->id)->count(); $withdrawals = $tag->transactionjournals()->where('transaction_type_id', $withdrawal->id)->count();
/** @var TransactionType $transfer */ /** @var TransactionType $transfer */
$transfer = TransactionType::whereType('Transfer')->first(); $transfer = TransactionType::whereType(TransactionType::TRANSFER)->first();
$transfers = $tag->transactionjournals()->where('transaction_type_id', $transfer->id)->count(); $transfers = $tag->transactionjournals()->where('transaction_type_id', $transfer->id)->count();
@ -275,11 +275,11 @@ class TagRepository implements TagRepositoryInterface
protected function connectAdvancePayment(TransactionJournal $journal, Tag $tag) protected function connectAdvancePayment(TransactionJournal $journal, Tag $tag)
{ {
/** @var TransactionType $transfer */ /** @var TransactionType $transfer */
$transfer = TransactionType::whereType('Transfer')->first(); $transfer = TransactionType::whereType(TransactionType::TRANSFER)->first();
/** @var TransactionType $withdrawal */ /** @var TransactionType $withdrawal */
$withdrawal = TransactionType::whereType('Withdrawal')->first(); $withdrawal = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
/** @var TransactionType $deposit */ /** @var TransactionType $deposit */
$deposit = TransactionType::whereType('Deposit')->first(); $deposit = TransactionType::whereType(TransactionType::DEPOSIT)->first();
$withdrawals = $tag->transactionjournals()->where('transaction_type_id', $withdrawal->id)->count(); $withdrawals = $tag->transactionjournals()->where('transaction_type_id', $withdrawal->id)->count();
$deposits = $tag->transactionjournals()->where('transaction_type_id', $deposit->id)->count(); $deposits = $tag->transactionjournals()->where('transaction_type_id', $deposit->id)->count();
@ -326,10 +326,10 @@ class TagRepository implements TagRepositoryInterface
foreach ($tag->transactionjournals as $check) { foreach ($tag->transactionjournals as $check) {
// $checkAccount is the source_account for a withdrawal // $checkAccount is the source_account for a withdrawal
// $checkAccount is the destination_account for a deposit // $checkAccount is the destination_account for a deposit
if ($check->transactionType->type == 'Withdrawal' && $check->source_account->id != $journal->destination_account->id) { if ($check->isWithdrawal() && $check->source_account->id != $journal->destination_account->id) {
$match = false; $match = false;
} }
if ($check->transactionType->type == 'Deposit' && $check->destination_account->id != $journal->destination_account->id) { if ($check->isDeposit() && $check->destination_account->id != $journal->destination_account->id) {
$match = false; $match = false;
} }

View File

@ -40,7 +40,7 @@ class Amount
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get();
} else { } else {
$currencyPreference = Prefs::get('currencyPreference', 'EUR'); $currencyPreference = Prefs::get('currencyPreference', env('DEFAULT_CURRENCY','EUR'));
$currency = TransactionCurrency::whereCode($currencyPreference->data)->first(); $currency = TransactionCurrency::whereCode($currencyPreference->data)->first();
$cache->store($currency->symbol); $cache->store($currency->symbol);
@ -60,7 +60,7 @@ class Amount
{ {
$amount = floatval($amount); $amount = floatval($amount);
$amount = round($amount, 2); $amount = round($amount, 2);
$string = number_format($amount, 2, ',', '.'); $string = money_format('%!.2n', $amount);
if ($coloured === true) { if ($coloured === true) {
if ($amount === 0.0) { if ($amount === 0.0) {
@ -100,13 +100,13 @@ class Amount
$symbol = $journal->symbol; $symbol = $journal->symbol;
} }
if ($journal->transactionType->type == 'Transfer' && $coloured) { if ($journal->isTransfer() && $coloured) {
$txt = '<span class="text-info">' . $this->formatWithSymbol($symbol, $journal->amount_positive, false) . '</span>'; $txt = '<span class="text-info">' . $this->formatWithSymbol($symbol, $journal->amount_positive, false) . '</span>';
$cache->store($txt); $cache->store($txt);
return $txt; return $txt;
} }
if ($journal->transactionType->type == 'Transfer' && !$coloured) { if ($journal->isTransfer() && !$coloured) {
$txt = $this->formatWithSymbol($symbol, $journal->amount_positive, false); $txt = $this->formatWithSymbol($symbol, $journal->amount_positive, false);
$cache->store($txt); $cache->store($txt);
@ -152,7 +152,7 @@ class Amount
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get();
} else { } else {
$currencyPreference = Prefs::get('currencyPreference', 'EUR'); $currencyPreference = Prefs::get('currencyPreference', env('DEFAULT_CURRENCY','EUR'));
$currency = TransactionCurrency::whereCode($currencyPreference->data)->first(); $currency = TransactionCurrency::whereCode($currencyPreference->data)->first();
if ($currency) { if ($currency) {
@ -161,9 +161,9 @@ class Amount
return $currency->code; return $currency->code;
} }
$cache->store('EUR'); $cache->store(env('DEFAULT_CURRENCY','EUR'));
return 'EUR'; // @codeCoverageIgnore return env('DEFAULT_CURRENCY','EUR'); // @codeCoverageIgnore
} }
} }

View File

@ -23,10 +23,10 @@ class Budget extends Twig_Extension
{ {
$functions = []; $functions = [];
$functions[] = new Twig_SimpleFunction( $functions[] = new Twig_SimpleFunction(
'spentInRepetitionCorrected', function (LimitRepetition $repetition) { 'spentInRepetition', function (LimitRepetition $repetition) {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($repetition->id); $cache->addProperty($repetition->id);
$cache->addProperty('spentInRepetitionCorrected'); $cache->addProperty('spentInRepetition');
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }

View File

@ -68,19 +68,17 @@ class Journal extends Twig_Extension
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$type = $journal->transactionType->type; switch (true) {
case $journal->isWithdrawal():
switch ($type) {
case 'Withdrawal':
$txt = '<i class="fa fa-long-arrow-left fa-fw" title="' . trans('firefly.withdrawal') . '"></i>'; $txt = '<i class="fa fa-long-arrow-left fa-fw" title="' . trans('firefly.withdrawal') . '"></i>';
break; break;
case 'Deposit': case $journal->isDeposit():
$txt = '<i class="fa fa-long-arrow-right fa-fw" title="' . trans('firefly.deposit') . '"></i>'; $txt = '<i class="fa fa-long-arrow-right fa-fw" title="' . trans('firefly.deposit') . '"></i>';
break; break;
case 'Transfer': case $journal->isTransfer():
$txt = '<i class="fa fa-fw fa-exchange" title="' . trans('firefly.transfer') . '"></i>'; $txt = '<i class="fa fa-fw fa-exchange" title="' . trans('firefly.transfer') . '"></i>';
break; break;
case 'Opening balance': case $journal->isOpeningBalance():
$txt = '<i class="fa-fw fa fa-ban" title="' . trans('firefly.openingBalance') . '"></i>'; $txt = '<i class="fa-fw fa fa-ban" title="' . trans('firefly.openingBalance') . '"></i>';
break; break;
default: default:
@ -189,7 +187,7 @@ class Journal extends Twig_Extension
} }
if ($tag->tagMode == 'advancePayment') { if ($tag->tagMode == 'advancePayment') {
if ($journal->transactionType->type == 'Deposit') { if ($journal->isDeposit()) {
$amount = app('amount')->formatJournal($journal, false); $amount = app('amount')->formatJournal($journal, false);
$string = '<a href="' . route('tags.show', [$tag->id]) . '" class="label label-success" title="' . $amount $string = '<a href="' . route('tags.show', [$tag->id]) . '" class="label label-success" title="' . $amount
. '"><i class="fa fa-fw fa-sort-numeric-desc"></i> ' . $tag->tag . '</a>'; . '"><i class="fa fa-fw fa-sort-numeric-desc"></i> ' . $tag->tag . '</a>';
@ -201,7 +199,7 @@ class Journal extends Twig_Extension
* AdvancePayment with a withdrawal will show the amount with a link to * AdvancePayment with a withdrawal will show the amount with a link to
* the tag. The TransactionJournal should properly calculate the amount. * the tag. The TransactionJournal should properly calculate the amount.
*/ */
if ($journal->transactionType->type == 'Withdrawal') { if ($journal->isWithdrawal()) {
$amount = app('amount')->formatJournal($journal); $amount = app('amount')->formatJournal($journal);
$string = '<a href="' . route('tags.show', [$tag->id]) . '">' . $amount . '</a>'; $string = '<a href="' . route('tags.show', [$tag->id]) . '">' . $amount . '</a>';

737
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -139,8 +139,8 @@ return [
'TwigBridge\ServiceProvider', 'TwigBridge\ServiceProvider',
'DaveJamesMiller\Breadcrumbs\ServiceProvider', 'DaveJamesMiller\Breadcrumbs\ServiceProvider',
//'Barryvdh\Debugbar\ServiceProvider', // 'Barryvdh\Debugbar\ServiceProvider',
//'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider', // 'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
'Zizaco\Entrust\EntrustServiceProvider', 'Zizaco\Entrust\EntrustServiceProvider',
/* /*

View File

@ -49,7 +49,7 @@ return [
'sqlite' => [ 'sqlite' => [
'driver' => 'sqlite', 'driver' => 'sqlite',
'database' => __DIR__ . '/../storage/database/testing.db', 'database' => storage_path('database/testing.db'),
'prefix' => '', 'prefix' => '',
], ],

View File

@ -2,7 +2,7 @@
return [ return [
'chart' => 'chartjs', 'chart' => 'chartjs',
'version' => '3.5.3', 'version' => '3.5.4',
'index_periods' => ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom'], 'index_periods' => ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom'],
'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], 'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
'csv_import_enabled' => true, 'csv_import_enabled' => true,

View File

@ -15,7 +15,8 @@ return [
| |
*/ */
'driver' => env('EMAIL_DRIVER', 'smtp'), 'blocked_domains' => explode(',', env('BLOCKED_DOMAINS')),
'driver' => env('EMAIL_DRIVER', 'smtp'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -28,7 +29,7 @@ return [
| |
*/ */
'host' => env('EMAIL_SMTP', 'smtp.mailgun.org'), 'host' => env('EMAIL_SMTP', 'smtp.mailgun.org'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -41,7 +42,7 @@ return [
| |
*/ */
'port' => 587, 'port' => 587,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -54,7 +55,7 @@ return [
| |
*/ */
'from' => ['address' => env('EMAIL_USERNAME', null), 'name' => 'Firefly III Mailer'], 'from' => ['address' => env('EMAIL_USERNAME', null), 'name' => 'Firefly III Mailer'],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -80,7 +81,7 @@ return [
| |
*/ */
'username' => env('EMAIL_USERNAME', null), 'username' => env('EMAIL_USERNAME', null),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -93,7 +94,7 @@ return [
| |
*/ */
'password' => env('EMAIL_PASSWORD', null), 'password' => env('EMAIL_PASSWORD', null),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -106,7 +107,7 @@ return [
| |
*/ */
'sendmail' => '/usr/sbin/sendmail -bs', 'sendmail' => '/usr/sbin/sendmail -bs',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -119,6 +120,6 @@ return [
| |
*/ */
'pretend' => env('EMAIL_PRETEND', false), 'pretend' => env('EMAIL_PRETEND', false),
]; ];

View File

@ -49,7 +49,7 @@ class TestDataSeeder extends Seeder
$this->createPiggybanks(); $this->createPiggybanks();
// dates: // dates:
$start = Carbon::now()->subyear()->startOfMonth(); $start = Carbon::now()->subYears(5)->startOfMonth();
$end = Carbon::now()->endOfDay(); $end = Carbon::now()->endOfDay();

View File

@ -12,10 +12,10 @@ class TransactionTypeSeeder extends Seeder
DB::table('transaction_types')->delete(); DB::table('transaction_types')->delete();
TransactionType::create(['type' => 'Withdrawal']); TransactionType::create(['type' => TransactionType::WITHDRAWAL]);
TransactionType::create(['type' => 'Deposit']); TransactionType::create(['type' => TransactionType::DEPOSIT]);
TransactionType::create(['type' => 'Transfer']); TransactionType::create(['type' => TransactionType::TRANSFER]);
TransactionType::create(['type' => 'Opening balance']); TransactionType::create(['type' => TransactionType::OPENING_BALANCE]);
} }
} }

4
public/js/accounting.min.js vendored Normal file
View File

@ -0,0 +1,4 @@
/*!
* accounting.js v0.4.2, copyright 2014 Open Exchange Rates, MIT license, http://openexchangerates.github.io/accounting.js
*/
(function(p,z){function q(a){return!!(""===a||a&&a.charCodeAt&&a.substr)}function m(a){return u?u(a):"[object Array]"===v.call(a)}function r(a){return"[object Object]"===v.call(a)}function s(a,b){var d,a=a||{},b=b||{};for(d in b)b.hasOwnProperty(d)&&null==a[d]&&(a[d]=b[d]);return a}function j(a,b,d){var c=[],e,h;if(!a)return c;if(w&&a.map===w)return a.map(b,d);for(e=0,h=a.length;e<h;e++)c[e]=b.call(d,a[e],e,a);return c}function n(a,b){a=Math.round(Math.abs(a));return isNaN(a)?b:a}function x(a){var b=c.settings.currency.format;"function"===typeof a&&(a=a());return q(a)&&a.match("%v")?{pos:a,neg:a.replace("-","").replace("%v","-%v"),zero:a}:!a||!a.pos||!a.pos.match("%v")?!q(b)?b:c.settings.currency.format={pos:b,neg:b.replace("%v","-%v"),zero:b}:a}var c={version:"0.4.1",settings:{currency:{symbol:"$",format:"%s%v",decimal:".",thousand:",",precision:2,grouping:3},number:{precision:0,grouping:3,thousand:",",decimal:"."}}},w=Array.prototype.map,u=Array.isArray,v=Object.prototype.toString,o=c.unformat=c.parse=function(a,b){if(m(a))return j(a,function(a){return o(a,b)});a=a||0;if("number"===typeof a)return a;var b=b||".",c=RegExp("[^0-9-"+b+"]",["g"]),c=parseFloat((""+a).replace(/\((.*)\)/,"-$1").replace(c,"").replace(b,"."));return!isNaN(c)?c:0},y=c.toFixed=function(a,b){var b=n(b,c.settings.number.precision),d=Math.pow(10,b);return(Math.round(c.unformat(a)*d)/d).toFixed(b)},t=c.formatNumber=c.format=function(a,b,d,i){if(m(a))return j(a,function(a){return t(a,b,d,i)});var a=o(a),e=s(r(b)?b:{precision:b,thousand:d,decimal:i},c.settings.number),h=n(e.precision),f=0>a?"-":"",g=parseInt(y(Math.abs(a||0),h),10)+"",l=3<g.length?g.length%3:0;return f+(l?g.substr(0,l)+e.thousand:"")+g.substr(l).replace(/(\d{3})(?=\d)/g,"$1"+e.thousand)+(h?e.decimal+y(Math.abs(a),h).split(".")[1]:"")},A=c.formatMoney=function(a,b,d,i,e,h){if(m(a))return j(a,function(a){return A(a,b,d,i,e,h)});var a=o(a),f=s(r(b)?b:{symbol:b,precision:d,thousand:i,decimal:e,format:h},c.settings.currency),g=x(f.format);return(0<a?g.pos:0>a?g.neg:g.zero).replace("%s",f.symbol).replace("%v",t(Math.abs(a),n(f.precision),f.thousand,f.decimal))};c.formatColumn=function(a,b,d,i,e,h){if(!a)return[];var f=s(r(b)?b:{symbol:b,precision:d,thousand:i,decimal:e,format:h},c.settings.currency),g=x(f.format),l=g.pos.indexOf("%s")<g.pos.indexOf("%v")?!0:!1,k=0,a=j(a,function(a){if(m(a))return c.formatColumn(a,f);a=o(a);a=(0<a?g.pos:0>a?g.neg:g.zero).replace("%s",f.symbol).replace("%v",t(Math.abs(a),n(f.precision),f.thousand,f.decimal));if(a.length>k)k=a.length;return a});return j(a,function(a){return q(a)&&a.length<k?l?a.replace(f.symbol,f.symbol+Array(k-a.length+1).join(" ")):Array(k-a.length+1).join(" ")+a:a})};if("undefined"!==typeof exports){if("undefined"!==typeof module&&module.exports)exports=module.exports=c;exports.accounting=c}else"function"===typeof define&&define.amd?define([],function(){return c}):(c.noConflict=function(a){return function(){p.accounting=a;c.noConflict=z;return c}}(p.accounting),p.accounting=c)})(this);

View File

@ -24,6 +24,23 @@ var colourSet = [
]; ];
// Settings object that controls default parameters for library methods:
accounting.settings = {
currency: {
symbol : currencySymbol, // default currency symbol is '$'
format: "%s %v", // controls output: %s = symbol, %v = value/number (can be object: see below)
decimal : ",", // decimal point separator
thousand: ".", // thousands separator
precision : 2 // decimal places
},
number: {
precision : 0, // default precision on numbers is 0
thousand: ",",
decimal : "."
}
}
var fillColors = []; var fillColors = [];
var strokePointHighColors = []; var strokePointHighColors = [];
@ -45,9 +62,9 @@ var defaultAreaOptions = {
animation: false, animation: false,
scaleFontSize: 10, scaleFontSize: 10,
responsive: false, responsive: false,
scaleLabel: " <%= '" + currencySymbol + " ' + Number(value).toFixed(0).replace('.', ',') %>", scaleLabel: " <%= accounting.formatMoney(value) %>",
tooltipFillColor: "rgba(0,0,0,0.5)", tooltipFillColor: "rgba(0,0,0,0.5)",
multiTooltipTemplate: "<%=datasetLabel%>: <%= '" + currencySymbol + " ' + Number(value).toFixed(2).replace('.', ',') %>" multiTooltipTemplate: "<%=datasetLabel%>: <%= accounting.formatMoney(value) %>"
}; };
@ -61,7 +78,7 @@ var defaultPieOptions = {
scaleFontSize: 10, scaleFontSize: 10,
responsive: false, responsive: false,
tooltipFillColor: "rgba(0,0,0,0.5)", tooltipFillColor: "rgba(0,0,0,0.5)",
tooltipTemplate: "<%if (label){%><%=label%>: <%}%>" + currencySymbol + " <%= value %>", tooltipTemplate: "<%if (label){%><%=label%>: <%}%> <%= accounting.formatMoney(value) %>",
}; };
@ -90,10 +107,10 @@ var defaultColumnOptions = {
scaleFontSize: 10, scaleFontSize: 10,
responsive: false, responsive: false,
animation: false, animation: false,
scaleLabel: "<%= '" + currencySymbol + " ' + Number(value).toFixed(0).replace('.', ',') %>", scaleLabel: "<%= accounting.formatMoney(value) %>",
tooltipFillColor: "rgba(0,0,0,0.5)", tooltipFillColor: "rgba(0,0,0,0.5)",
tooltipTemplate: "<%if (label){%><%=label%>: <%}%>" + currencySymbol + " <%= value %>", tooltipTemplate: "<%if (label){%><%=label%>: <%}%> <%= accounting.formatMoney(value) %>",
multiTooltipTemplate: "<%=datasetLabel%>: " + currencySymbol + " <%= Number(value).toFixed(2).replace('.', ',') %>" multiTooltipTemplate: "<%=datasetLabel%>: <%= accounting.formatMoney(value) %>"
}; };
var defaultStackedColumnOptions = { var defaultStackedColumnOptions = {
@ -105,9 +122,9 @@ var defaultStackedColumnOptions = {
animation: false, animation: false,
scaleFontSize: 10, scaleFontSize: 10,
responsive: false, responsive: false,
scaleLabel: "<%= '" + currencySymbol + " ' + Number(value).toFixed(0).replace('.', ',') %>", scaleLabel: "<%= accounting.formatMoney(value) %>",
tooltipFillColor: "rgba(0,0,0,0.5)", tooltipFillColor: "rgba(0,0,0,0.5)",
multiTooltipTemplate: "<%=datasetLabel%>: " + currencySymbol + " <%= Number(value).toFixed(2).replace('.', ',') %>" multiTooltipTemplate: "<%=datasetLabel%>: <%= accounting.formatMoney(value) %>"
}; };

View File

@ -1,93 +0,0 @@
/* globals google, expenseRestShow:true, incomeRestShow:true, year, shared, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
$(function () {
"use strict";
drawChart();
});
function drawChart() {
"use strict";
if (typeof columnChart !== 'undefined' && typeof year !== 'undefined' && typeof month === 'undefined') {
columnChart('chart/report/in-out/' + year + shared, 'income-expenses-chart');
columnChart('chart/report/in-out-sum/' + year + shared, 'income-expenses-sum-chart');
}
if (typeof stackedColumnChart !== 'undefined' && typeof year !== 'undefined' && typeof month === 'undefined') {
stackedColumnChart('chart/budget/year/' + year + shared, 'budgets');
stackedColumnChart('chart/category/spent-in-year/' + year + shared, 'categories-spent-in-year');
stackedColumnChart('chart/category/earned-in-year/' + year + shared, 'categories-earned-in-year');
}
if (typeof lineChart !== 'undefined' && typeof month !== 'undefined') {
lineChart('/chart/account/month/' + year + '/' + month + shared, 'account-balances-chart');
}
}
function openModal(e) {
"use strict";
var target = $(e.target).parent();
var URL = target.attr('href');
$.get(URL).success(function (data) {
$('#defaultModal').empty().html(data).modal('show');
}).fail(function () {
alert('Could not load data.');
});
return false;
}
function showIncomes() {
"use strict";
if (incomeRestShow) {
// hide everything, make button say "show"
$('#showIncomes').text(showTheRest);
$('.incomesCollapsed').removeClass('in').addClass('out');
// toggle:
incomeRestShow = false;
} else {
// show everything, make button say "hide".
$('#showIncomes').text(hideTheRest);
$('.incomesCollapsed').removeClass('out').addClass('in');
// toggle:
incomeRestShow = true;
}
return false;
}
function showExpenses() {
"use strict";
if (expenseRestShow) {
// hide everything, make button say "show"
$('#showExpenses').text(showTheRestExpense);
$('.expenseCollapsed').removeClass('in').addClass('out');
// toggle:
expenseRestShow = false;
} else {
// show everything, make button say "hide".
$('#showExpenses').text(hideTheRestExpense);
$('.expenseCollapsed').removeClass('out').addClass('in');
// toggle:
expenseRestShow = true;
}
return false;
}
$(function () {
"use strict";
$('.openModal').on('click', openModal);
// click open the top X income list:
$('#showIncomes').click(showIncomes);
// click open the top X expense list:
$('#showExpenses').click(showExpenses);
});

View File

@ -0,0 +1,64 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
$(function () {
"use strict";
drawChart();
// click open the top X income list:
$('#showIncomes').click(showIncomes);
// click open the top X expense list:
$('#showExpenses').click(showExpenses);
});
function drawChart() {
"use strict";
// month view:
// draw account chart
lineChart('/chart/account/report/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'account-balances-chart');
}
function showIncomes() {
"use strict";
if (incomeRestShow) {
// hide everything, make button say "show"
$('#showIncomes').text(showTheRest);
$('.incomesCollapsed').removeClass('in').addClass('out');
// toggle:
incomeRestShow = false;
} else {
// show everything, make button say "hide".
$('#showIncomes').text(hideTheRest);
$('.incomesCollapsed').removeClass('out').addClass('in');
// toggle:
incomeRestShow = true;
}
return false;
}
function showExpenses() {
"use strict";
if (expenseRestShow) {
// hide everything, make button say "show"
$('#showExpenses').text(showTheRestExpense);
$('.expenseCollapsed').removeClass('in').addClass('out');
// toggle:
expenseRestShow = false;
} else {
// show everything, make button say "hide".
$('#showExpenses').text(hideTheRestExpense);
$('.expenseCollapsed').removeClass('out').addClass('in');
// toggle:
expenseRestShow = true;
}
return false;
}

View File

@ -0,0 +1,155 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
$(function () {
"use strict";
drawChart();
});
function drawChart() {
"use strict";
// income and expense over multi year:
columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart');
columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart');
$.each($('.account-chart'), function (i, v) {
var holder = $(v);
console.log('Will draw chart for account #' + holder.data('id'));
});
// draw budget chart based on selected budgets:
$('.budget-checkbox').on('change', updateBudgetChart);
selectBudgetsByCookie();
updateBudgetChart();
// draw category chart based on selected budgets:
$('.category-checkbox').on('change', updateCategoryChart);
selectCategoriesByCookie();
updateCategoryChart();
}
function selectBudgetsByCookie() {
"use strict";
var cookie = readCookie('multi-year-budgets');
if (cookie !== null) {
var cookieArray = cookie.split(',');
for (var x in cookieArray) {
var budgetId = cookieArray[x];
$('.budget-checkbox[value="' + budgetId + '"').prop('checked', true);
}
}
}
function selectCategoriesByCookie() {
"use strict";
var cookie = readCookie('multi-year-categories');
if (cookie !== null) {
var cookieArray = cookie.split(',');
for (var x in cookieArray) {
var categoryId = cookieArray[x];
$('.category-checkbox[value="' + categoryId + '"').prop('checked', true);
}
}
}
function updateBudgetChart() {
"use strict";
console.log('will update budget chart.');
// get all budget ids:
var budgets = [];
$.each($('.budget-checkbox'), function (i, v) {
var current = $(v);
if (current.prop('checked')) {
budgets.push(current.val());
}
});
if (budgets.length > 0) {
var budgetIds = budgets.join(',');
// remove old chart:
$('#budgets-chart').replaceWith('<canvas id="budgets-chart" class="budgets-chart" style="width:100%;height:400px;"></canvas>');
// hide message:
$('#budgets-chart-message').hide();
// draw chart. Redraw when exists? Not sure if we support that.
columnChart('chart/budget/multi-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds + '/' + budgetIds, 'budgets-chart');
createCookie('multi-year-budgets', budgets, 365);
} else {
// hide canvas, show message:
$('#budgets-chart-message').show();
$('#budgets-chart').hide();
}
}
function updateCategoryChart() {
"use strict";
console.log('will update category chart.');
// get all category ids:
var categories = [];
$.each($('.category-checkbox'), function (i, v) {
var current = $(v);
if (current.prop('checked')) {
categories.push(current.val());
}
});
if (categories.length > 0) {
var categoryIds = categories.join(',');
// remove old chart:
$('#categories-chart').replaceWith('<canvas id="categories-chart" class="budgets-chart" style="width:100%;height:400px;"></canvas>');
// hide message:
$('#categories-chart-message').hide();
// draw chart. Redraw when exists? Not sure if we support that.
columnChart('chart/category/multi-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds + '/' + categoryIds, 'categories-chart');
createCookie('multi-year-categories', categories, 365);
} else {
// hide canvas, show message:
$('#categories-chart-message').show();
$('#categories-chart').hide();
}
}
function createCookie(name, value, days) {
"use strict";
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
} else {
expires = "";
}
document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
}
function readCookie(name) {
"use strict";
var nameEQ = encodeURIComponent(name) + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
return null;
}
function eraseCookie(name) {
createCookie(name, "", -1);
}

View File

@ -0,0 +1,212 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
$(function () {
"use strict";
drawChart();
if ($('#inputDateRange').length > 0) {
picker = $('#inputDateRange').daterangepicker(
{
locale: {
format: 'YYYY-MM-DD',
firstDay: 1,
},
minDate: minDate,
drops: 'up',
}
);
// set values from cookies, if any:
if (readCookie('report-type') !== null) {
$('select[name="report_type"]').val(readCookie('report-type'));
}
if ((readCookie('report-accounts') !== null)) {
var arr = readCookie('report-accounts').split(',');
arr.forEach(function (val) {
$('input[type="checkbox"][value="' + val + '"]').prop('checked', true);
});
}
// set date:
var startStr = readCookie('report-start');
var endStr = readCookie('report-end');
if (startStr !== null && endStr !== null && startStr.length == 8 && endStr.length == 8) {
var startDate = moment(startStr, "YYYYMMDD");
var endDate = moment(endStr, "YYYYMMDD");
var datePicker = $('#inputDateRange').data('daterangepicker');
datePicker.setStartDate(startDate);
datePicker.setEndDate(endDate);
}
}
$('.openModal').on('click', openModal);
$('.date-select').on('click', preSelectDate);
$('#report-form').on('submit', catchSubmit);
// click open the top X income list:
$('#showIncomes').click(showIncomes);
// click open the top X expense list:
$('#showExpenses').click(showExpenses);
});
function catchSubmit() {
"use strict";
// default;20141201;20141231;4;5
// report name:
var url = '' + $('select[name="report_type"]').val() + '/';
// date, processed:
var picker = $('#inputDateRange').data('daterangepicker');
url += moment(picker.startDate).format("YYYYMMDD") + '/';
url += moment(picker.endDate).format("YYYYMMDD") + '/';
// all account ids:
var count = 0;
var accounts = [];
$.each($('.account-checkbox'), function (i, v) {
var c = $(v);
if (c.prop('checked')) {
url += c.val() + ';';
accounts.push(c.val());
count++;
}
});
if (count > 0) {
// set cookie to remember choices.
createCookie('report-type', $('select[name="report_type"]').val(), 365);
createCookie('report-accounts', accounts, 365);
createCookie('report-start', moment(picker.startDate).format("YYYYMMDD"), 365);
createCookie('report-end', moment(picker.endDate).format("YYYYMMDD"), 365);
window.location.href = reportURL + "/" + url;
}
//console.log(url);
return false;
}
function preSelectDate(e) {
"use strict";
var link = $(e.target);
var picker = $('#inputDateRange').data('daterangepicker');
picker.setStartDate(link.data('start'));
picker.setEndDate(link.data('end'));
return false;
}
function drawChart() {
"use strict";
if (typeof columnChart !== 'undefined' && typeof year !== 'undefined' && typeof month === 'undefined') {
columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart');
columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart');
}
if (typeof stackedColumnChart !== 'undefined' && typeof year !== 'undefined' && typeof month === 'undefined') {
stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets');
stackedColumnChart('chart/category/spent-in-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-spent-in-year');
stackedColumnChart('chart/category/earned-in-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-earned-in-year');
}
//if (typeof lineChart !== 'undefined' && typeof month !== 'undefined' && typeof reportURL === 'undefined') {
// lineChart('/chart/account/month/' + year + '/' + month + shared, 'account-balances-chart');
//}
if (typeof lineChart !== 'undefined' && typeof accountIds !== 'undefined') {
lineChart('/chart/account/report/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'account-balances-chart');
}
}
function openModal(e) {
"use strict";
var target = $(e.target).parent();
var URL = target.attr('href');
$.get(URL).success(function (data) {
$('#defaultModal').empty().html(data).modal('show');
}).fail(function () {
alert('Could not load data.');
});
return false;
}
function showIncomes() {
"use strict";
if (incomeRestShow) {
// hide everything, make button say "show"
$('#showIncomes').text(showTheRest);
$('.incomesCollapsed').removeClass('in').addClass('out');
// toggle:
incomeRestShow = false;
} else {
// show everything, make button say "hide".
$('#showIncomes').text(hideTheRest);
$('.incomesCollapsed').removeClass('out').addClass('in');
// toggle:
incomeRestShow = true;
}
return false;
}
function showExpenses() {
"use strict";
if (expenseRestShow) {
// hide everything, make button say "show"
$('#showExpenses').text(showTheRestExpense);
$('.expenseCollapsed').removeClass('in').addClass('out');
// toggle:
expenseRestShow = false;
} else {
// show everything, make button say "hide".
$('#showExpenses').text(hideTheRestExpense);
$('.expenseCollapsed').removeClass('out').addClass('in');
// toggle:
expenseRestShow = true;
}
return false;
}
function createCookie(name, value, days) {
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
} else {
expires = "";
}
document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
}
function readCookie(name) {
var nameEQ = encodeURIComponent(name) + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
return null;
}
function eraseCookie(name) {
createCookie(name, "", -1);
}

View File

@ -0,0 +1,67 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
$(function () {
"use strict";
drawChart();
// click open the top X income list:
$('#showIncomes').click(showIncomes);
// click open the top X expense list:
$('#showExpenses').click(showExpenses);
});
function drawChart() {
"use strict";
columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart');
columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart');
stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets');
stackedColumnChart('chart/category/spent-in-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-spent-in-year');
stackedColumnChart('chart/category/earned-in-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-earned-in-year');
}
function showIncomes() {
"use strict";
if (incomeRestShow) {
// hide everything, make button say "show"
$('#showIncomes').text(showTheRest);
$('.incomesCollapsed').removeClass('in').addClass('out');
// toggle:
incomeRestShow = false;
} else {
// show everything, make button say "hide".
$('#showIncomes').text(hideTheRest);
$('.incomesCollapsed').removeClass('out').addClass('in');
// toggle:
incomeRestShow = true;
}
return false;
}
function showExpenses() {
"use strict";
if (expenseRestShow) {
// hide everything, make button say "show"
$('#showExpenses').text(showTheRestExpense);
$('.expenseCollapsed').removeClass('in').addClass('out');
// toggle:
expenseRestShow = false;
} else {
// show everything, make button say "hide".
$('#showExpenses').text(hideTheRestExpense);
$('.expenseCollapsed').removeClass('out').addClass('in');
// toggle:
expenseRestShow = true;
}
return false;
}

122
public/js/reports/index.js Normal file
View File

@ -0,0 +1,122 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
$(function () {
"use strict";
if ($('#inputDateRange').length > 0) {
picker = $('#inputDateRange').daterangepicker(
{
locale: {
format: 'YYYY-MM-DD',
firstDay: 1,
},
minDate: minDate,
drops: 'up',
}
);
// set values from cookies, if any:
if (readCookie('report-type') !== null) {
$('select[name="report_type"]').val(readCookie('report-type'));
}
if ((readCookie('report-accounts') !== null)) {
var arr = readCookie('report-accounts').split(',');
arr.forEach(function (val) {
$('input[type="checkbox"][value="' + val + '"]').prop('checked', true);
});
}
// set date:
var startStr = readCookie('report-start');
var endStr = readCookie('report-end');
if (startStr !== null && endStr !== null && startStr.length == 8 && endStr.length == 8) {
var startDate = moment(startStr, "YYYYMMDD");
var endDate = moment(endStr, "YYYYMMDD");
var datePicker = $('#inputDateRange').data('daterangepicker');
datePicker.setStartDate(startDate);
datePicker.setEndDate(endDate);
}
}
$('.date-select').on('click', preSelectDate);
$('#report-form').on('submit', catchSubmit);
});
function catchSubmit() {
"use strict";
// default;20141201;20141231;4;5
// report name:
var url = '' + $('select[name="report_type"]').val() + '/';
// date, processed:
var picker = $('#inputDateRange').data('daterangepicker');
url += moment(picker.startDate).format("YYYYMMDD") + '/';
url += moment(picker.endDate).format("YYYYMMDD") + '/';
// all account ids:
var count = 0;
var accounts = [];
$.each($('.account-checkbox'), function (i, v) {
var c = $(v);
if (c.prop('checked')) {
url += c.val() + ',';
accounts.push(c.val());
count++;
}
});
if (count > 0) {
// set cookie to remember choices.
createCookie('report-type', $('select[name="report_type"]').val(), 365);
createCookie('report-accounts', accounts, 365);
createCookie('report-start', moment(picker.startDate).format("YYYYMMDD"), 365);
createCookie('report-end', moment(picker.endDate).format("YYYYMMDD"), 365);
window.location.href = reportURL + "/" + url;
}
//console.log(url);
return false;
}
function preSelectDate(e) {
"use strict";
var link = $(e.target);
var picker = $('#inputDateRange').data('daterangepicker');
picker.setStartDate(link.data('start'));
picker.setEndDate(link.data('end'));
return false;
}
function createCookie(name, value, days) {
"use strict";
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
} else {
expires = "";
}
document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
}
function readCookie(name) {
"use strict";
var nameEQ = encodeURIComponent(name) + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
return null;
}

View File

@ -250,35 +250,27 @@ return [
'details_for_expense' => 'Details for expense account ":name"', 'details_for_expense' => 'Details for expense account ":name"',
'details_for_revenue' => 'Details for revenue account ":name"', 'details_for_revenue' => 'Details for revenue account ":name"',
'details_for_cash' => 'Details for cash account ":name"', 'details_for_cash' => 'Details for cash account ":name"',
'store_new_asset_account' => 'Store new asset account', 'store_new_asset_account' => 'Store new asset account',
'store_new_expense_account' => 'Store new expense account', 'store_new_expense_account' => 'Store new expense account',
'store_new_revenue_account' => 'Store new revenue account', 'store_new_revenue_account' => 'Store new revenue account',
'edit_asset_account' => 'Edit asset account ":name"', 'edit_asset_account' => 'Edit asset account ":name"',
'edit_expense_account' => 'Edit expense account ":name"', 'edit_expense_account' => 'Edit expense account ":name"',
'edit_revenue_account' => 'Edit revenue account ":name"', 'edit_revenue_account' => 'Edit revenue account ":name"',
'delete_asset_account' => 'Delete asset account ":name"', 'delete_asset_account' => 'Delete asset account ":name"',
'delete_expense_account' => 'Delete expense account ":name"', 'delete_expense_account' => 'Delete expense account ":name"',
'delete_revenue_account' => 'Delete revenue account ":name"', 'delete_revenue_account' => 'Delete revenue account ":name"',
'asset_deleted' => 'Successfully deleted asset account ":name"', 'asset_deleted' => 'Successfully deleted asset account ":name"',
'expense_deleted' => 'Successfully deleted expense account ":name"', 'expense_deleted' => 'Successfully deleted expense account ":name"',
'revenue_deleted' => 'Successfully deleted revenue account ":name"', 'revenue_deleted' => 'Successfully deleted revenue account ":name"',
'update_asset_account' => 'Update asset account', 'update_asset_account' => 'Update asset account',
'update_expense_account' => 'Update expense account', 'update_expense_account' => 'Update expense account',
'update_revenue_account' => 'Update revenue account', 'update_revenue_account' => 'Update revenue account',
'make_new_asset_account' => 'Create a new asset account', 'make_new_asset_account' => 'Create a new asset account',
'make_new_expense_account' => 'Create a new expense account', 'make_new_expense_account' => 'Create a new expense account',
'make_new_revenue_account' => 'Create a new revenue account', 'make_new_revenue_account' => 'Create a new revenue account',
'asset_accounts' => 'Asset accounts', 'asset_accounts' => 'Asset accounts',
'expense_accounts' => 'Expense accounts', 'expense_accounts' => 'Expense accounts',
'revenue_accounts' => 'Revenue accounts', 'revenue_accounts' => 'Revenue accounts',
'accountExtraHelp_asset' => '', 'accountExtraHelp_asset' => '',
'accountExtraHelp_expense' => '', 'accountExtraHelp_expense' => '',
'accountExtraHelp_revenue' => '', 'accountExtraHelp_revenue' => '',
@ -341,6 +333,7 @@ return [
'Default account' => 'Asset account', 'Default account' => 'Asset account',
'Expense account' => 'Expense account', 'Expense account' => 'Expense account',
'Revenue account' => 'Revenue account', 'Revenue account' => 'Revenue account',
'Initial balance account' => 'Initial balance account',
'budgets' => 'Budgets', 'budgets' => 'Budgets',
'tags' => 'Tags', 'tags' => 'Tags',
'reports' => 'Reports', 'reports' => 'Reports',
@ -376,120 +369,133 @@ return [
'profile' => 'Profile', 'profile' => 'Profile',
// reports: // reports:
'reportForYear' => 'Yearly report for :year', 'report_default' => 'Default financial report for :start until :end',
'reportForYearShared' => 'Yearly report for :year (including shared accounts)', 'quick_link_reports' => 'Quick links',
'reportForMonth' => 'Montly report for :month', 'quick_link_default_report' => 'Default financial report',
'reportForMonthShared' => 'Montly report for :month (including shared accounts)', 'report_this_month_quick' => 'Current month, all accounts',
'incomeVsExpenses' => 'Income vs. expenses', 'report_this_year_quick' => 'Current year, all accounts',
'accountBalances' => 'Account balances', 'report_all_time_quick' => 'All-time, all accounts',
'balanceStartOfYear' => 'Balance at start of year', 'reports_can_bookmark' => 'Remember that reports can be bookmarked.',
'balanceEndOfYear' => 'Balance at end of year', 'incomeVsExpenses' => 'Income vs. expenses',
'balanceStartOfMonth' => 'Balance at start of month', 'accountBalances' => 'Account balances',
'balanceEndOfMonth' => 'Balance at end of month', 'balanceStartOfYear' => 'Balance at start of year',
'balanceStart' => 'Balance at start of period', 'balanceEndOfYear' => 'Balance at end of year',
'balanceEnd' => 'Balance at end of period', 'balanceStartOfMonth' => 'Balance at start of month',
'reportsOwnAccounts' => 'Reports for your own accounts', 'balanceEndOfMonth' => 'Balance at end of month',
'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', 'balanceStart' => 'Balance at start of period',
'splitByAccount' => 'Split by account', 'balanceEnd' => 'Balance at end of period',
'balancedByTransfersAndTags' => 'Balanced by transfers and tags', 'reportsOwnAccounts' => 'Reports for your own accounts',
'coveredWithTags' => 'Covered with tags', 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts',
'leftUnbalanced' => 'Left unbalanced', 'splitByAccount' => 'Split by account',
'expectedBalance' => 'Expected balance', 'balancedByTransfersAndTags' => 'Balanced by transfers and tags',
'outsideOfBudgets' => 'Outside of budgets', 'coveredWithTags' => 'Covered with tags',
'leftInBudget' => 'Left in budget', 'leftUnbalanced' => 'Left unbalanced',
'sumOfSums' => 'Sum of sums', 'expectedBalance' => 'Expected balance',
'notCharged' => 'Not charged (yet)', 'outsideOfBudgets' => 'Outside of budgets',
'inactive' => 'Inactive', 'leftInBudget' => 'Left in budget',
'difference' => 'Difference', 'sumOfSums' => 'Sum of sums',
'in' => 'In', 'noCategory' => '(no category)',
'out' => 'Out', 'notCharged' => 'Not charged (yet)',
'topX' => 'top :number', 'inactive' => 'Inactive',
'showTheRest' => 'Show everything', 'difference' => 'Difference',
'hideTheRest' => 'Show only the top :number', 'in' => 'In',
'sum_of_year' => 'Sum of year', 'out' => 'Out',
'average_of_year' => 'Average of year', 'topX' => 'top :number',
'categories_earned_in_year' => 'Categories (by earnings)', 'showTheRest' => 'Show everything',
'categories_spent_in_year' => 'Categories (by spendings)', 'hideTheRest' => 'Show only the top :number',
'sum_of_year' => 'Sum of year',
'sum_of_years' => 'Sum of years',
'average_of_year' => 'Average of year',
'average_of_years' => 'Average of years',
'categories_earned_in_year' => 'Categories (by earnings)',
'categories_spent_in_year' => 'Categories (by spendings)',
'report_type' => 'Report type',
'report_type_default' => 'Default financial report',
'report_included_accounts' => 'Included accounts',
'report_date_range' => 'Date range',
'report_include_help' => 'In all cases, transfers to shared accounts count as expenses, and transfers from shared accounts count as income.',
'report_preset_ranges' => 'Pre-set ranges',
'shared' => 'Shared',
// charts: // charts:
'dayOfMonth' => 'Day of the month', 'dayOfMonth' => 'Day of the month',
'month' => 'Month', 'month' => 'Month',
'budget' => 'Budget', 'budget' => 'Budget',
'spent' => 'Spent', 'spent' => 'Spent',
'earned' => 'Earned', 'earned' => 'Earned',
'overspent' => 'Overspent', 'overspent' => 'Overspent',
'left' => 'Left', 'left' => 'Left',
'noBudget' => '(no budget)', 'noBudget' => '(no budget)',
'maxAmount' => 'Maximum amount', 'maxAmount' => 'Maximum amount',
'minAmount' => 'Minumum amount', 'minAmount' => 'Minumum amount',
'billEntry' => 'Current bill entry', 'billEntry' => 'Current bill entry',
'name' => 'Name', 'name' => 'Name',
'date' => 'Date', 'date' => 'Date',
'paid' => 'Paid', 'paid' => 'Paid',
'unpaid' => 'Unpaid', 'unpaid' => 'Unpaid',
'day' => 'Day', 'day' => 'Day',
'budgeted' => 'Budgeted', 'budgeted' => 'Budgeted',
'period' => 'Period', 'period' => 'Period',
'balance' => 'Balance', 'balance' => 'Balance',
'summary' => 'Summary', 'summary' => 'Summary',
'sum' => 'Sum', 'sum' => 'Sum',
'average' => 'Average', 'average' => 'Average',
'balanceFor' => 'Balance for :name', 'balanceFor' => 'Balance for :name',
// piggy banks: // piggy banks:
'piggy_bank' => 'Piggy bank', 'piggy_bank' => 'Piggy bank',
'new_piggy_bank' => 'Create new piggy bank', 'new_piggy_bank' => 'Create new piggy bank',
'store_piggy_bank' => 'Store new piggy bank', 'store_piggy_bank' => 'Store new piggy bank',
'account_status' => 'Account status', 'account_status' => 'Account status',
'left_for_piggy_banks' => 'Left for piggy banks', 'left_for_piggy_banks' => 'Left for piggy banks',
'sum_of_piggy_banks' => 'Sum of piggy banks', 'sum_of_piggy_banks' => 'Sum of piggy banks',
'saved_so_far' => 'Saved so far', 'saved_so_far' => 'Saved so far',
'left_to_save' => 'Left to save', 'left_to_save' => 'Left to save',
'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"',
'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"',
'add' => 'Add', 'add' => 'Add',
'remove' => 'Remove', 'remove' => 'Remove',
'max_amount_add' => 'The maximum amount you can add is', 'max_amount_add' => 'The maximum amount you can add is',
'max_amount_remove' => 'The maximum amount you can remove is', 'max_amount_remove' => 'The maximum amount you can remove is',
'update_piggy_button' => 'Update piggy bank', 'update_piggy_button' => 'Update piggy bank',
'update_piggy_title' => 'Update piggy bank ":name"', 'update_piggy_title' => 'Update piggy bank ":name"',
'details' => 'Details', 'details' => 'Details',
'events' => 'Events', 'events' => 'Events',
'target_amount' => 'Target amount', 'target_amount' => 'Target amount',
'start_date' => 'Start date', 'start_date' => 'Start date',
'target_date' => 'Target date', 'target_date' => 'Target date',
'no_target_date' => 'No target date', 'no_target_date' => 'No target date',
'todo' => 'to do', 'todo' => 'to do',
'table' => 'Table', 'table' => 'Table',
'piggy_bank_not_exists' => 'Piggy bank no longer exists.', 'piggy_bank_not_exists' => 'Piggy bank no longer exists.',
'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.',
'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date',
'delete_piggy_bank' => 'Delete piggy bank ":name"', 'delete_piggy_bank' => 'Delete piggy bank ":name"',
// tags // tags
'regular_tag' => 'Just a regular tag.', 'regular_tag' => 'Just a regular tag.',
'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.',
'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.',
'delete_tag' => 'Delete tag ":tag"', 'delete_tag' => 'Delete tag ":tag"',
'new_tag' => 'Make new tag', 'new_tag' => 'Make new tag',
'edit_tag' => 'Edit tag ":tag"', 'edit_tag' => 'Edit tag ":tag"',
'no_year' => 'No year set', 'no_year' => 'No year set',
'no_month' => 'No month set', 'no_month' => 'No month set',
'tag_title_nothing' => 'Default tags', 'tag_title_nothing' => 'Default tags',
'tag_title_balancingAct' => 'Balancing act tags', 'tag_title_balancingAct' => 'Balancing act tags',
'tag_title_advancePayment' => 'Advance payment tags', 'tag_title_advancePayment' => 'Advance payment tags',
'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like' . 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like' .
' <span class="label label-info">expensive</span>, <span class="label label-info">bill</span>' . ' <span class="label label-info">expensive</span>, <span class="label label-info">bill</span>' .
' or <span class="label label-info">for-party</span>. In Firefly III, tags can have more properties' . ' or <span class="label label-info">for-party</span>. In Firefly III, tags can have more properties' .
' such as a date, description and location. This allows you to join transactions together in a more' . ' such as a date, description and location. This allows you to join transactions together in a more' .
' meaningful way. For example, you could make a tag called <span class="label label-success">' . ' meaningful way. For example, you could make a tag called <span class="label label-success">' .
'Christmas dinner with friends</span> and add information about the restaurant. Such tags are "singular",' . 'Christmas dinner with friends</span> and add information about the restaurant. Such tags are "singular",' .
' you would only use them for a single occasion, perhaps with multiple transactions.', ' you would only use them for a single occasion, perhaps with multiple transactions.',
'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money' . '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 ' . ' 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.' . '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. ', ' 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.',
]; ];

View File

@ -17,5 +17,6 @@ return [
"token" => "This password reset token is invalid.", "token" => "This password reset token is invalid.",
"sent" => "We have e-mailed your password reset link!", "sent" => "We have e-mailed your password reset link!",
"reset" => "Your password has been reset!", "reset" => "Your password has been reset!",
'blocked' => 'Nice try though.'
]; ];

View File

@ -13,6 +13,7 @@ return [
| |
*/ */
'invalid_domain' => 'Due to security constraints, you cannot register from this domain.',
'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.',
'file_attached' => 'Succesfully uploaded file ":name".', 'file_attached' => 'Succesfully uploaded file ":name".',
'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.',
@ -28,12 +29,10 @@ return [
"before" => "The :attribute must be a date before :date.", "before" => "The :attribute must be a date before :date.",
'unique_object_for_user' => 'This name is already in use', 'unique_object_for_user' => 'This name is already in use',
'unique_account_for_user' => 'This account name is already in use', 'unique_account_for_user' => 'This account name is already in use',
"between" => [ "between.numeric" => "The :attribute must be between :min and :max.",
"numeric" => "The :attribute must be between :min and :max.", "between.file" => "The :attribute must be between :min and :max kilobytes.",
"file" => "The :attribute must be between :min and :max kilobytes.", "between.string" => "The :attribute must be between :min and :max characters.",
"string" => "The :attribute must be between :min and :max characters.", "between.array" => "The :attribute must have between :min and :max items.",
"array" => "The :attribute must have between :min and :max items.",
],
"boolean" => "The :attribute field must be true or false.", "boolean" => "The :attribute field must be true or false.",
"confirmed" => "The :attribute confirmation does not match.", "confirmed" => "The :attribute confirmation does not match.",
"date" => "The :attribute is not a valid date.", "date" => "The :attribute is not a valid date.",
@ -48,19 +47,15 @@ return [
"in" => "The selected :attribute is invalid.", "in" => "The selected :attribute is invalid.",
"integer" => "The :attribute must be an integer.", "integer" => "The :attribute must be an integer.",
"ip" => "The :attribute must be a valid IP address.", "ip" => "The :attribute must be a valid IP address.",
"max" => [ "max.numeric" => "The :attribute may not be greater than :max.",
"numeric" => "The :attribute may not be greater than :max.", "max.file" => "The :attribute may not be greater than :max kilobytes.",
"file" => "The :attribute may not be greater than :max kilobytes.", "max.string" => "The :attribute may not be greater than :max characters.",
"string" => "The :attribute may not be greater than :max characters.", "max.array" => "The :attribute may not have more than :max items.",
"array" => "The :attribute may not have more than :max items.",
],
"mimes" => "The :attribute must be a file of type: :values.", "mimes" => "The :attribute must be a file of type: :values.",
"min" => [ "min.numeric" => "The :attribute must be at least :min.",
"numeric" => "The :attribute must be at least :min.", "min.file" => "The :attribute must be at least :min kilobytes.",
"file" => "The :attribute must be at least :min kilobytes.", "min.string" => "The :attribute must be at least :min characters.",
"string" => "The :attribute must be at least :min characters.", "min.array" => "The :attribute must have at least :min items.",
"array" => "The :attribute must have at least :min items.",
],
"not_in" => "The selected :attribute is invalid.", "not_in" => "The selected :attribute is invalid.",
"numeric" => "The :attribute must be a number.", "numeric" => "The :attribute must be a number.",
"regex" => "The :attribute format is invalid.", "regex" => "The :attribute format is invalid.",
@ -71,44 +66,13 @@ return [
"required_without" => "The :attribute field is required when :values is not present.", "required_without" => "The :attribute field is required when :values is not present.",
"required_without_all" => "The :attribute field is required when none of :values are present.", "required_without_all" => "The :attribute field is required when none of :values are present.",
"same" => "The :attribute and :other must match.", "same" => "The :attribute and :other must match.",
"size" => [ "size.numeric" => "The :attribute must be :size.",
"numeric" => "The :attribute must be :size.", "size.file" => "The :attribute must be :size kilobytes.",
"file" => "The :attribute must be :size kilobytes.", "size.string" => "The :attribute must be :size characters.",
"string" => "The :attribute must be :size characters.", "size.array" => "The :attribute must contain :size items.",
"array" => "The :attribute must contain :size items.",
],
"unique" => "The :attribute has already been taken.", "unique" => "The :attribute has already been taken.",
"url" => "The :attribute format is invalid.", "url" => "The :attribute format is invalid.",
"timezone" => "The :attribute must be a valid zone.", "timezone" => "The :attribute must be a valid zone.",
'attributes' => [],
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => [],
]; ];

View File

@ -253,249 +253,264 @@ return [
'details_for_revenue' => 'Overzicht voor debiteur ":name"', 'details_for_revenue' => 'Overzicht voor debiteur ":name"',
'details_for_cash' => 'Overzicht voor contant geldrekening ":name"', 'details_for_cash' => 'Overzicht voor contant geldrekening ":name"',
'store_new_asset_account' => 'Sla nieuwe betaalrekening op', 'store_new_asset_account' => 'Sla nieuwe betaalrekening op',
'store_new_expense_account' => 'Sla nieuwe crediteur op', 'store_new_expense_account' => 'Sla nieuwe crediteur op',
'store_new_revenue_account' => 'Sla nieuwe debiteur op', 'store_new_revenue_account' => 'Sla nieuwe debiteur op',
'edit_asset_account' => 'Wijzig betaalrekening ":name"', 'edit_asset_account' => 'Wijzig betaalrekening ":name"',
'edit_expense_account' => 'Wijzig crediteur ":name"', 'edit_expense_account' => 'Wijzig crediteur ":name"',
'edit_revenue_account' => 'Wijzig debiteur ":name"', 'edit_revenue_account' => 'Wijzig debiteur ":name"',
'delete_asset_account' => 'Verwijder betaalrekening ":name"', 'delete_asset_account' => 'Verwijder betaalrekening ":name"',
'delete_expense_account' => 'Verwijder crediteur ":name"', 'delete_expense_account' => 'Verwijder crediteur ":name"',
'delete_revenue_account' => 'Verwijder debiteur ":name"', 'delete_revenue_account' => 'Verwijder debiteur ":name"',
'asset_deleted' => 'Betaalrekening ":name" is verwijderd.', 'asset_deleted' => 'Betaalrekening ":name" is verwijderd.',
'expense_deleted' => 'Crediteur ":name" is verwijderd.', 'expense_deleted' => 'Crediteur ":name" is verwijderd.',
'revenue_deleted' => 'Debiteur ":name" is verwijderd.', 'revenue_deleted' => 'Debiteur ":name" is verwijderd.',
'update_asset_account' => 'Wijzig betaalrekening', 'update_asset_account' => 'Wijzig betaalrekening',
'update_expense_account' => 'Wijzig crediteur', 'update_expense_account' => 'Wijzig crediteur',
'update_revenue_account' => 'Wijzig debiteur', 'update_revenue_account' => 'Wijzig debiteur',
'make_new_asset_account' => 'Nieuwe betaalrekening', 'make_new_asset_account' => 'Nieuwe betaalrekening',
'make_new_expense_account' => 'Nieuwe crediteur', 'make_new_expense_account' => 'Nieuwe crediteur',
'make_new_revenue_account' => 'Nieuwe debiteur', 'make_new_revenue_account' => 'Nieuwe debiteur',
'asset_accounts' => 'Betaalrekeningen', 'asset_accounts' => 'Betaalrekeningen',
'expense_accounts' => 'Crediteuren', 'expense_accounts' => 'Crediteuren',
'revenue_accounts' => 'Debiteuren', 'revenue_accounts' => 'Debiteuren',
'account_type' => 'Account type', 'account_type' => 'Account type',
// some extra help: // some extra help:
'accountExtraHelp_asset' => '', 'accountExtraHelp_asset' => '',
'accountExtraHelp_expense' => 'accountExtraHelp_expense' =>
'Een crediteur is een persoon of een bedrijf waar je geld aan moet betalen. Je staat bij ze in het krijt. Een verwarrende' . 'Een crediteur is een persoon of een bedrijf waar je geld aan moet betalen. Je staat bij ze in het krijt. Een verwarrende' .
' term misschien, maar zo werkt het nou eenmaal. De supermarkt, je huurbaas of de bank zijn crediteuren. Jouw ' . ' term misschien, maar zo werkt het nou eenmaal. De supermarkt, je huurbaas of de bank zijn crediteuren. Jouw ' .
'geld (krediet) gaat naar hen toe. De term komt uit de wereld van de boekhouding. De uitgaves die je hier ziet zijn ' . 'geld (krediet) gaat naar hen toe. De term komt uit de wereld van de boekhouding. De uitgaves die je hier ziet zijn ' .
'positief, want je kijkt uit hun perspectief. Zodra jij afrekent in een winkel, komt het geld er bij hen bij (positief).', 'positief, want je kijkt uit hun perspectief. Zodra jij afrekent in een winkel, komt het geld er bij hen bij (positief).',
'accountExtraHelp_revenue' => 'Als je geld krijgt van een bedrijf of een persoon is dat een debiteur. ' . 'accountExtraHelp_revenue' => 'Als je geld krijgt van een bedrijf of een persoon is dat een debiteur. ' .
'Dat kan salaris zijn, of een andere betaling. ' . 'Dat kan salaris zijn, of een andere betaling. ' .
' Ze hebben een schuld (debet) aan jou. De term komt uit de wereld van de boekhouding.' . ' Ze hebben een schuld (debet) aan jou. De term komt uit de wereld van de boekhouding.' .
' De inkomsten die je hier ziet zijn negatief, want je kijkt uit hun perspectief. Zodra een debiteur geld naar jou ' . ' De inkomsten die je hier ziet zijn negatief, want je kijkt uit hun perspectief. Zodra een debiteur geld naar jou ' .
'overmaakt gaat het er bij hen af (negatief).', 'overmaakt gaat het er bij hen af (negatief).',
'save_transactions_by_moving' => 'Bewaar deze transacties door ze aan een andere rekening te koppelen:', 'save_transactions_by_moving' => 'Bewaar deze transacties door ze aan een andere rekening te koppelen:',
// categories: // categories:
'new_category' => 'Nieuwe categorie', 'new_category' => 'Nieuwe categorie',
'create_new_category' => 'Nieuwe categorie', 'create_new_category' => 'Nieuwe categorie',
'without_category' => 'Zonder categorie', 'without_category' => 'Zonder categorie',
'update_category' => 'Wijzig categorie', 'update_category' => 'Wijzig categorie',
'edit_category' => 'Wijzig categorie ":name"', 'edit_category' => 'Wijzig categorie ":name"',
'categories' => 'Categorieën', 'categories' => 'Categorieën',
'no_category' => '(geen categorie)', 'no_category' => '(geen categorie)',
'category' => 'Categorie', 'category' => 'Categorie',
'delete_category' => 'Verwijder categorie ":name"', 'delete_category' => 'Verwijder categorie ":name"',
'store_category' => 'Sla nieuwe categorie op', 'store_category' => 'Sla nieuwe categorie op',
// transactions: // transactions:
'update_withdrawal' => 'Wijzig uitgave', 'update_withdrawal' => 'Wijzig uitgave',
'update_deposit' => 'Wijzig inkomsten', 'update_deposit' => 'Wijzig inkomsten',
'update_transfer' => 'Wijzig overschrijving', 'update_transfer' => 'Wijzig overschrijving',
'delete_withdrawal' => 'Verwijder uitgave ":description"', 'delete_withdrawal' => 'Verwijder uitgave ":description"',
'delete_deposit' => 'Verwijder inkomsten ":description"', 'delete_deposit' => 'Verwijder inkomsten ":description"',
'delete_transfer' => 'Verwijder overschrijving ":description"', 'delete_transfer' => 'Verwijder overschrijving ":description"',
// new user: // new user:
'welcome' => 'Welkom bij Firefly!', 'welcome' => 'Welkom bij Firefly!',
'createNewAsset' => 'Maak om te beginnen een nieuwe betaalrekening. Dit is je start van je financiële beheer.', 'createNewAsset' => 'Maak om te beginnen een nieuwe betaalrekening. Dit is je start van je financiële beheer.',
'createNewAssetButton' => 'Maak een nieuwe betaalrekening', 'createNewAssetButton' => 'Maak een nieuwe betaalrekening',
// home page: // home page:
'yourAccounts' => 'Je betaalrekeningen', 'yourAccounts' => 'Je betaalrekeningen',
'budgetsAndSpending' => 'Budgetten en uitgaven', 'budgetsAndSpending' => 'Budgetten en uitgaven',
'savings' => 'Sparen', 'savings' => 'Sparen',
'markAsSavingsToContinue' => 'Om hier wat te zien stel je je betaalrekeningen in als "spaarrekening".', 'markAsSavingsToContinue' => 'Om hier wat te zien stel je je betaalrekeningen in als "spaarrekening".',
'createPiggyToContinue' => 'Maak spaarpotjes om hier iets te zien.', 'createPiggyToContinue' => 'Maak spaarpotjes om hier iets te zien.',
'newWithdrawal' => 'Nieuwe uitgave', 'newWithdrawal' => 'Nieuwe uitgave',
'newDeposit' => 'Nieuwe inkomsten', 'newDeposit' => 'Nieuwe inkomsten',
'newTransfer' => 'Nieuwe overschrijving', 'newTransfer' => 'Nieuwe overschrijving',
'moneyIn' => 'Inkomsten', 'moneyIn' => 'Inkomsten',
'moneyOut' => 'Uitgaven', 'moneyOut' => 'Uitgaven',
'billsToPay' => 'Openstaande contracten', 'billsToPay' => 'Openstaande contracten',
'billsPaid' => 'Betaalde contracten', 'billsPaid' => 'Betaalde contracten',
'viewDetails' => 'Meer info', 'viewDetails' => 'Meer info',
'divided' => 'verdeeld', 'divided' => 'verdeeld',
'toDivide' => 'te verdelen', 'toDivide' => 'te verdelen',
// menu and titles, should be recycled as often as possible: // menu and titles, should be recycled as often as possible:
'toggleNavigation' => 'Navigatie aan of uit', 'toggleNavigation' => 'Navigatie aan of uit',
'currency' => 'Valuta', 'currency' => 'Valuta',
'preferences' => 'Voorkeuren', 'preferences' => 'Voorkeuren',
'logout' => 'Uitloggen', 'logout' => 'Uitloggen',
'searchPlaceholder' => 'Zoeken...', 'searchPlaceholder' => 'Zoeken...',
'dashboard' => 'Dashboard', 'dashboard' => 'Dashboard',
'currencies' => 'Valuta', 'currencies' => 'Valuta',
'accounts' => 'Rekeningen', 'accounts' => 'Rekeningen',
'Asset account' => 'Betaalrekening', 'Asset account' => 'Betaalrekening',
'Default account' => 'Betaalrekening', 'Default account' => 'Betaalrekening',
'Expense account' => 'Crediteur', 'Expense account' => 'Crediteur',
'Revenue account' => 'Debiteur', 'Revenue account' => 'Debiteur',
'budgets' => 'Budgetten', 'Initial balance account' => 'Startbalansrekening',
'tags' => 'Tags', 'budgets' => 'Budgetten',
'reports' => 'Overzichten', 'tags' => 'Tags',
'transactions' => 'Transacties', 'reports' => 'Overzichten',
'expenses' => 'Uitgaven', 'transactions' => 'Transacties',
'income' => 'Inkomsten', 'expenses' => 'Uitgaven',
'transfers' => 'Overschrijvingen', 'income' => 'Inkomsten',
'moneyManagement' => 'Geldbeheer', 'transfers' => 'Overschrijvingen',
'piggyBanks' => 'Spaarpotjes', 'moneyManagement' => 'Geldbeheer',
'bills' => 'Contracten', 'piggyBanks' => 'Spaarpotjes',
'createNew' => 'Nieuw', 'bills' => 'Contracten',
'withdrawal' => 'Uitgave', 'createNew' => 'Nieuw',
'deposit' => 'Inkomsten', 'withdrawal' => 'Uitgave',
'account' => 'Rekening', 'deposit' => 'Inkomsten',
'transfer' => 'Overschrijving', 'account' => 'Rekening',
'Withdrawal' => 'Uitgave', 'transfer' => 'Overschrijving',
'Deposit' => 'Inkomsten', 'Withdrawal' => 'Uitgave',
'Transfer' => 'Overschrijving', 'Deposit' => 'Inkomsten',
'profile' => 'Profiel', 'Transfer' => 'Overschrijving',
'bill' => 'Contract', 'profile' => 'Profiel',
'yes' => 'Ja', 'bill' => 'Contract',
'no' => 'Nee', 'yes' => 'Ja',
'amount' => 'Bedrag', 'no' => 'Nee',
'newBalance' => 'Nieuw saldo', 'amount' => 'Bedrag',
'overview' => 'Overzicht', 'newBalance' => 'Nieuw saldo',
'saveOnAccount' => 'Sparen op rekening', 'overview' => 'Overzicht',
'unknown' => 'Onbekend', 'saveOnAccount' => 'Sparen op rekening',
'daily' => 'Dagelijks', 'unknown' => 'Onbekend',
'weekly' => 'Wekelijks', 'daily' => 'Dagelijks',
'monthly' => 'Maandelijks', 'weekly' => 'Wekelijks',
'quarterly' => 'Elk kwartaal', 'monthly' => 'Maandelijks',
'half-year' => 'Elk half jaar', 'quarterly' => 'Elk kwartaal',
'yearly' => 'Jaarlijks', 'half-year' => 'Elk half jaar',
'sum_of_year' => 'Som van jaar', 'yearly' => 'Jaarlijks',
'average_of_year' => 'Gemiddelde in jaar', 'sum_of_year' => 'Som van jaar',
'average_of_year' => 'Gemiddelde in jaar',
'sum_of_years' => 'Som van jaren',
'average_of_years' => 'Gemiddelde in jaren',
// reports: // reports:
'reportForYear' => 'Jaaroverzicht :year', 'report_default' => 'Standaard financieel rapport (:start tot :end)',
'reportForYearShared' => 'Jaaroverzicht :year (inclusief gedeelde rekeningen)', 'quick_link_reports' => 'Snelle links',
'reportForMonth' => 'Maandoverzicht voor :month', 'quick_link_default_report' => 'Standaard financieel rapport',
'reportForMonthShared' => 'Maandoverzicht voor :month (inclusief gedeelde rekeningen)', 'report_this_month_quick' => 'Deze maand, alle rekeningen',
'incomeVsExpenses' => 'Inkomsten tegenover uitgaven', 'report_this_year_quick' => 'Dit jaar, alle rekeningen',
'accountBalances' => 'Rekeningsaldi', 'report_all_time_quick' => 'Gehele periode, alle rekeningen',
'balanceStartOfYear' => 'Saldo aan het begin van het jaar', 'reports_can_bookmark' => 'Je kan rapporten aan je favorieten toevoegen.',
'balanceEndOfYear' => 'Saldo aan het einde van het jaar', 'incomeVsExpenses' => 'Inkomsten tegenover uitgaven',
'balanceStartOfMonth' => 'Saldo aan het begin van de maand', 'accountBalances' => 'Rekeningsaldi',
'balanceEndOfMonth' => 'Saldo aan het einde van de maand', 'balanceStartOfYear' => 'Saldo aan het begin van het jaar',
'balanceStart' => 'Saldo aan het begin van de periode', 'balanceEndOfYear' => 'Saldo aan het einde van het jaar',
'balanceEnd' => 'Saldo aan het einde van de periode', 'balanceStartOfMonth' => 'Saldo aan het begin van de maand',
'reportsOwnAccounts' => 'Overzichten voor je eigen betaalrekeningen', 'balanceEndOfMonth' => 'Saldo aan het einde van de maand',
'reportsOwnAccountsAndShared' => 'Overzichten voor je eigen betaalrekeningen en gedeelde rekeningen', 'balanceStart' => 'Saldo aan het begin van de periode',
'splitByAccount' => 'Per betaalrekening', 'balanceEnd' => 'Saldo aan het einde van de periode',
'balancedByTransfersAndTags' => 'Gecorrigeerd met overschrijvingen en tags', 'reportsOwnAccounts' => 'Overzichten voor je eigen betaalrekeningen',
'coveredWithTags' => 'Gecorrigeerd met tags', 'reportsOwnAccountsAndShared' => 'Overzichten voor je eigen betaalrekeningen en gedeelde rekeningen',
'leftUnbalanced' => 'Ongecorrigeerd', 'splitByAccount' => 'Per betaalrekening',
'expectedBalance' => 'Verwacht saldo', 'balancedByTransfersAndTags' => 'Gecorrigeerd met overschrijvingen en tags',
'outsideOfBudgets' => 'Buiten budgetten', 'coveredWithTags' => 'Gecorrigeerd met tags',
'leftInBudget' => 'Over van budget', 'leftUnbalanced' => 'Ongecorrigeerd',
'sumOfSums' => 'Alles bij elkaar', 'expectedBalance' => 'Verwacht saldo',
'notCharged' => '(Nog) niet betaald', 'outsideOfBudgets' => 'Buiten budgetten',
'inactive' => 'Niet actief', 'leftInBudget' => 'Over van budget',
'difference' => 'Verschil', 'sumOfSums' => 'Alles bij elkaar',
'in' => 'In', 'noCategory' => '(zonder categorie)',
'out' => 'Uit', 'notCharged' => '(Nog) niet betaald',
'topX' => 'top :number', 'inactive' => 'Niet actief',
'showTheRest' => 'Laat alles zien', 'difference' => 'Verschil',
'hideTheRest' => 'Laat alleen de top :number zien', 'in' => 'In',
'categories_earned_in_year' => 'Categorieën (inkomsten)', 'out' => 'Uit',
'categories_spent_in_year' => 'Categorieën (uitgaven)', 'topX' => 'top :number',
'showTheRest' => 'Laat alles zien',
'hideTheRest' => 'Laat alleen de top :number zien',
'categories_earned_in_year' => 'Categorieën (inkomsten)',
'categories_spent_in_year' => 'Categorieën (uitgaven)',
'report_type' => 'Rapporttype',
'report_type_default' => 'Standard financieel rapport',
'report_included_accounts' => 'Accounts in rapport',
'report_date_range' => 'Datumbereik',
'report_include_help' => 'Overboekingen naar gedeelde rekeningen tellen als uitgave. Overboekingen van gedeelde rekeningen tellen als inkomsten.',
'report_preset_ranges' => 'Standaardbereik',
'shared' => 'Gedeeld',
// charts: // charts:
'dayOfMonth' => 'Dag vd maand', 'dayOfMonth' => 'Dag vd maand',
'month' => 'Maand', 'month' => 'Maand',
'budget' => 'Budget', 'budget' => 'Budget',
'spent' => 'Uitgegeven', 'spent' => 'Uitgegeven',
'earned' => 'Verdiend', 'earned' => 'Verdiend',
'overspent' => 'Teveel uitgegeven', 'overspent' => 'Teveel uitgegeven',
'left' => 'Over', 'left' => 'Over',
'noBudget' => '(geen budget)', 'noBudget' => '(geen budget)',
'maxAmount' => 'Maximaal bedrag', 'maxAmount' => 'Maximaal bedrag',
'minAmount' => 'Minimaal bedrag', 'minAmount' => 'Minimaal bedrag',
'billEntry' => 'Bedrag voor dit contract', 'billEntry' => 'Bedrag voor dit contract',
'name' => 'Naam', 'name' => 'Naam',
'date' => 'Datum', 'date' => 'Datum',
'paid' => 'Betaald', 'paid' => 'Betaald',
'unpaid' => 'Niet betaald', 'unpaid' => 'Niet betaald',
'day' => 'Dag', 'day' => 'Dag',
'budgeted' => 'Gebudgetteerd', 'budgeted' => 'Gebudgetteerd',
'period' => 'Periode', 'period' => 'Periode',
'balance' => 'Saldo', 'balance' => 'Saldo',
'summary' => 'Samenvatting', 'summary' => 'Samenvatting',
'sum' => 'Som', 'sum' => 'Som',
'average' => 'Gemiddeld', 'average' => 'Gemiddeld',
'balanceFor' => 'Saldo op :name', 'balanceFor' => 'Saldo op :name',
// piggy banks: // piggy banks:
'piggy_bank' => 'Spaarpotje', 'piggy_bank' => 'Spaarpotje',
'new_piggy_bank' => 'Nieuw spaarpotje', 'new_piggy_bank' => 'Nieuw spaarpotje',
'store_piggy_bank' => 'Sla spaarpotje op', 'store_piggy_bank' => 'Sla spaarpotje op',
'account_status' => 'Rekeningoverzicht', 'account_status' => 'Rekeningoverzicht',
'left_for_piggy_banks' => 'Over voor spaarpotjes', 'left_for_piggy_banks' => 'Over voor spaarpotjes',
'sum_of_piggy_banks' => 'Som van spaarpotjes', 'sum_of_piggy_banks' => 'Som van spaarpotjes',
'saved_so_far' => 'Gespaard', 'saved_so_far' => 'Gespaard',
'left_to_save' => 'Te sparen', 'left_to_save' => 'Te sparen',
'add_money_to_piggy_title' => 'Stop geld in spaarpotje ":name"', 'add_money_to_piggy_title' => 'Stop geld in spaarpotje ":name"',
'remove_money_from_piggy_title' => 'Haal geld uit spaarpotje ":name"', 'remove_money_from_piggy_title' => 'Haal geld uit spaarpotje ":name"',
'add' => 'Toevoegen', 'add' => 'Toevoegen',
'remove' => 'Verwijderen', 'remove' => 'Verwijderen',
'max_amount_add' => 'Hooguit toe te voegen', 'max_amount_add' => 'Hooguit toe te voegen',
'max_amount_remove' => 'Hooguit te verwijderen', 'max_amount_remove' => 'Hooguit te verwijderen',
'update_piggy_button' => 'Wijzig spaarpotje', 'update_piggy_button' => 'Wijzig spaarpotje',
'update_piggy_title' => 'Wijzig spaarpotje ":name"', 'update_piggy_title' => 'Wijzig spaarpotje ":name"',
'details' => 'Details', 'details' => 'Details',
'events' => 'Gebeurtenissen', 'events' => 'Gebeurtenissen',
'target_amount' => 'Doelbedrag', 'target_amount' => 'Doelbedrag',
'start_date' => 'Startdatum', 'start_date' => 'Startdatum',
'target_date' => 'Doeldatum', 'target_date' => 'Doeldatum',
'no_target_date' => 'Geen doeldatum', 'no_target_date' => 'Geen doeldatum',
'todo' => 'te doen', 'todo' => 'te doen',
'table' => 'Tabel', 'table' => 'Tabel',
'piggy_bank_not_exists' => 'Dit spaarpotje bestaat niet meer.', 'piggy_bank_not_exists' => 'Dit spaarpotje bestaat niet meer.',
'add_any_amount_to_piggy' => 'Stop geld in dit spaarpotje om het doel van :amount te halen.', 'add_any_amount_to_piggy' => 'Stop geld in dit spaarpotje om het doel van :amount te halen.',
'add_set_amount_to_piggy' => 'Stop voor :date :amount in dit spaarpotje om hem op tijd te vullen.', 'add_set_amount_to_piggy' => 'Stop voor :date :amount in dit spaarpotje om hem op tijd te vullen.',
'delete_piggy_bank' => 'Verwijder spaarpotje ":name"', 'delete_piggy_bank' => 'Verwijder spaarpotje ":name"',
// tags // tags
'regular_tag' => 'Een gewone tag.', 'regular_tag' => 'Een gewone tag.',
'balancing_act' => 'Er kunnen maar twee transacties worden getagged; een uitgaven en inkomsten. Ze balanceren elkaar.', 'balancing_act' => 'Er kunnen maar twee transacties worden getagged; een uitgaven en inkomsten. Ze balanceren elkaar.',
'advance_payment' => 'Je kan een uitgave taggen en zoveel inkomsten om de uitgave (helemaal) te compenseren.', 'advance_payment' => 'Je kan een uitgave taggen en zoveel inkomsten om de uitgave (helemaal) te compenseren.',
'delete_tag' => 'Verwijder tag ":tag"', 'delete_tag' => 'Verwijder tag ":tag"',
'new_tag' => 'Maak nieuwe tag', 'new_tag' => 'Maak nieuwe tag',
'edit_tag' => 'Wijzig tag ":tag"', 'edit_tag' => 'Wijzig tag ":tag"',
'no_year' => 'Zonder jaar', 'no_year' => 'Zonder jaar',
'no_month' => 'Zonder maand', 'no_month' => 'Zonder maand',
'tag_title_nothing' => 'Standaard tags', 'tag_title_nothing' => 'Standaard tags',
'tag_title_balancingAct' => 'Balancerende tags', 'tag_title_balancingAct' => 'Balancerende tags',
'tag_title_advancePayment' => 'Vooruitbetaalde tags', 'tag_title_advancePayment' => 'Vooruitbetaalde tags',
'tags_introduction' => 'Normaal gesproken zijn tags enkele woorden, gebruikt om gerelateerde zaken snel aan elkaar te plakken. ' . 'tags_introduction' => 'Normaal gesproken zijn tags enkele woorden, gebruikt om gerelateerde zaken snel aan elkaar te plakken. ' .
'<span class="label label-info">dure-aanschaf</span>, <span class="label label-info">rekening</span>, ' . '<span class="label label-info">dure-aanschaf</span>, <span class="label label-info">rekening</span>, ' .
'<span class="label label-info">feestje</span>. In Firefly III hebben tags meer betekenis en kan je er een datum' . '<span class="label label-info">feestje</span>. In Firefly III hebben tags meer betekenis en kan je er een datum' .
', beschrijving en locatie aan geven. Daarmee kan je je transacties op een wat zinvollere manier aan elkaar ' . ', beschrijving en locatie aan geven. Daarmee kan je je transacties op een wat zinvollere manier aan elkaar ' .
'koppelen. Je kan bijvoorbeeld een tag <span class="label label-success">Kerstdiner</span> maken en informatie over' . 'koppelen. Je kan bijvoorbeeld een tag <span class="label label-success">Kerstdiner</span> maken en informatie over' .
' het restaurant meenemen. Zulke tags zijn enkelvoudig; je gebruikt ze maar bij één gelegenheid.', ' het restaurant meenemen. Zulke tags zijn enkelvoudig; je gebruikt ze maar bij één gelegenheid.',
'tags_group' => 'Omdat tags transacties groeperen kan je er teruggaves, vergoedingen en andere geldzaken mee aanduiden, zolang' . 'tags_group' => 'Omdat tags transacties groeperen kan je er teruggaves, vergoedingen en andere geldzaken mee aanduiden, zolang' .
' de transacties elkaar "opheffen". Hoe je dit aanpakt is aan jou. De gewone manier kan natuurlijk ook.', ' de transacties elkaar "opheffen". Hoe je dit aanpakt is aan jou. De gewone manier kan natuurlijk ook.',
'tags_start' => 'Maak hieronder een tag, of voer nieuwe tags in als je nieuwe transacties maakt.', 'tags_start' => 'Maak hieronder een tag, of voer nieuwe tags in als je nieuwe transacties maakt.',
]; ];

View File

@ -17,5 +17,6 @@ return [
"token" => "Ongeldig token! Sorry", "token" => "Ongeldig token! Sorry",
"sent" => "Je krijgt een mailtje met een linkje om je wachtwoord te herstellen!", "sent" => "Je krijgt een mailtje met een linkje om je wachtwoord te herstellen!",
"reset" => "Je wachtwoord is hersteld!", "reset" => "Je wachtwoord is hersteld!",
'blocked' => 'Leuk geprobeerd wel.'
]; ];

View File

@ -13,6 +13,7 @@ return [
| |
*/ */
'invalid_domain' => 'Kan niet registereren vanaf dit domein.',
'file_already_attached' => 'Het geuploade bestand ":name" is al gelinkt aan deze transactie.', 'file_already_attached' => 'Het geuploade bestand ":name" is al gelinkt aan deze transactie.',
'file_attached' => 'Bestand met naam ":name" is met succes geuploaded.', 'file_attached' => 'Bestand met naam ":name" is met succes geuploaded.',
'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.', 'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.',
@ -28,12 +29,10 @@ return [
"before" => "The :attribute must be a date before :date.", "before" => "The :attribute must be a date before :date.",
'unique_object_for_user' => 'Deze naam is al in gebruik', 'unique_object_for_user' => 'Deze naam is al in gebruik',
'unique_account_for_user' => 'This rekeningnaam is already in use', 'unique_account_for_user' => 'This rekeningnaam is already in use',
"between" => [ "between.numeric" => "The :attribute must be between :min and :max.",
"numeric" => "The :attribute must be between :min and :max.", "between.file" => "The :attribute must be between :min and :max kilobytes.",
"file" => "The :attribute must be between :min and :max kilobytes.", "between.string" => "The :attribute must be between :min and :max characters.",
"string" => "The :attribute must be between :min and :max characters.", "between.array" => "The :attribute must have between :min and :max items.",
"array" => "The :attribute must have between :min and :max items.",
],
"boolean" => "The :attribute field must be true or false.", "boolean" => "The :attribute field must be true or false.",
"confirmed" => "The :attribute confirmation does not match.", "confirmed" => "The :attribute confirmation does not match.",
"date" => "The :attribute is not a valid date.", "date" => "The :attribute is not a valid date.",
@ -48,19 +47,15 @@ return [
"in" => "The selected :attribute is invalid.", "in" => "The selected :attribute is invalid.",
"integer" => "The :attribute must be an integer.", "integer" => "The :attribute must be an integer.",
"ip" => "The :attribute must be a valid IP address.", "ip" => "The :attribute must be a valid IP address.",
"max" => [ "max.numeric" => "The :attribute may not be greater than :max.",
"numeric" => "The :attribute may not be greater than :max.", "max.file" => "The :attribute may not be greater than :max kilobytes.",
"file" => "The :attribute may not be greater than :max kilobytes.", "max.string" => "The :attribute may not be greater than :max characters.",
"string" => "The :attribute may not be greater than :max characters.", "max.array" => "The :attribute may not have more than :max items.",
"array" => "The :attribute may not have more than :max items.",
],
"mimes" => "The :attribute must be a file of type: :values.", "mimes" => "The :attribute must be a file of type: :values.",
"min" => [ "min.numeric" => "The :attribute must be at least :min.",
"numeric" => "The :attribute must be at least :min.", "min.file" => "The :attribute must be at least :min kilobytes.",
"file" => "The :attribute must be at least :min kilobytes.", "min.string" => "The :attribute must be at least :min characters.",
"string" => "The :attribute must be at least :min characters.", "min.array" => "The :attribute must have at least :min items.",
"array" => "The :attribute must have at least :min items.",
],
"not_in" => "The selected :attribute is invalid.", "not_in" => "The selected :attribute is invalid.",
"numeric" => "The :attribute must be a number.", "numeric" => "The :attribute must be a number.",
"regex" => "The :attribute format is invalid.", "regex" => "The :attribute format is invalid.",
@ -71,44 +66,13 @@ return [
"required_without" => "The :attribute field is required when :values is not present.", "required_without" => "The :attribute field is required when :values is not present.",
"required_without_all" => "The :attribute field is required when none of :values are present.", "required_without_all" => "The :attribute field is required when none of :values are present.",
"same" => "The :attribute and :other must match.", "same" => "The :attribute and :other must match.",
"size" => [ "size.numeric" => "The :attribute must be :size.",
"numeric" => "The :attribute must be :size.", "size.file" => "The :attribute must be :size kilobytes.",
"file" => "The :attribute must be :size kilobytes.", "size.string" => "The :attribute must be :size characters.",
"string" => "The :attribute must be :size characters.", "size.array" => "The :attribute must contain :size items.",
"array" => "The :attribute must contain :size items.",
],
"unique" => "The :attribute has already been taken.", "unique" => "The :attribute has already been taken.",
"url" => "The :attribute format is invalid.", "url" => "The :attribute format is invalid.",
"timezone" => "The :attribute must be a valid zone.", "timezone" => "The :attribute must be a valid zone.",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => [], 'attributes' => [],
]; ];

View File

@ -17,16 +17,20 @@
<div class="register-box-body"> <div class="register-box-body">
<p class="login-box-msg">Register a new account</p> <p class="login-box-msg">Register a new account</p>
{% if host == 'geld.nder.be' %} {% if host == 'geld.nder.be' or host == 'firefly.app' %}
<p class="text-info login-box-msg">Please note that an account on this site will only <p class="text-info login-box-msg">Please note that an account on this site will only
work for one (1) week.</p> work for one (1) month.</p>
{% endif %} {% endif %}
<form role="form" id="register" method="POST" action="{{ URL.to('/auth/register') }}"> <form role="form" id="register" method="POST" action="{{ URL.to('/auth/register') }}">
<input type="hidden" name="_token" value="{{ csrf_token() }}"> <input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input type="email" name="email" class="form-control" placeholder="Email"/> <input type="email" name="email" class="form-control" placeholder="Email" />
{% if host == 'geld.nder.be' or host == 'firefly.app' %}
<p class="help-block">You will receive an email from Firefly III. If your email address
is incorrect, your account may not work.</p>
{% endif %}
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input type="password" class="form-control" placeholder="Password" name="password"/> <input type="password" class="form-control" placeholder="Password" name="password"/>

View File

@ -43,7 +43,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-4 col-sm-3"> <div class="col-lg-6 col-md-4 col-sm-3">
<small>{{ 'spent'|_ }}: <span class="text-danger">{{ (spent*-1)|formatAmountPlain }}</span></small> <small>{{ 'spent'|_ }}: {{ spent|formatAmount }}</small>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -144,7 +144,7 @@
</tr> </tr>
<tr> <tr>
<td style="width:50%;">{{ 'spent'|_ }}</td> <td style="width:50%;">{{ 'spent'|_ }}</td>
<td><span class="text-danger">{{ (budget.spent*-1)|formatAmountPlain }}</a></td> <td>{{ budget.spent|formatAmount }}</a></td>
</tr> </tr>
</table> </table>
</div> </div>
@ -179,7 +179,7 @@
{% block scripts %} {% block scripts %}
<script type="text/javascript"> <script type="text/javascript">
// actually spent bar data: // actually spent bar data:
var spent = {{ spent * -1 }}; var spent = {{ spent * -1 }}; // must be positive for the calculation to work.
var currencySymbol = "{{ getCurrencySymbol()|raw }}"; var currencySymbol = "{{ getCurrencySymbol()|raw }}";
// budgeted data: // budgeted data:

View File

@ -50,7 +50,7 @@
{% for limit in limits %} {% for limit in limits %}
{% for rep in limit.limitRepetitions %} {% for rep in limit.limitRepetitions %}
{% set spentInRep = (spentInRepetitionCorrected(rep)*-1) %} {% set spentInRep = spentInRepetition(rep) %}
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"><a href="{{ route('budgets.show',[budget.id,rep.id]) }}">{{ rep.startdate.formatLocalized(monthFormat) }}</a> <h3 class="box-title"><a href="{{ route('budgets.show',[budget.id,rep.id]) }}">{{ rep.startdate.formatLocalized(monthFormat) }}</a>
@ -62,15 +62,15 @@
{{ 'amount'|_ }}: {{ rep.amount|formatAmount }} {{ 'amount'|_ }}: {{ rep.amount|formatAmount }}
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-6"> <div class="col-lg-6 col-md-6 col-sm-6">
{{ 'spent'|_ }}: <span class="text-danger">{{ spentInRep|formatAmountPlain }}</span> {{ 'spent'|_ }}: {{ spentInRep|formatAmount }}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12"> <div class="col-lg-12 col-md-12 col-sm-12">
{% set overspent = spentInRep > rep.amount %} {% set overspent = rep.amount + spentInRep < 0 %}
{% if overspent %} {% if overspent %}
{% set pct = (spentInRep != 0 ? (rep.amount / spentInRep)*100 : 0) %} {% set pct = (spentInRep != 0 ? (rep.amount / (spentInRep*-1))*100 : 0) %} <!-- must have -1 here -->
<div class="progress progress-striped"> <div class="progress progress-striped">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{ pct|round }}" aria-valuemin="0" <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{ pct|round }}" aria-valuemin="0"
aria-valuemax="100" style="width: {{ pct|round }}%;"></div> aria-valuemax="100" style="width: {{ pct|round }}%;"></div>
@ -79,7 +79,7 @@
</div> </div>
{% else %} {% else %}
{% set amount = rep.amount %} {% set amount = rep.amount %}
{% set pct = (amount != 0 ? ((spentInRep / amount)*100) : 0) %} {% set pct = (amount != 0 ? (((spentInRep*-1) / amount)*100) : 0) %} <!-- must have -1 here -->
<div class="progress progress-striped"> <div class="progress progress-striped">
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="{{ pct|round }}" aria-valuemin="0" <div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="{{ pct|round }}" aria-valuemin="0"
aria-valuemax="100" style="width: {{ pct|round }}%;"></div> aria-valuemax="100" style="width: {{ pct|round }}%;"></div>

View File

@ -36,6 +36,10 @@
Enjoy! Enjoy!
</p> </p>
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:11px;color:#aaa;">
The registration has been created from IP {{ ip }}
</p>
<script type="application/ld+json"> <script type="application/ld+json">
{ {

View File

@ -17,3 +17,5 @@ Password reset:
Documentation: Documentation:
https://github.com/JC5/firefly-iii/wiki/First-use https://github.com/JC5/firefly-iii/wiki/First-use
https://github.com/JC5/firefly-iii/wiki/full-description https://github.com/JC5/firefly-iii/wiki/full-description
The registration has been created from IP {{ ip }}

View File

@ -6,7 +6,7 @@
<div class="popover-navigation"> <div class="popover-navigation">
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-sm btn-default" data-role="prev">&laquo; {{ 'prev'|_ }}</button> <button class="btn btn-sm btn-default" data-role="prev">&laquo; {{ 'prev'|_ }}</button>
<button class="btn btn-sm btn-default" data-role="next">{{ 'prev'|_ }} &raquo;</button> <button class="btn btn-sm btn-default" data-role="next">{{ 'next'|_ }} &raquo;</button>
<button class="btn btn-sm btn-default" data-role="pause-resume" data-pause-text="{{ 'pause'|_ }}" <button class="btn btn-sm btn-default" data-role="pause-resume" data-pause-text="{{ 'pause'|_ }}"
data-resume-text="Resume"> {{ 'pause'|_ }}</button> data-resume-text="Resume"> {{ 'pause'|_ }}</button>
</div> </div>

View File

@ -157,7 +157,7 @@
<script src="js/moment.min.js" type="text/javascript"></script> <script src="js/moment.min.js" type="text/javascript"></script>
<script src="js/daterangepicker.js" type="text/javascript"></script> <script src="js/daterangepicker.js" type="text/javascript"></script>
<script src="dist/js/app.min.js" type="text/javascript"></script> <script src="dist/js/app.min.js" type="text/javascript"></script>
<script type="text/javascript" src="js/accounting.min.js"></script>
<script src="js/bootstrap-tour.min.js" type="text/javascript"></script> <script src="js/bootstrap-tour.min.js" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -24,10 +24,8 @@
<td> <td>
{% if event.amount < 0 %} {% if event.amount < 0 %}
<span class="text-danger">{{ trans('firefly.removed_amount', {amount: (event.amount)|formatAmountPlain})|raw }}</span>
<span class="text-danger">{{ trans('firefly.removed_amount', {amount: (event.amount*-1)|formatAmountPlain})|raw }}</span>
{% else %} {% else %}
<span class="text-success">{{ trans('firefly.added_amount', {amount: (event.amount)|formatAmountPlain})|raw }}</span> <span class="text-success">{{ trans('firefly.added_amount', {amount: (event.amount)|formatAmountPlain})|raw }}</span>
{% endif %} {% endif %}
</td> </td>

View File

@ -1,7 +1,7 @@
{% extends "./layout/default.twig" %} {% extends "./layout/default.twig" %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, start, shared) }} {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, start, end, report_type, accountIds) }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -26,26 +26,26 @@
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-6 col-sm-6"> <div class="col-lg-6 col-md-6 col-sm-6">
{% include 'partials/reports/accounts.twig' %} {% include 'reports/partials/accounts.twig' %}
{% include 'partials/reports/income-vs-expenses.twig' %} {% include 'reports/partials/income-vs-expenses.twig' %}
</div> </div>
<div class="col-lg-3 col-md-3 col-sm-3"> <div class="col-lg-3 col-md-3 col-sm-3">
<!-- income --> <!-- income -->
{% include 'partials/reports/income.twig' %} {% include 'reports/partials/income.twig' %}
</div> </div>
<div class="col-lg-3 col-md-3 col-sm-3"> <div class="col-lg-3 col-md-3 col-sm-3">
<!-- expenses --> <!-- expenses -->
{% include 'partials/reports/expenses.twig' %} {% include 'reports/partials/expenses.twig' %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-8 col-md-8 col-sm-12"> <div class="col-lg-8 col-md-8 col-sm-12">
<!-- budgets --> <!-- budgets -->
{% include 'partials/reports/budgets.twig' %} {% include 'reports/partials/budgets.twig' %}
</div> </div>
<div class="col-lg-4 col-md-4 col-sm-12"> <div class="col-lg-4 col-md-4 col-sm-12">
<!-- categories --> <!-- categories -->
{% include 'partials/reports/categories.twig' %} {% include 'reports/partials/categories.twig' %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -55,12 +55,12 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12"> <div class="col-lg-12 col-md-12 col-sm-12">
{% include 'partials/reports/balance.twig' %} {% include 'reports/partials/balance.twig' %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12"> <div class="col-lg-12 col-md-12 col-sm-12">
{% include 'partials/reports/bills.twig' %} {% include 'reports/partials/bills.twig' %}
</div> </div>
</div> </div>
@ -85,8 +85,13 @@
<script type="text/javascript"> <script type="text/javascript">
var year = {{ start.year }}; var year = {{ start.year }};
var month = {{ start.month }}; var month = {{ start.month }};
var shared = {% if shared %}'/shared'
{% else %}''{% endif %}; // to report another URL:
var startDate = '{{ start.format('Ymd') }}';
var endDate = '{{ end.format('Ymd') }}';
var reportType = '{{ report_type }}';
var accountIds = '{{ accountIds }}';
var incomeTopLength = {{ incomeTopLength }}; var incomeTopLength = {{ incomeTopLength }};
var expenseTopLength = {{ expenseTopLength }}; var expenseTopLength = {{ expenseTopLength }};
var incomeRestShow = false; // starts hidden. var incomeRestShow = false; // starts hidden.
@ -96,5 +101,5 @@
var showTheRestExpense = '{{ trans('firefly.showTheRest',{number:expenseTopLength}) }}'; var showTheRestExpense = '{{ trans('firefly.showTheRest',{number:expenseTopLength}) }}';
var hideTheRestExpense = '{{ trans('firefly.hideTheRest',{number:expenseTopLength}) }}'; var hideTheRestExpense = '{{ trans('firefly.hideTheRest',{number:expenseTopLength}) }}';
</script> </script>
<script type="text/javascript" src="js/reports.js"></script> <script type="text/javascript" src="js/reports/default/month.js"></script>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,141 @@
{% extends "./layout/default.twig" %}
{% block breadcrumbs %}
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, start, end, report_type, accountIds) }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8 col-md-8 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'incomeVsExpenses'|_ }}</h3>
</div>
<div class="box-body">
<canvas id="income-expenses-chart" style="width:100%;height:400px;"></canvas>
</div>
</div>
</div>
<div class="col-lg-4 col-md-4 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'incomeVsExpenses'|_ }}</h3>
</div>
<div class="box-body">
<canvas id="income-expenses-sum-chart" style="width:100%;height:400px;"></canvas>
</div>
</div>
</div>
</div>
{% for account in accounts %}
<div class="row" style="display:none;">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'Asset account'|_ }}: {{ account.name }}</h3>
</div>
<div class="box-body">
<canvas id="account-chart-{{ account.id }}" class="account-chart" data-id="{{ account.id }}" style="width:100%;height:400px;"></canvas>
</div>
</div>
</div>
</div>
{% endfor %}
<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">Selected budgets</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-12">
<p class="well" id="budgets-chart-message" style="display:none;">
Select one or more budgets to generate this chart. Insofar possible, your selection will be saved.
</p>
<canvas id="budgets-chart" class="budgets-chart" style="width:100%;height:400px;"></canvas>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="checkbox-inline">
<input type="checkbox" class="budget-checkbox" name="budgets[]" value="0"> {{ 'noBudget'|_ }}
</label>
{% for budget in budgets %}
<label class="checkbox-inline">
<input type="checkbox" class="budget-checkbox" name="budgets[]" value="{{ budget.id }}"> {{ budget.name }}
</label>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
<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">Selected categories</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-12">
<p class="well" id="categories-chart-message" style="display:none;">
Select one or more categories to generate this chart. Insofar possible, your selection will be saved.
</p>
<canvas id="categories-chart" class="categories-chart" style="width:100%;height:400px;"></canvas>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<label class="checkbox-inline">
<input type="checkbox" class="category-checkbox" name="categories[]" value="0"> {{ 'noCategory'|_ }}
</label>
{% for category in categories %}
<label class="checkbox-inline">
<input type="checkbox" name="categories[]" class="category-checkbox" value="{{ category.id }}"> {{ category.name }}
</label>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="css/bootstrap-sortable.css" type="text/css" media="all"/>
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="js/bootstrap-sortable.js"></script>
<!-- load the libraries and scripts necessary for Google Charts: -->
{% if Config.get('firefly.chart') == 'google' %}
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="js/gcharts.js"></script>
{% endif %}
{% if Config.get('firefly.chart') == 'chartjs' %}
<script type="text/javascript" src="js/Chart.min.js"></script>
<script type="text/javascript" src="js/charts.js"></script>
{% endif %}
<script type="text/javascript">
var year = {{ start.year }};
var month = {{ start.month }};
// to report another URL:
var startDate = '{{ start.format('Ymd') }}';
var endDate = '{{ end.format('Ymd') }}';
var reportType = '{{ report_type }}';
var accountIds = '{{ accountIds }}';
</script>
<script type="text/javascript" src="js/reports/default/multi-year.js"></script>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "./layout/default.twig" %} {% extends "./layout/default.twig" %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, start, shared) }} {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, start, end, report_type, accountIds) }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -13,12 +13,7 @@
<h3 class="box-title">{{ 'incomeVsExpenses'|_ }}</h3> <h3 class="box-title">{{ 'incomeVsExpenses'|_ }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
{% if Config.get('firefly.chart') == 'google' %} <canvas id="income-expenses-chart" style="width:100%;height:400px;"></canvas>
<div id="income-expenses-chart"></div>
{% endif %}
{% if Config.get('firefly.chart') == 'chartjs' %}
<canvas id="income-expenses-chart" style="width:100%;height:400px;"></canvas>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -28,12 +23,7 @@
<h3 class="box-title">{{ 'incomeVsExpenses'|_ }}</h3> <h3 class="box-title">{{ 'incomeVsExpenses'|_ }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
{% if Config.get('firefly.chart') == 'google' %} <canvas id="income-expenses-sum-chart" style="width:100%;height:400px;"></canvas>
<div id="income-expenses-sum-chart"></div>
{% endif %}
{% if Config.get('firefly.chart') == 'chartjs' %}
<canvas id="income-expenses-sum-chart" style="width:100%;height:400px;"></canvas>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -41,15 +31,15 @@
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-6 col-sm-6"> <div class="col-lg-6 col-md-6 col-sm-6">
{% include 'partials/reports/accounts.twig' %} {% include 'reports/partials/accounts.twig' %}
{% include 'partials/reports/income-vs-expenses.twig' %} {% include 'reports/partials/income-vs-expenses.twig' %}
</div> </div>
<div class="col-lg-3 col-md-3 col-sm-3"> <div class="col-lg-3 col-md-3 col-sm-3">
{% include 'partials/reports/income.twig' %} {% include 'reports/partials/income.twig' %}
</div> </div>
<div class="col-lg-3 col-md-3 col-sm-3"> <div class="col-lg-3 col-md-3 col-sm-3">
{% include 'partials/reports/expenses.twig' %} {% include 'reports/partials/expenses.twig' %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -122,8 +112,14 @@
<script type="text/javascript"> <script type="text/javascript">
var year = '{{start.year}}'; var year = '{{start.year}}';
var shared = {% if shared %}'/shared'
{% else %}''{% endif %}; // to report another URL:
var startDate = '{{ start.format('Ymd') }}';
var endDate = '{{ end.format('Ymd') }}';
var reportType = '{{ report_type }}';
var accountIds = '{{ accountIds }}';
var incomeTopLength = {{ incomeTopLength }}; var incomeTopLength = {{ incomeTopLength }};
var expenseTopLength = {{ expenseTopLength }}; var expenseTopLength = {{ expenseTopLength }};
var incomeRestShow = false; // starts hidden. var incomeRestShow = false; // starts hidden.
@ -134,6 +130,6 @@
var hideTheRestExpense = '{{ trans('firefly.hideTheRest',{number:expenseTopLength}) }}'; var hideTheRestExpense = '{{ trans('firefly.hideTheRest',{number:expenseTopLength}) }}';
</script> </script>
<script type="text/javascript" src="js/reports.js"></script> <script type="text/javascript" src="js/reports/default/year.js"></script>
{% endblock %} {% endblock %}

View File

@ -6,45 +6,134 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12"> <div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">{{ 'reportsOwnAccounts'|_ }}</h3> <h3 class="box-title">{{ 'reports'|_ }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
{% for year, entries in months %} <form class="form-horizontal" id="report-form" action="{{ route('reports.index') }}" method="post">
<h4><a href="{{ route('reports.year',year) }}">{{ year }}</a></h4> <input type="hidden" name="_token" value="{{ csrf_token() }}" />
<ul class="list-inline"> <div class="form-group">
{% for month in entries %} <label for="inputReportType" class="col-sm-3 control-label">{{ 'report_type'|_ }}</label>
<li><a href="{{ route('reports.month',[month.year, month.month]) }}">{{ month.formatted }}</a></li>
{% endfor %} <div class="col-sm-9">
</ul> <select name="report_type" class="form-control" id="inputReportType">
{% endfor %} <option label="{{ 'report_type_default'|_ }}" value="default">{{ 'report_type_default'|_ }}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="inputAccounts" class="col-sm-3 control-label">{{ 'report_included_accounts'|_ }}</label>
<div class="col-sm-9">
{% for account in accounts %}
<div class="checkbox">
<label>
<input type="checkbox" class="account-checkbox" name="accounts[]" value="{{ account.id }}">
{{ account.name }}
{% if account.getMeta('accountRole') == 'sharedAsset' %}
({{ 'shared'|_|lower }})
{% endif %}
</label>
</div>
{% endfor %}
<p class="help-block">
{{ 'report_include_help'|_ }}
</p>
</div>
</div>
<div class="form-group">
<label for="inputDateRange" class="col-sm-3 control-label">{{ 'report_date_range'|_ }}</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="inputDateRange" name="daterange"
value="{{ Session.get('start').format('Y-m-d') }} - {{ Session.get('end').format('Y-m-d') }}">
</div>
</div>
<div class="form-group">
<label for="__none__" class="col-sm-3 control-label">{{ 'report_preset_ranges'|_ }}</label>
<div class="col-sm-9">
{% for year, data in months %}
<a href="#" class="date-select" data-start="{{ data.start }}" data-end="{{ data.end }}">{{ year }}</a>
<ul class="list-inline">
{% for month in data.months %}
<li>
<a data-start="{{ month.start }}" data-end="{{ month.end }}" class="date-select" href="#">{{ month.formatted }}</a>
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-default">{{ 'submit'|_ }}</button>
</div>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">{{ 'reportsOwnAccountsAndShared'|_ }}</h3> <h3 class="box-title">{{ 'quick_link_reports'|_ }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
<h4>{{ 'quick_link_default_report'|_ }}</h4>
{% for year, entries in months %} <ul>
<h4><a href="{{ route('reports.year',[year, 'shared']) }}">{{ year }}</a></h4> <li>
<ul class="list-inline"> <a href="{{ route('reports.report',
{% for month in entries %} ['default',
<li><a href="{{ route('reports.month',[month.year, month.month,'shared']) }}">{{ month.formatted }}</a></li> Session.get('start').startOfMonth.format('Ymd'),
{% endfor %} Session.get('start').endOfMonth.format('Ymd'),
</ul> accountList
{% endfor %} ]) }}">{{ 'report_this_month_quick'|_ }}</a>
</li>
<li>
<a href="{{ route('reports.report',
['default',
Session.get('start').startOfYear.format('Ymd'),
Session.get('start').endOfYear.format('Ymd'),
accountList
]) }}">{{ 'report_this_year_quick'|_ }}</a>
</li>
<li>
<a href="{{ route('reports.report',
['default',
start.format('Ymd'),
Session.get('end').endOfMonth.format('Ymd'),
accountList
]) }}">{{ 'report_all_time_quick'|_ }}</a>
</li>
</ul>
<p>
<em>{{ 'reports_can_bookmark'|_ }}</em>
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script type="text/javascript" src="js/reports.js"></script> <script type="text/javascript">
var reportURL = "{{ route('reports.report', ['']) }}";
var minDate = "{{ start.format('m/d/Y') }}";
var picker;
</script>
<script type="text/javascript" src="js/reports/index.js"></script>
{% endblock %} {% endblock %}

View File

@ -13,7 +13,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for account in accounts.getAccounts %} {% for account in accountReport.getAccounts %}
<tr> <tr>
<td> <td>
<a href="{{ route('accounts.show',account.id) }}" title="{{ account.name }}">{{ account.name }}</a> <a href="{{ route('accounts.show',account.id) }}" title="{{ account.name }}">{{ account.name }}</a>
@ -27,9 +27,9 @@
<tfoot> <tfoot>
<tr> <tr>
<td><em>{{ 'sumOfSums'|_ }}</em></td> <td><em>{{ 'sumOfSums'|_ }}</em></td>
<td>{{ accounts.getStart|formatAmount }}</td> <td>{{ accountReport.getStart|formatAmount }}</td>
<td>{{ accounts.getEnd|formatAmount }}</td> <td>{{ accountReport.getEnd|formatAmount }}</td>
<td>{{ accounts.getDifference|formatAmount }}</td> <td>{{ accountReport.getDifference|formatAmount }}</td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>

View File

@ -14,7 +14,6 @@
<th> <th>
{{ 'leftInBudget'|_ }} {{ 'leftInBudget'|_ }}
</th> </th>
<th>{{ 'sum'|_ }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -35,7 +34,7 @@
{% for balanceEntry in balanceLine.getBalanceEntries %} {% for balanceEntry in balanceLine.getBalanceEntries %}
<td> <td>
{% if balanceEntry.getSpent != 0 %} {% if balanceEntry.getSpent != 0 %}
<span class="text-danger">{{ (balanceEntry.getSpent*-1)|formatAmountPlain }}</span> <span class="text-danger">{{ (balanceEntry.getSpent)|formatAmountPlain }}</span>
{% endif %} {% endif %}
{% if balanceEntry.getLeft != 0 %} {% if balanceEntry.getLeft != 0 %}
<span class="text-success">{{ (balanceEntry.getLeft)|formatAmountPlain }}</span> <span class="text-success">{{ (balanceEntry.getLeft)|formatAmountPlain }}</span>
@ -43,14 +42,7 @@
</td> </td>
{% endfor %} {% endfor %}
<td> <td>
{% if balanceLine.leftOfRepetition != 0 %} {{ balanceLine.leftOfRepetition|formatAmount }}
{{ balanceLine.leftOfRepetition|formatAmount }}
{% endif %}
</td>
<td>
{% if balanceLine.sumOfLeft != 0 %}
{{ balanceLine.sumOfLeft|formatAmount }}
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -39,7 +39,7 @@
{% endif %} {% endif %}
<td data-value="{{ (line.getMax - line.getAmount) }}"> <td data-value="{{ (line.getMax - line.getAmount) }}">
{% if line.isActive %} {% if line.isActive %}
{{ (line.getMax - line.getAmount)|formatAmount }} {{ (line.getMax + line.getAmount)|formatAmount }}
{% endif %} {% endif %}
</td> </td>

View File

@ -41,15 +41,17 @@
{% if budgetLine.getSpent != 0 %} {% if budgetLine.getSpent != 0 %}
{{ budgetLine.getSpent|formatAmount }} {{ budgetLine.getSpent|formatAmount }}
{% endif %} {% endif %}
</td>
<td> {% if budgetLine.getSpent == 0 %}
{% if budgetLine.getLeft != 0 %} {{ budgetLine.getSpent|formatAmount }}
{{ budgetLine.getLeft|formatAmount }}
{% endif %} {% endif %}
</td> </td>
<td>
{{ budgetLine.getLeft|formatAmount }}
</td>
<td> <td>
{% if budgetLine.getOverspent != 0 %} {% if budgetLine.getOverspent != 0 %}
<span class="text-danger">{{ budgetLine.getOverspent|formatAmountPlain }}</span> {{ budgetLine.getOverspent|formatAmount }}
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -59,7 +61,14 @@
<tr> <tr>
<td colspan="2"><em>{{ 'sum'|_ }}</em></td> <td colspan="2"><em>{{ 'sum'|_ }}</em></td>
<td>{{ budgets.getBudgeted|formatAmount }}</td> <td>{{ budgets.getBudgeted|formatAmount }}</td>
<td>{{ budgets.getSpent|formatAmount }}</td> <td>
{% if budgets.getSpent != 0 %}
<span class="text-danger">{{ budgets.getSpent|formatAmountPlain }}</span>
{% endif %}
{% if budgets.getSpent == 0 %}
{{ budgets.getSpent|formatAmount }}
{% endif %}
</td>
<td>{{ budgets.getLeft|formatAmount }}</td> <td>{{ budgets.getLeft|formatAmount }}</td>
<td><span class="text-danger">{{ budgets.getOverspent|formatAmountPlain }}</span></td> <td><span class="text-danger">{{ budgets.getOverspent|formatAmountPlain }}</span></td>
</tr> </tr>

View File

@ -16,14 +16,14 @@
<td> <td>
<a href="{{ route('categories.show',cat.id) }}">{{ cat.name }}</a> <a href="{{ route('categories.show',cat.id) }}">{{ cat.name }}</a>
</td> </td>
<td><span class="text-danger">{{ (cat.spent * -1)|formatAmountPlain }}</span></td> <td>{{ cat.spent|formatAmount }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td><em>{{ 'sum'|_ }}</em></td> <td><em>{{ 'sum'|_ }}</em></td>
<td class="text-danger">{{ (categories.getTotal * -1)|formatAmountPlain }}</td> <td>{{ categories.getTotal|formatAmount }}</td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>

View File

@ -18,7 +18,7 @@
<small>{{ expense.count }} {{ 'transactions'|_|lower }}</small> <small>{{ expense.count }} {{ 'transactions'|_|lower }}</small>
{% endif %} {% endif %}
</td> </td>
<td><span class="text-danger">{{ (expense.amount)|formatAmountPlain }}</span></td> <td>{{ (expense.amount)|formatAmount }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -32,7 +32,7 @@
{% endif %} {% endif %}
<tr> <tr>
<td><em>{{ 'sum'|_ }}</em></td> <td><em>{{ 'sum'|_ }}</em></td>
<td><span class="text-danger">{{ (expenses.getTotal)|formatAmountPlain }}</span></td> <td>{{ (expenses.getTotal)|formatAmount }}</td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>

View File

@ -10,11 +10,11 @@
</tr> </tr>
<tr> <tr>
<td>{{ 'out'|_ }}</td> <td>{{ 'out'|_ }}</td>
<td><span class="text-danger">{{ (expenses.getTotal)|formatAmountPlain }}</span></td> <td>{{ (expenses.getTotal)|formatAmount }}</td>
</tr> </tr>
<tr> <tr>
<td>{{ 'difference'|_ }}</td> <td>{{ 'difference'|_ }}</td>
<td>{{ (incomes.getTotal + (expenses.getTotal * -1))|formatAmount }}</td> <td>{{ (incomes.getTotal + expenses.getTotal)|formatAmount }}</td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@ -43,6 +43,12 @@
<td><a href="{{ route('categories.show',category.id) }}">{{ category.name }}</a></td> <td><a href="{{ route('categories.show',category.id) }}">{{ category.name }}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
{% if journal.bill %}
<tr>
<td>{{ 'bill'|_ }}</td>
<td><a href="{{ route('bills.show', journal.bill_id) }}">{{ journal.bill.name }}</a></td>
</tr>
{% endif %}
{% if journal.tags|length > 0 %} {% if journal.tags|length > 0 %}
<tr> <tr>
<td>{{ 'tags'|_ }}</td> <td>{{ 'tags'|_ }}</td>

View File

@ -1,9 +1,5 @@
<?php <?php
use Carbon\Carbon;
use FireflyIII\Models\TransactionCurrency;
use League\FactoryMuffin\Facade as FactoryMuffin;
/** /**
* Class TestCase * Class TestCase
*/ */
@ -52,20 +48,4 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
{ {
parent::tearDown(); parent::tearDown();
} }
/**
* @param string $class
*
* @return Mockery\MockInterface
*/
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
} }

View File

@ -1,20 +0,0 @@
<?php
/**
* Class ExampleTest
*/
class ExampleTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
}
protected function tearDown()
{
}
// tests
public function testMe()
{
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace FireflyIII\Models;
use TestCase;
class TransactionTypeTest extends TestCase
{
public function testIsWithdrawal()
{
$transactionType = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
$this->assertTrue($transactionType->isWithdrawal());
}
public function testIsDeposit()
{
$transactionType = TransactionType::whereType(TransactionType::DEPOSIT)->first();
$this->assertTrue($transactionType->isDeposit());
}
public function testIsTransfer()
{
$transactionType = TransactionType::whereType(TransactionType::TRANSFER)->first();
$this->assertTrue($transactionType->isTransfer());
}
public function testIsOpeningBalance()
{
$transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
$this->assertTrue($transactionType->isOpeningBalance());
}
}