diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4db7ca588f..107e66c744 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,19 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
-## [4.2.1] - 2015-05-25
+## [4.2.2] - 2016-12-18
+### Added
+- New budget report (still a bit of a beta)
+- Can now edit user
+
+### Changed
+- New config for specific events. Still need to build Notifications.
+
+### Fixed
+- Various bugs
+- Issue #472 thanks to @zjean
+
+## [4.2.1] - 2016-12-09
### Added
- BIC support (see #430)
- New category report section and chart (see the general financial report)
diff --git a/README.md b/README.md
index 1043c10aa8..b6a4297b78 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,11 @@
## A personal finances manager
-[](https://i.nder.be/hhfv03hp) [](https://i.nder.be/hhmwmqw9)
+[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
-[](https://i.nder.be/g63q05m0) [](https://i.nder.be/c2g30ngg)
+[](https://i.nder.be/ccn0u2mp) [](https://i.nder.be/gm8hbh7z)
+
+_(You can click on the images for a better view)_
"Firefly III" is a financial manager. It can help you keep track of expenses, income, budgets and everything in between. It even supports credit cards, shared household accounts and savings accounts! It's pretty fancy. You should use it to save and organise money.
diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php
index 830ce56903..89767b2240 100644
--- a/app/Console/Commands/CreateImport.php
+++ b/app/Console/Commands/CreateImport.php
@@ -50,7 +50,7 @@ class CreateImport extends Command
}
/**
- *
+ *
*/
public function handle()
{
diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php
index 2b21ed83fc..07cebe2fdf 100644
--- a/app/Console/Commands/UpgradeDatabase.php
+++ b/app/Console/Commands/UpgradeDatabase.php
@@ -73,10 +73,10 @@ class UpgradeDatabase extends Command
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereNull('transaction_journals.deleted_at')
- ->whereNull('transactions.deleted_at')
- ->groupBy(['transaction_journals.id'])
- ->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
+ ->whereNull('transaction_journals.deleted_at')
+ ->whereNull('transactions.deleted_at')
+ ->groupBy(['transaction_journals.id'])
+ ->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
->mergeBindings($subQuery->getQuery())
@@ -98,9 +98,9 @@ class UpgradeDatabase extends Command
try {
/** @var Transaction $opposing */
$opposing = Transaction::where('transaction_journal_id', $journalId)
- ->where('amount', $amount)->where('identifier', '=', 0)
- ->whereNotIn('id', $processed)
- ->first();
+ ->where('amount', $amount)->where('identifier', '=', 0)
+ ->whereNotIn('id', $processed)
+ ->first();
} catch (QueryException $e) {
Log::error($e->getMessage());
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php
index 7159976171..1d295abf0f 100644
--- a/app/Console/Commands/VerifyDatabase.php
+++ b/app/Console/Commands/VerifyDatabase.php
@@ -17,8 +17,6 @@ use Crypt;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
-use FireflyIII\Models\Category;
-use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@@ -103,12 +101,12 @@ class VerifyDatabase extends Command
private function reportAccounts()
{
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
- ->leftJoin('users', 'accounts.user_id', '=', 'users.id')
- ->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
- ->whereNull('transactions.account_id')
- ->get(
- ['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
- );
+ ->leftJoin('users', 'accounts.user_id', '=', 'users.id')
+ ->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
+ ->whereNull('transactions.account_id')
+ ->get(
+ ['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
+ );
/** @var stdClass $entry */
foreach ($set as $entry) {
@@ -125,10 +123,10 @@ class VerifyDatabase extends Command
private function reportBudgetLimits()
{
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
- ->leftJoin('users', 'budgets.user_id', '=', 'users.id')
- ->groupBy(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email'])
- ->whereNull('budget_limits.id')
- ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']);
+ ->leftJoin('users', 'budgets.user_id', '=', 'users.id')
+ ->groupBy(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email'])
+ ->whereNull('budget_limits.id')
+ ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']);
/** @var stdClass $entry */
foreach ($set as $entry) {
@@ -147,20 +145,20 @@ class VerifyDatabase extends Command
private function reportDeletedAccounts()
{
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->whereNotNull('accounts.deleted_at')
- ->whereNotNull('transactions.id')
- ->where(
- function (Builder $q) {
- $q->whereNull('transactions.deleted_at');
- $q->orWhereNull('transaction_journals.deleted_at');
- }
- )
- ->get(
- ['accounts.id as account_id', 'accounts.deleted_at as account_deleted_at', 'transactions.id as transaction_id',
- 'transactions.deleted_at as transaction_deleted_at', 'transaction_journals.id as journal_id',
- 'transaction_journals.deleted_at as journal_deleted_at']
- );
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->whereNotNull('accounts.deleted_at')
+ ->whereNotNull('transactions.id')
+ ->where(
+ function (Builder $q) {
+ $q->whereNull('transactions.deleted_at');
+ $q->orWhereNull('transaction_journals.deleted_at');
+ }
+ )
+ ->get(
+ ['accounts.id as account_id', 'accounts.deleted_at as account_deleted_at', 'transactions.id as transaction_id',
+ 'transactions.deleted_at as transaction_deleted_at', 'transaction_journals.id as journal_id',
+ 'transaction_journals.deleted_at as journal_deleted_at']
+ );
/** @var stdClass $entry */
foreach ($set as $entry) {
$date = is_null($entry->transaction_deleted_at) ? $entry->journal_deleted_at : $entry->transaction_deleted_at;
@@ -185,14 +183,17 @@ class VerifyDatabase extends Command
];
foreach ($configuration as $transactionType => $accountTypes) {
$set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
- ->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
- ->leftJoin('users', 'users.id', '=', 'transaction_journals.user_id')
- ->where('transaction_types.type', $transactionType)
- ->whereIn('account_types.type', $accountTypes)
- ->whereNull('transaction_journals.deleted_at')
- ->get(['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type', 'transaction_types.type']);
+ ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
+ ->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
+ ->leftJoin('users', 'users.id', '=', 'transaction_journals.user_id')
+ ->where('transaction_types.type', $transactionType)
+ ->whereIn('account_types.type', $accountTypes)
+ ->whereNull('transaction_journals.deleted_at')
+ ->get(
+ ['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type',
+ 'transaction_types.type']
+ );
foreach ($set as $entry) {
$this->error(
sprintf(
@@ -215,17 +216,17 @@ class VerifyDatabase extends Command
private function reportJournals()
{
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereNotNull('transaction_journals.deleted_at')// USE THIS
- ->whereNull('transactions.deleted_at')
- ->whereNotNull('transactions.id')
- ->get(
- [
- 'transaction_journals.id as journal_id',
- 'transaction_journals.description',
- 'transaction_journals.deleted_at as journal_deleted',
- 'transactions.id as transaction_id',
- 'transactions.deleted_at as transaction_deleted_at']
- );
+ ->whereNotNull('transaction_journals.deleted_at')// USE THIS
+ ->whereNull('transactions.deleted_at')
+ ->whereNotNull('transactions.id')
+ ->get(
+ [
+ 'transaction_journals.id as journal_id',
+ 'transaction_journals.description',
+ 'transaction_journals.deleted_at as journal_deleted',
+ 'transactions.id as transaction_id',
+ 'transactions.deleted_at as transaction_deleted_at']
+ );
/** @var stdClass $entry */
foreach ($set as $entry) {
$this->error(
@@ -241,9 +242,9 @@ class VerifyDatabase extends Command
private function reportNoTransactions()
{
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->groupBy('transaction_journals.id')
- ->whereNull('transactions.transaction_journal_id')
- ->get(['transaction_journals.id']);
+ ->groupBy('transaction_journals.id')
+ ->whereNull('transactions.transaction_journal_id')
+ ->get(['transaction_journals.id']);
foreach ($set as $entry) {
$this->error(
@@ -262,11 +263,11 @@ class VerifyDatabase extends Command
$class = sprintf('FireflyIII\Models\%s', ucfirst($name));
$field = $name == 'tag' ? 'tag' : 'name';
$set = $class::leftJoin($name . '_transaction_journal', $plural . '.id', '=', $name . '_transaction_journal.' . $name . '_id')
- ->leftJoin('users', $plural . '.user_id', '=', 'users.id')
- ->distinct()
- ->whereNull($name . '_transaction_journal.' . $name . '_id')
- ->whereNull($plural . '.deleted_at')
- ->get([$plural . '.id', $plural . '.' . $field . ' as name', $plural . '.user_id', 'users.email']);
+ ->leftJoin('users', $plural . '.user_id', '=', 'users.id')
+ ->distinct()
+ ->whereNull($name . '_transaction_journal.' . $name . '_id')
+ ->whereNull($plural . '.deleted_at')
+ ->get([$plural . '.id', $plural . '.' . $field . ' as name', $plural . '.user_id', 'users.email']);
/** @var stdClass $entry */
foreach ($set as $entry) {
@@ -309,12 +310,12 @@ class VerifyDatabase extends Command
private function reportTransactions()
{
$set = Transaction::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereNotNull('transactions.deleted_at')
- ->whereNull('transaction_journals.deleted_at')
- ->get(
- ['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id',
- 'transaction_journals.deleted_at']
- );
+ ->whereNotNull('transactions.deleted_at')
+ ->whereNull('transaction_journals.deleted_at')
+ ->get(
+ ['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id',
+ 'transaction_journals.deleted_at']
+ );
/** @var stdClass $entry */
foreach ($set as $entry) {
$this->error(
@@ -330,10 +331,10 @@ class VerifyDatabase extends Command
private function reportTransfersBudgets()
{
$set = TransactionJournal::distinct()
- ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
- ->where('transaction_types.type', TransactionType::TRANSFER)
- ->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.id']);
+ ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
+ ->where('transaction_types.type', TransactionType::TRANSFER)
+ ->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.id']);
/** @var TransactionJournal $entry */
foreach ($set as $entry) {
diff --git a/app/Events/BlockedBadLogin.php b/app/Events/BlockedBadLogin.php
new file mode 100644
index 0000000000..fb984abd04
--- /dev/null
+++ b/app/Events/BlockedBadLogin.php
@@ -0,0 +1,41 @@
+email = $email;
+ $this->ipAddress = $ipAddress;
+ }
+}
diff --git a/app/Events/BlockedUseOfDomain.php b/app/Events/BlockedUseOfDomain.php
new file mode 100644
index 0000000000..0ccacea749
--- /dev/null
+++ b/app/Events/BlockedUseOfDomain.php
@@ -0,0 +1,42 @@
+email = $email;
+ $this->ipAddress = $ipAddress;
+ }
+}
diff --git a/app/Events/BlockedUseOfEmail.php b/app/Events/BlockedUseOfEmail.php
new file mode 100644
index 0000000000..8b9d7cbed5
--- /dev/null
+++ b/app/Events/BlockedUseOfEmail.php
@@ -0,0 +1,42 @@
+email = $email;
+ $this->ipAddress = $ipAddress;
+ }
+}
diff --git a/app/Events/BlockedUserLogin.php b/app/Events/BlockedUserLogin.php
new file mode 100644
index 0000000000..fd12fb8a36
--- /dev/null
+++ b/app/Events/BlockedUserLogin.php
@@ -0,0 +1,42 @@
+user = $user;
+ $this->ipAddress = $ipAddress;
+ }
+}
diff --git a/app/Events/DeletedUser.php b/app/Events/DeletedUser.php
new file mode 100644
index 0000000000..2670b2c48a
--- /dev/null
+++ b/app/Events/DeletedUser.php
@@ -0,0 +1,38 @@
+email = $email;
+ }
+}
\ No newline at end of file
diff --git a/app/Events/LockedOutUser.php b/app/Events/LockedOutUser.php
new file mode 100644
index 0000000000..c498ad1ed7
--- /dev/null
+++ b/app/Events/LockedOutUser.php
@@ -0,0 +1,41 @@
+email = $email;
+ $this->ipAddress = $ipAddress;
+ }
+}
diff --git a/app/Export/Collector/JournalExportCollector.php b/app/Export/Collector/JournalExportCollector.php
index a3e97c957f..57d65fc4e7 100644
--- a/app/Export/Collector/JournalExportCollector.php
+++ b/app/Export/Collector/JournalExportCollector.php
@@ -293,55 +293,55 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac
{
$accountIds = $this->accounts->pluck('id')->toArray();
$this->workSet = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin(
- 'transactions AS opposing', function (JoinClause $join) {
- $join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
- ->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
- ->where('transactions.identifier', '=', DB::raw('opposing.identifier'));
- }
- )
- ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
- ->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
- ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
- ->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id')
- ->whereIn('transactions.account_id', $accountIds)
- ->where('transaction_journals.user_id', $this->job->user_id)
- ->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $this->end->format('Y-m-d'))
- ->where('transaction_journals.completed', 1)
- ->whereNull('transaction_journals.deleted_at')
- ->whereNull('transactions.deleted_at')
- ->whereNull('opposing.deleted_at')
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transactions.identifier', 'ASC')
- ->get(
- [
- 'transactions.id',
- 'transactions.amount',
- 'transactions.description',
- 'transactions.account_id',
- 'accounts.name as account_name',
- 'accounts.encrypted as account_name_encrypted',
- 'transactions.identifier',
+ ->leftJoin(
+ 'transactions AS opposing', function (JoinClause $join) {
+ $join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
+ ->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
+ ->where('transactions.identifier', '=', DB::raw('opposing.identifier'));
+ }
+ )
+ ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
+ ->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
+ ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
+ ->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id')
+ ->whereIn('transactions.account_id', $accountIds)
+ ->where('transaction_journals.user_id', $this->job->user_id)
+ ->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
+ ->where('transaction_journals.date', '<=', $this->end->format('Y-m-d'))
+ ->where('transaction_journals.completed', 1)
+ ->whereNull('transaction_journals.deleted_at')
+ ->whereNull('transactions.deleted_at')
+ ->whereNull('opposing.deleted_at')
+ ->orderBy('transaction_journals.date', 'DESC')
+ ->orderBy('transactions.identifier', 'ASC')
+ ->get(
+ [
+ 'transactions.id',
+ 'transactions.amount',
+ 'transactions.description',
+ 'transactions.account_id',
+ 'accounts.name as account_name',
+ 'accounts.encrypted as account_name_encrypted',
+ 'transactions.identifier',
- 'opposing.id as opposing_id',
- 'opposing.amount AS opposing_amount',
- 'opposing.description as opposing_description',
- 'opposing.account_id as opposing_account_id',
- 'opposing_accounts.name as opposing_account_name',
- 'opposing_accounts.encrypted as opposing_account_encrypted',
- 'opposing.identifier as opposing_identifier',
+ 'opposing.id as opposing_id',
+ 'opposing.amount AS opposing_amount',
+ 'opposing.description as opposing_description',
+ 'opposing.account_id as opposing_account_id',
+ 'opposing_accounts.name as opposing_account_name',
+ 'opposing_accounts.encrypted as opposing_account_encrypted',
+ 'opposing.identifier as opposing_identifier',
- 'transaction_journals.id as transaction_journal_id',
- 'transaction_journals.date',
- 'transaction_journals.description as journal_description',
- 'transaction_journals.encrypted as journal_encrypted',
- 'transaction_journals.transaction_type_id',
- 'transaction_types.type as transaction_type',
- 'transaction_journals.transaction_currency_id',
- 'transaction_currencies.code AS transaction_currency_code',
+ 'transaction_journals.id as transaction_journal_id',
+ 'transaction_journals.date',
+ 'transaction_journals.description as journal_description',
+ 'transaction_journals.encrypted as journal_encrypted',
+ 'transaction_journals.transaction_type_id',
+ 'transaction_types.type as transaction_type',
+ 'transaction_journals.transaction_currency_id',
+ 'transaction_currencies.code AS transaction_currency_code',
- ]
- );
+ ]
+ );
}
}
diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php
index c4d030a10c..1895cb8a5e 100644
--- a/app/Export/Collector/UploadCollector.php
+++ b/app/Export/Collector/UploadCollector.php
@@ -94,7 +94,7 @@ class UploadCollector extends BasicCollector implements CollectorInterface
*
* @return bool
*/
- private function collectVintageUploads():bool
+ private function collectVintageUploads(): bool
{
// grab upload directory.
$files = $this->uploadDisk->files();
diff --git a/app/Export/Processor.php b/app/Export/Processor.php
index 62fbe7a6a0..15960d63f6 100644
--- a/app/Export/Processor.php
+++ b/app/Export/Processor.php
@@ -30,7 +30,7 @@ use ZipArchive;
*
* @package FireflyIII\Export
*/
-class Processor
+class Processor implements ProcessorInterface
{
/** @var Collection */
diff --git a/app/Export/ProcessorInterface.php b/app/Export/ProcessorInterface.php
new file mode 100644
index 0000000000..364f4730d4
--- /dev/null
+++ b/app/Export/ProcessorInterface.php
@@ -0,0 +1,67 @@
+ 1,
- 'labels' => [], 'datasets' => [[
- 'label' => trans('firefly.spent'),
- 'data' => []]]];
- foreach ($accounts as $account) {
- if ($account->difference > 0) {
- $data['labels'][] = $account->name;
- $data['datasets'][0]['data'][] = $account->difference;
- }
- }
-
- return $data;
- }
-
- /**
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function frontpage(Collection $accounts, Carbon $start, Carbon $end): array
- {
- // language:
- $format = (string)trans('config.month_and_day');
- $data = ['count' => 0, 'labels' => [], 'datasets' => [],];
- $current = clone $start;
- while ($current <= $end) {
- $data['labels'][] = $current->formatLocalized($format);
- $current->addDay();
- }
-
- foreach ($accounts as $account) {
- $data['datasets'][] = [
- 'label' => $account->name,
- 'fillColor' => 'rgba(220,220,220,0.2)',
- 'strokeColor' => 'rgba(220,220,220,1)',
- 'pointColor' => 'rgba(220,220,220,1)',
- 'pointStrokeColor' => '#fff',
- 'pointHighlightFill' => '#fff',
- 'pointHighlightStroke' => 'rgba(220,220,220,1)',
- 'data' => $account->balances,
- ];
- }
- $data['count'] = count($data['datasets']);
-
- return $data;
- }
-
- /**
- * @param array $values
- * @param array $names
- *
- * @return array
- */
- public function pieChart(array $values, array $names): array
- {
- $data = [
- 'datasets' => [
- 0 => [],
- ],
- 'labels' => [],
- ];
- $index = 0;
- foreach ($values as $categoryId => $value) {
-
- // make larger than 0
- if (bccomp($value, '0') === -1) {
- $value = bcmul($value, '-1');
- }
-
- $data['datasets'][0]['data'][] = round($value, 2);
- $data['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index);
- $data['labels'][] = $names[$categoryId];
- $index++;
- }
-
- return $data;
- }
-
- /**
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array
- {
- $data = [
- 'count' => 1,
- 'labels' => [], 'datasets' => [[
- 'label' => trans('firefly.earned'),
- 'data' => []]]];
- foreach ($accounts as $account) {
- if ($account->difference > 0) {
- $data['labels'][] = $account->name;
- $data['datasets'][0]['data'][] = $account->difference;
- }
- }
-
- return $data;
- }
-
- /**
- * @param Account $account
- * @param array $labels
- * @param array $dataSet
- *
- * @return array
- */
- public function single(Account $account, array $labels, array $dataSet): array
- {
- $data = [
- 'count' => 1,
- 'labels' => $labels,
- 'datasets' => [
- [
- 'label' => $account->name,
- 'data' => $dataSet,
- ],
- ],
- ];
-
- return $data;
- }
-}
diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php
new file mode 100644
index 0000000000..a4a4caa8c1
--- /dev/null
+++ b/app/Generator/Chart/Basic/ChartJsGenerator.php
@@ -0,0 +1,137 @@
+ 'label of set',
+ * 'type' => bar or line, optional
+ * 'entries' =>
+ * [
+ * 'label-of-entry' => 'value'
+ * ]
+ * ]
+ * 1: [
+ * 'label' => 'label of another set',
+ * 'type' => bar or line, optional
+ * 'entries' =>
+ * [
+ * 'label-of-entry' => 'value'
+ * ]
+ * ]
+ *
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ public function multiSet(array $data): array
+ {
+ reset($data);
+ $first = current($data);
+ $labels = array_keys($first['entries']);
+
+ $chartData = [
+ 'count' => count($data),
+ 'labels' => $labels, // take ALL labels from the first set.
+ 'datasets' => [],
+ ];
+ unset($first, $labels);
+
+ foreach ($data as $set) {
+ $chartData['datasets'][] = [
+ 'label' => $set['label'],
+ 'type' => $set['type'] ?? 'line',
+ 'data' => array_values($set['entries']),
+ ];
+ }
+
+ return $chartData;
+ }
+
+ /**
+ * Expects data as:
+ *
+ * key => value
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ public function pieChart(array $data): array
+ {
+ $chartData = [
+ 'datasets' => [
+ 0 => [],
+ ],
+ 'labels' => [],
+ ];
+ $index = 0;
+ foreach ($data as $key => $value) {
+
+ // make larger than 0
+ if (bccomp($value, '0') === -1) {
+ $value = bcmul($value, '-1');
+ }
+
+ $chartData['datasets'][0]['data'][] = round($value, 2);
+ $chartData['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index);
+ $chartData['labels'][] = $key;
+ $index++;
+ }
+
+ return $chartData;
+ }
+
+ /**
+ * Will generate a (ChartJS) compatible array from the given input. Expects this format:
+ *
+ * 'label-of-entry' => value
+ * 'label-of-entry' => value
+ *
+ * @param string $setLabel
+ * @param array $data
+ *
+ * @return array
+ */
+ public function singleSet(string $setLabel, array $data): array
+ {
+ $chartData = [
+ 'count' => 1,
+ 'labels' => array_keys($data), // take ALL labels from the first set.
+ 'datasets' => [
+ [
+ 'label' => $setLabel,
+ 'data' => array_values($data),
+ ],
+ ],
+ ];
+
+ return $chartData;
+ }
+}
\ No newline at end of file
diff --git a/app/Generator/Chart/Basic/GeneratorInterface.php b/app/Generator/Chart/Basic/GeneratorInterface.php
new file mode 100644
index 0000000000..8299851822
--- /dev/null
+++ b/app/Generator/Chart/Basic/GeneratorInterface.php
@@ -0,0 +1,73 @@
+ 'label of set',
+ * 'entries' =>
+ * [
+ * 'label-of-entry' => 'value'
+ * ]
+ * ]
+ * 1: [
+ * 'label' => 'label of another set',
+ * 'entries' =>
+ * [
+ * 'label-of-entry' => 'value'
+ * ]
+ * ]
+ *
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ public function multiSet(array $data): array;
+
+ /**
+ * Expects data as:
+ *
+ * key => value
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ public function pieChart(array $data): array;
+
+ /**
+ * Will generate a (ChartJS) compatible array from the given input. Expects this format:
+ *
+ * 'label-of-entry' => value
+ * 'label-of-entry' => value
+ *
+ * @param string $setLabel
+ * @param array $data
+ *
+ * @return array
+ */
+ public function singleSet(string $setLabel, array $data): array;
+
+}
\ No newline at end of file
diff --git a/app/Generator/Chart/Bill/BillChartGeneratorInterface.php b/app/Generator/Chart/Bill/BillChartGeneratorInterface.php
deleted file mode 100644
index 27b1c11cef..0000000000
--- a/app/Generator/Chart/Bill/BillChartGeneratorInterface.php
+++ /dev/null
@@ -1,44 +0,0 @@
- [
- [
- 'data' => [round($unpaid, 2), round(bcmul($paid, '-1'), 2)],
- 'backgroundColor' => [ChartColour::getColour(0), ChartColour::getColour(1)],
- ],
-
- ],
- 'labels' => [strval(trans('firefly.unpaid')), strval(trans('firefly.paid'))],
-
- ];
-
- return $data;
- }
-
- /**
- * @param Bill $bill
- * @param Collection $entries
- *
- * @return array
- */
- public function single(Bill $bill, Collection $entries): array
- {
- $format = (string)trans('config.month');
- $data = ['count' => 3, 'labels' => [], 'datasets' => [],];
- $minAmount = [];
- $maxAmount = [];
- $actualAmount = [];
- /** @var Transaction $entry */
- foreach ($entries as $entry) {
- $data['labels'][] = $entry->date->formatLocalized($format);
- $minAmount[] = round($bill->amount_min, 2);
- $maxAmount[] = round($bill->amount_max, 2);
- // journalAmount has been collected in BillRepository::getJournals
- $actualAmount[] = bcmul($entry->transaction_amount, '-1');
- }
-
- $data['datasets'][] = [
- 'type' => 'bar',
- 'label' => trans('firefly.minAmount'),
- 'data' => $minAmount,
- ];
- $data['datasets'][] = [
- 'type' => 'line',
- 'label' => trans('firefly.billEntry'),
- 'data' => $actualAmount,
- ];
- $data['datasets'][] = [
- 'type' => 'bar',
- 'label' => trans('firefly.maxAmount'),
- 'data' => $maxAmount,
- ];
-
- $data['count'] = count($data['datasets']);
-
- return $data;
- }
-}
diff --git a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php
deleted file mode 100644
index 1abaa89746..0000000000
--- a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php
+++ /dev/null
@@ -1,54 +0,0 @@
- [],
- 'datasets' => [
- [
- 'label' => 'Amount',
- 'data' => [],
- ],
- ],
- ];
-
- /** @var array $entry */
- foreach ($entries as $entry) {
- $data['labels'][] = $entry[0]->formatLocalized($format);
- $data['datasets'][0]['data'][] = $entry[1];
-
- }
-
- $data['count'] = count($data['datasets']);
-
- return $data;
- }
-
- /**
- * @param Collection $entries
- *
- * @return array
- */
- public function frontpage(Collection $entries): array
- {
- $data = [
- 'count' => 0,
- 'labels' => [],
- 'datasets' => [],
- ];
- $left = [];
- $spent = [];
- $overspent = [];
- $filtered = $entries->filter(
- function ($entry) {
- return ($entry[1] != 0 || $entry[2] != 0 || $entry[3] != 0);
- }
- );
- foreach ($filtered as $entry) {
- $data['labels'][] = $entry[0];
- $left[] = round($entry[1], 2);
- $spent[] = round(bcmul($entry[2], '-1'), 2); // spent is coming in negative, must be positive
- $overspent[] = round(bcmul($entry[3], '-1'), 2); // same
- }
-
- $data['datasets'][] = [
- 'label' => trans('firefly.overspent'),
- 'data' => $overspent,
- ];
- $data['datasets'][] = [
- 'label' => trans('firefly.left'),
- 'data' => $left,
- ];
- $data['datasets'][] = [
- 'label' => trans('firefly.spent'),
- 'data' => $spent,
- ];
-
- $data['count'] = 3;
-
- return $data;
- }
-
- /**
- * @param array $entries
- *
- * @return array
- */
- public function period(array $entries): array
- {
-
- $data = [
- 'labels' => array_keys($entries),
- 'datasets' => [
- 0 => [
- 'label' => trans('firefly.budgeted'),
- 'data' => [],
- ],
- 1 => [
- 'label' => trans('firefly.spent'),
- 'data' => [],
- ],
- ],
- 'count' => 2,
- ];
-
- foreach ($entries as $label => $entry) {
- // data set 0 is budgeted
- // data set 1 is spent:
- $data['datasets'][0]['data'][] = $entry['budgeted'];
- $data['datasets'][1]['data'][] = round(($entry['spent'] * -1), 2);
-
- }
-
- return $data;
-
- }
-
- /**
- * @param array $entries
- *
- * @return array
- */
- public function periodNoBudget(array $entries): array
- {
- $data = [
- 'labels' => array_keys($entries),
- 'datasets' => [
- 0 => [
- 'label' => trans('firefly.spent'),
- 'data' => [],
- ],
- ],
- 'count' => 1,
- ];
-
- foreach ($entries as $label => $entry) {
- // data set 0 is budgeted
- // data set 1 is spent:
- $data['datasets'][0]['data'][] = round(($entry['spent'] * -1), 2);
-
- }
-
- return $data;
- }
-}
diff --git a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php b/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php
deleted file mode 100644
index c311d70298..0000000000
--- a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php
+++ /dev/null
@@ -1,67 +0,0 @@
- 2,
- 'labels' => [],
- 'datasets' => [
- [
- 'label' => trans('firefly.spent'),
- 'data' => [],
- ],
- [
- 'label' => trans('firefly.earned'),
- 'data' => [],
- ],
- ],
- ];
-
- foreach ($entries as $entry) {
- $data['labels'][] = $entry[1];
- $spent = $entry[2];
- $earned = $entry[3];
-
- $data['datasets'][0]['data'][] = bccomp($spent, '0') === 0 ? null : round(bcmul($spent, '-1'), 4);
- $data['datasets'][1]['data'][] = bccomp($earned, '0') === 0 ? null : round($earned, 4);
- }
-
- return $data;
- }
-
- /**
- * @param Collection $entries
- *
- * @return array
- */
- public function frontpage(Collection $entries): array
- {
- $data = [
- 'count' => 1,
- 'labels' => [],
- 'datasets' => [
- [
- 'label' => trans('firefly.spent'),
- 'data' => [],
- ],
- ],
- ];
- foreach ($entries as $entry) {
- if ($entry->spent != 0) {
- $data['labels'][] = $entry->name;
- $data['datasets'][0]['data'][] = round(bcmul($entry->spent, '-1'), 2);
- }
- }
-
- return $data;
- }
-
- /**
- * @param array $entries
- *
- * @return array
- */
- public function mainReportChart(array $entries): array
- {
-
- $data = [
- 'count' => 0,
- 'labels' => array_keys($entries),
- 'datasets' => [],
- ];
-
-
- foreach ($entries as $row) {
- foreach ($row['in'] as $categoryId => $amount) {
- // get in:
- $data['datasets'][$categoryId . 'in']['data'][] = round($amount, 2);
-
- // get out:
- $opposite = $row['out'][$categoryId];
- $data['datasets'][$categoryId . 'out']['data'][] = round($opposite, 2);
-
- // set name:
- $data['datasets'][$categoryId . 'out']['label'] = $row['name'][$categoryId] . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')';
- $data['datasets'][$categoryId . 'in']['label'] = $row['name'][$categoryId] . ' (' . strtolower(strval(trans('firefly.income'))) . ')';
-
- }
- }
-
- // remove empty rows:
- foreach ($data['datasets'] as $key => $content) {
- if (array_sum($content['data']) === 0.0) {
- unset($data['datasets'][$key]);
- }
- }
-
- // re-key the datasets array:
- $data['datasets'] = array_values($data['datasets']);
- $data['count'] = count($data['datasets']);
-
- return $data;
- }
-
- /**
- *
- * @param Collection $entries
- *
- * @return array
- */
- public function period(Collection $entries): array
- {
- return $this->all($entries);
-
- }
-
- /**
- * @param array $entries
- *
- * @return array
- */
- public function reportPeriod(array $entries): array
- {
-
- $data = [
- 'labels' => array_keys($entries),
- 'datasets' => [
- 0 => [
- 'label' => trans('firefly.earned'),
- 'data' => [],
- ],
- 1 => [
- 'label' => trans('firefly.spent'),
- 'data' => [],
- ],
- ],
- 'count' => 2,
- ];
-
- foreach ($entries as $label => $entry) {
- // data set 0 is budgeted
- // data set 1 is spent:
- $data['datasets'][0]['data'][] = round($entry['earned'], 2);
- $data['datasets'][1]['data'][] = round(bcmul($entry['spent'], '-1'), 2);
-
- }
-
- return $data;
-
- }
-
- /**
- * @param array $entries
- *
- * @return array
- */
- public function pieChart(array $entries): array
- {
- $data = [
- 'datasets' => [
- 0 => [],
- ],
- 'labels' => [],
- ];
- $index = 0;
- foreach ($entries as $entry) {
-
- if (bccomp($entry['amount'], '0') === -1) {
- $entry['amount'] = bcmul($entry['amount'], '-1');
- }
-
- $data['datasets'][0]['data'][] = round($entry['amount'], 2);
- $data['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index);
- $data['labels'][] = $entry['name'];
- $index++;
- }
-
- return $data;
- }
-
-}
diff --git a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php b/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php
deleted file mode 100644
index 2908fa0661..0000000000
--- a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php
+++ /dev/null
@@ -1,58 +0,0 @@
- 1,
- 'labels' => [],
- 'datasets' => [
- [
- 'label' => 'Diff',
- 'data' => [],
- ],
- ],
- ];
- $sum = '0';
- foreach ($set as $key => $value) {
- $date = new Carbon($key);
- $sum = bcadd($sum, $value);
- $data['labels'][] = $date->formatLocalized($format);
- $data['datasets'][0]['data'][] = round($sum, 2);
- }
-
- return $data;
- }
-}
diff --git a/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php b/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php
deleted file mode 100644
index 2841cd928e..0000000000
--- a/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php
+++ /dev/null
@@ -1,31 +0,0 @@
- 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 $expense
- * @param int $count
- *
- * @return array
- */
- public function multiYearSum(string $income, string $expense, int $count): array
- {
- $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;
- }
-
- /**
- * @param Collection $entries
- *
- * @return array
- */
- public function netWorth(Collection $entries) : array
- {
- $format = (string)trans('config.month_and_day');
- $data = [
- 'count' => 1,
- 'labels' => [],
- 'datasets' => [
- [
- 'label' => trans('firefly.net_worth'),
- 'data' => [],
- ],
- ],
- ];
- foreach ($entries as $entry) {
- $data['labels'][] = trim($entry['date']->formatLocalized($format));
- $data['datasets'][0]['data'][] = round($entry['net-worth'], 2);
- }
-
- return $data;
- }
-
- /**
- * @param Collection $entries
- *
- * @return array
- */
- public function yearOperations(Collection $entries): array
- {
- // language:
- $format = (string)trans('config.month');
-
- $data = [
- 'count' => 2,
- 'labels' => [],
- 'datasets' => [
- [
- 'label' => trans('firefly.income'),
- 'data' => [],
- ],
- [
- 'label' => trans('firefly.expenses'),
- 'data' => [],
- ],
- ],
- ];
-
- foreach ($entries as $entry) {
- $data['labels'][] = $entry[0]->formatLocalized($format);
- $data['datasets'][0]['data'][] = round($entry[1], 2);
- $data['datasets'][1]['data'][] = round($entry[2], 2);
- }
-
- return $data;
- }
-
- /**
- * @param string $income
- * @param string $expense
- * @param int $count
- *
- * @return array
- */
- public function yearSum(string $income, string $expense, int $count): array
- {
-
- $data = [
- 'count' => 2,
- 'labels' => [trans('firefly.sum_of_year'), trans('firefly.average_of_year')],
- '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;
- }
-}
diff --git a/app/Generator/Chart/Report/ReportChartGeneratorInterface.php b/app/Generator/Chart/Report/ReportChartGeneratorInterface.php
deleted file mode 100644
index 97bcc6f944..0000000000
--- a/app/Generator/Chart/Report/ReportChartGeneratorInterface.php
+++ /dev/null
@@ -1,65 +0,0 @@
-income = new Collection;
+ $this->expenses = new Collection;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate(): string
+ {
+ $accountIds = join(',', $this->accounts->pluck('id')->toArray());
+ $budgetIds = join(',', $this->budgets->pluck('id')->toArray());
+ $expenses = $this->getExpenses();
+ $accountSummary = $this->summarizeByAccount($expenses);
+ $budgetSummary = $this->summarizeByBudget($expenses);
+ $averageExpenses = $this->getAverages($expenses, SORT_ASC);
+ $topExpenses = $this->getTopExpenses();
+
+ // render!
+ return view('reports.budget.month', compact('accountIds', 'budgetIds', 'accountSummary', 'budgetSummary', 'averageExpenses', 'topExpenses'))
+ ->with('start', $this->start)->with('end', $this->end)
+ ->with('budgets', $this->budgets)
+ ->with('accounts', $this->accounts)
+ ->render();
+ }
+
+ /**
+ * @param Collection $accounts
+ *
+ * @return ReportGeneratorInterface
+ */
+ public function setAccounts(Collection $accounts): ReportGeneratorInterface
+ {
+ $this->accounts = $accounts;
+
+ return $this;
+ }
+
+ /**
+ * @param Collection $budgets
+ *
+ * @return ReportGeneratorInterface
+ */
+ public function setBudgets(Collection $budgets): ReportGeneratorInterface
+ {
+ $this->budgets = $budgets;
+
+ return $this;
+ }
+
+ /**
+ * @param Collection $categories
+ *
+ * @return ReportGeneratorInterface
+ */
+ public function setCategories(Collection $categories): ReportGeneratorInterface
+ {
+ return $this;
+ }
+
+ /**
+ * @param Carbon $date
+ *
+ * @return ReportGeneratorInterface
+ */
+ public function setEndDate(Carbon $date): ReportGeneratorInterface
+ {
+ $this->end = $date;
+
+ return $this;
+ }
+
+ /**
+ * @param Carbon $date
+ *
+ * @return ReportGeneratorInterface
+ */
+ public function setStartDate(Carbon $date): ReportGeneratorInterface
+ {
+ $this->start = $date;
+
+ return $this;
+ }
+
+ /**
+ * @param Collection $collection
+ * @param int $sortFlag
+ *
+ * @return array
+ */
+ private function getAverages(Collection $collection, int $sortFlag): array
+ {
+ $result = [];
+ /** @var Transaction $transaction */
+ foreach ($collection as $transaction) {
+ // opposing name and ID:
+ $opposingId = $transaction->opposing_account_id;
+
+ // is not set?
+ if (!isset($result[$opposingId])) {
+ $name = $transaction->opposing_account_name;
+ $result[$opposingId] = [
+ 'name' => $name,
+ 'count' => 1,
+ 'id' => $opposingId,
+ 'average' => $transaction->transaction_amount,
+ 'sum' => $transaction->transaction_amount,
+ ];
+ continue;
+ }
+ $result[$opposingId]['count']++;
+ $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount);
+ $result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count']));
+ }
+
+ // sort result by average:
+ $average = [];
+ foreach ($result as $key => $row) {
+ $average[$key] = floatval($row['average']);
+ }
+
+ array_multisort($average, $sortFlag, $result);
+
+ return $result;
+ }
+
+ /**
+ * @return Collection
+ */
+ private function getExpenses(): Collection
+ {
+ if ($this->expenses->count() > 0) {
+ Log::debug('Return previous set of expenses.');
+
+ return $this->expenses;
+ }
+
+ $collector = new JournalCollector(auth()->user());
+ $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
+ ->setTypes([TransactionType::WITHDRAWAL])
+ ->setBudgets($this->budgets)->withOpposingAccount()->disableFilter();
+
+ $accountIds = $this->accounts->pluck('id')->toArray();
+ $transactions = $collector->getJournals();
+ $transactions = self::filterExpenses($transactions, $accountIds);
+ $this->expenses = $transactions;
+
+ return $transactions;
+ }
+
+ /**
+ * @return Collection
+ */
+ private function getTopExpenses(): Collection
+ {
+ $transactions = $this->getExpenses()->sortBy('transaction_amount');
+
+ return $transactions;
+ }
+
+ /**
+ * @param Collection $collection
+ *
+ * @return array
+ */
+ private function summarizeByAccount(Collection $collection): array
+ {
+ $result = [];
+ /** @var Transaction $transaction */
+ foreach ($collection as $transaction) {
+ $accountId = $transaction->account_id;
+ $result[$accountId] = $result[$accountId] ?? '0';
+ $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param Collection $collection
+ *
+ * @return array
+ */
+ private function summarizeByBudget(Collection $collection): array
+ {
+ $result = [];
+ /** @var Transaction $transaction */
+ foreach ($collection as $transaction) {
+ $jrnlBudId = intval($transaction->transaction_journal_budget_id);
+ $transBudId = intval($transaction->transaction_budget_id);
+ $budgetId = max($jrnlBudId, $transBudId);
+ $result[$budgetId] = $result[$budgetId] ?? '0';
+ $result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
+ }
+
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/app/Generator/Report/Budget/MultiYearReportGenerator.php b/app/Generator/Report/Budget/MultiYearReportGenerator.php
new file mode 100644
index 0000000000..3d92def9f3
--- /dev/null
+++ b/app/Generator/Report/Budget/MultiYearReportGenerator.php
@@ -0,0 +1,27 @@
+processRepetitionChange($budgetLimitEvent->budgetLimit, $budgetLimitEvent->end);
}
@@ -61,7 +61,7 @@ class BudgetEventHandler
*
* @return bool
*/
- private function processRepetitionChange(BudgetLimit $budgetLimit, Carbon $date):bool
+ private function processRepetitionChange(BudgetLimit $budgetLimit, Carbon $date): bool
{
$set = $budgetLimit->limitrepetitions()
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
diff --git a/app/Handlers/Events/UpdatedJournalEventHandler.php b/app/Handlers/Events/UpdatedJournalEventHandler.php
index 4528ccf910..0deea00280 100644
--- a/app/Handlers/Events/UpdatedJournalEventHandler.php
+++ b/app/Handlers/Events/UpdatedJournalEventHandler.php
@@ -35,7 +35,7 @@ class UpdatedJournalEventHandler
*
* @return bool
*/
- public function processRules(UpdatedTransactionJournal $updatedJournalEvent):bool
+ public function processRules(UpdatedTransactionJournal $updatedJournalEvent): bool
{
// get all the user's rule groups, with the rules, order by 'order'.
$journal = $updatedJournalEvent->journal;
diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php
index ef3b1d212f..0481dd516f 100644
--- a/app/Handlers/Events/UserEventHandler.php
+++ b/app/Handlers/Events/UserEventHandler.php
@@ -15,10 +15,17 @@ namespace FireflyIII\Handlers\Events;
use Exception;
use FireflyConfig;
+use FireflyIII\Events\BlockedBadLogin;
+use FireflyIII\Events\BlockedUseOfDomain;
+use FireflyIII\Events\BlockedUseOfEmail;
+use FireflyIII\Events\BlockedUserLogin;
use FireflyIII\Events\ConfirmedUser;
+use FireflyIII\Events\DeletedUser;
+use FireflyIII\Events\LockedOutUser;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\ResentConfirmation;
+use FireflyIII\Models\Configuration;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Mail\Message;
@@ -75,6 +82,210 @@ class UserEventHandler
return true;
}
+ /**
+ * @param BlockedBadLogin $event
+ *
+ * @return bool
+ */
+ public function reportBadLogin(BlockedBadLogin $event)
+ {
+ $email = $event->email;
+ $owner = env('SITE_OWNER');
+ $ipAddress = $event->ipAddress;
+ /** @var Configuration $sendmail */
+ $sendmail = FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login'));
+ Log::debug(sprintf('Now in reportBadLogin for email address %s', $email));
+ Log::error(sprintf('User %s tried to login with bad credentials.', $email));
+ if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
+
+ return true;
+ }
+
+ try {
+ Mail::send(
+ ['emails.blocked-bad-creds-html', 'emails.blocked-bad-creds-text'], ['email' => $email, 'ip' => $ipAddress],
+ function (Message $message) use ($owner) {
+ $message->to($owner, $owner)->subject('Blocked login attempt with bad credentials');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * @param BlockedUserLogin $event
+ *
+ * @return bool
+ */
+ public function reportBlockedUser(BlockedUserLogin $event): bool
+ {
+ $user = $event->user;
+ $owner = env('SITE_OWNER');
+ $email = $user->email;
+ $ipAddress = $event->ipAddress;
+ /** @var Configuration $sendmail */
+ $sendmail = FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login'));
+ Log::debug(sprintf('Now in reportBlockedUser for email address %s', $email));
+ Log::error(sprintf('User #%d (%s) has their accout blocked (blocked_code is "%s") but tried to login.', $user->id, $email, $user->blocked_code));
+ if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
+ return true;
+ }
+
+ // send email message:
+ try {
+ Mail::send(
+ ['emails.blocked-login-html', 'emails.blocked-login-text'],
+ [
+ 'user_id' => $user->id,
+ 'user_address' => $email,
+ 'ip' => $ipAddress,
+ 'code' => $user->blocked_code,
+ ], function (Message $message) use ($owner, $user) {
+ $message->to($owner, $owner)->subject('Blocked login attempt of blocked user');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * @param LockedOutUser $event
+ *
+ * @return bool
+ */
+ public function reportLockout(LockedOutUser $event): bool
+ {
+ $email = $event->email;
+ $owner = env('SITE_OWNER');
+ $ipAddress = $event->ipAddress;
+ /** @var Configuration $sendmail */
+ $sendmail = FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout'));
+ Log::debug(sprintf('Now in respondToLockout for email address %s', $email));
+ Log::error(sprintf('User %s was locked out after too many invalid login attempts.', $email));
+ if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
+ return true;
+ }
+
+ // send email message:
+ try {
+ Mail::send(
+ ['emails.locked-out-html', 'emails.locked-out-text'], ['email' => $email, 'ip' => $ipAddress], function (Message $message) use ($owner) {
+ $message->to($owner, $owner)->subject('User was locked out');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * @param BlockedUseOfDomain $event
+ *
+ * @return bool
+ */
+ public function reportUseBlockedDomain(BlockedUseOfDomain $event): bool
+ {
+ $email = $event->email;
+ $owner = env('SITE_OWNER');
+ $ipAddress = $event->ipAddress;
+ $parts = explode('@', $email);
+ /** @var Configuration $sendmail */
+ $sendmail = FireflyConfig::get('mail_for_blocked_domain', config('firefly.configuration.mail_for_blocked_domain'));
+ Log::debug(sprintf('Now in reportUseBlockedDomain for email address %s', $email));
+ Log::error(sprintf('Somebody tried to register using an email address (%s) connected to a banned domain (%s).', $email, $parts[1]));
+ if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
+ return true;
+ }
+
+ // send email message:
+ try {
+ Mail::send(
+ ['emails.blocked-registration-html', 'emails.blocked-registration-text'],
+ [
+ 'email_address' => $email,
+ 'blocked_domain' => $parts[1],
+ 'ip' => $ipAddress,
+ ], function (Message $message) use ($owner) {
+ $message->to($owner, $owner)->subject('Blocked registration attempt with blocked domain');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * @param BlockedUseOfEmail $event
+ *
+ * @return bool
+ */
+ public function reportUseOfBlockedEmail(BlockedUseOfEmail $event): bool
+ {
+ $email = $event->email;
+ $owner = env('SITE_OWNER');
+ $ipAddress = $event->ipAddress;
+ /** @var Configuration $sendmail */
+ $sendmail = FireflyConfig::get('mail_for_blocked_email', config('firefly.configuration.mail_for_blocked_email'));
+ Log::debug(sprintf('Now in reportUseOfBlockedEmail for email address %s', $email));
+ Log::error(sprintf('Somebody tried to register using email address %s which is blocked (SHA2 hash).', $email));
+ if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
+ return true;
+ }
+
+ // send email message:
+ try {
+ Mail::send(
+ ['emails.blocked-email-html', 'emails.blocked-email-text'],
+ [
+ 'user_address' => $email,
+ 'ip' => $ipAddress,
+ ], function (Message $message) use ($owner) {
+ $message->to($owner, $owner)->subject('Blocked registration attempt with blocked email address');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * @param DeletedUser $event
+ *
+ * @return bool
+ */
+ public function saveEmailAddress(DeletedUser $event): bool
+ {
+ Preferences::mark();
+ $email = hash('sha256', $event->email);
+ Log::debug(sprintf('Hash of email is %s', $email));
+ /** @var Configuration $configuration */
+ $configuration = FireflyConfig::get('deleted_users', []);
+ $content = $configuration->data;
+ if (!is_array($content)) {
+ $content = [];
+ }
+ $content[] = $email;
+ $configuration->data = $content;
+ Log::debug('New content of deleted_users is ', $content);
+ FireflyConfig::set('deleted_users', $content);
+
+ Preferences::mark();
+
+ return true;
+ }
+
/**
* This method will send a newly registered user a confirmation message, urging him or her to activate their account.
*
@@ -152,7 +363,7 @@ class UserEventHandler
try {
Mail::send(
['emails.registered-html', 'emails.registered-text'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
- $message->to($email, $email)->subject('Welcome to Firefly III! ');
+ $message->to($email, $email)->subject('Welcome to Firefly III!');
}
);
} catch (Swift_TransportException $e) {
@@ -194,7 +405,6 @@ class UserEventHandler
}
-
/**
* @param User $user
* @param string $ipAddress
diff --git a/app/Helpers/Collection/BillLine.php b/app/Helpers/Collection/BillLine.php
index fb28afed18..3b7bd692b6 100644
--- a/app/Helpers/Collection/BillLine.php
+++ b/app/Helpers/Collection/BillLine.php
@@ -79,6 +79,22 @@ class BillLine
$this->bill = $bill;
}
+ /**
+ * @return Carbon
+ */
+ public function getLastHitDate(): Carbon
+ {
+ return $this->lastHitDate;
+ }
+
+ /**
+ * @param Carbon $lastHitDate
+ */
+ public function setLastHitDate(Carbon $lastHitDate)
+ {
+ $this->lastHitDate = $lastHitDate;
+ }
+
/**
* @return string
*/
@@ -151,21 +167,5 @@ class BillLine
$this->hit = $hit;
}
- /**
- * @param Carbon $lastHitDate
- */
- public function setLastHitDate(Carbon $lastHitDate)
- {
- $this->lastHitDate = $lastHitDate;
- }
-
- /**
- * @return Carbon
- */
- public function getLastHitDate(): Carbon
- {
- return $this->lastHitDate;
- }
-
}
diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php
index eafacdc363..76f66a0019 100644
--- a/app/Helpers/Collector/JournalCollector.php
+++ b/app/Helpers/Collector/JournalCollector.php
@@ -709,7 +709,7 @@ class JournalCollector implements JournalCollectorInterface
*/
private function startQuery(): EloquentBuilder
{
-
+ /** @var EloquentBuilder $query */
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
diff --git a/app/Helpers/Help/Help.php b/app/Helpers/Help/Help.php
index 2167b9ad43..0d290955ca 100644
--- a/app/Helpers/Help/Help.php
+++ b/app/Helpers/Help/Help.php
@@ -88,7 +88,7 @@ class Help implements HelpInterface
*
* @return bool
*/
- public function hasRoute(string $route):bool
+ public function hasRoute(string $route): bool
{
return Route::has($route);
}
@@ -99,7 +99,7 @@ class Help implements HelpInterface
*
* @return bool
*/
- public function inCache(string $route, string $language):bool
+ public function inCache(string $route, string $language): bool
{
$line = sprintf('help.%s.%s', $route, $language);
$result = Cache::has($line);
diff --git a/app/Helpers/Help/HelpInterface.php b/app/Helpers/Help/HelpInterface.php
index 4187ddf1ce..d642f68294 100644
--- a/app/Helpers/Help/HelpInterface.php
+++ b/app/Helpers/Help/HelpInterface.php
@@ -34,7 +34,7 @@ interface HelpInterface
*
* @return string
*/
- public function getFromGithub(string $language, string $route):string;
+ public function getFromGithub(string $language, string $route): string;
/**
* @param string $route
diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
index 1d26e17f43..2372e7e101 100644
--- a/app/Http/Controllers/AccountController.php
+++ b/app/Http/Controllers/AccountController.php
@@ -235,11 +235,12 @@ class AccountController extends Controller
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$subTitle = $account->name;
$range = Preferences::get('viewRange', '1M')->data;
+
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
-
+ $chartUri = route('chart.account.single', [$account->id]);
// grab those journals:
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
$journals = $collector->getPaginatedJournals();
@@ -248,7 +249,7 @@ class AccountController extends Controller
// generate entries for each period (and cache those)
$entries = $this->periodEntries($account);
- return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end'));
+ return view('accounts.show', compact('account', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
}
/**
@@ -262,6 +263,7 @@ class AccountController extends Controller
$subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
+ $chartUri = route('chart.account.all', [$account->id]);
// replace with journal collector:
$collector = new JournalCollector(auth()->user());
@@ -273,7 +275,8 @@ class AccountController extends Controller
$start = $repository->oldestJournalDate($account);
$end = $repository->newestJournalDate($account);
- return view('accounts.show_with_date', compact('account', 'journals', 'subTitle', 'start', 'end'));
+ // same call, except "entries".
+ return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
}
/**
@@ -291,6 +294,7 @@ class AccountController extends Controller
$subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
+ $chartUri = route('chart.account.period', [$account->id, $carbon->format('Y-m-d')]);
// replace with journal collector:
$collector = new JournalCollector(auth()->user());
@@ -298,7 +302,8 @@ class AccountController extends Controller
$journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id . '/' . $date);
- return view('accounts.show-by-date', compact('category', 'date', 'account', 'journals', 'subTitle', 'carbon', 'start', 'end'));
+ // same call, except "entries".
+ return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
}
/**
diff --git a/app/Http/Controllers/Admin/ConfigurationController.php b/app/Http/Controllers/Admin/ConfigurationController.php
index 62e5c1298a..7d3a8bd3b0 100644
--- a/app/Http/Controllers/Admin/ConfigurationController.php
+++ b/app/Http/Controllers/Admin/ConfigurationController.php
@@ -61,8 +61,21 @@ class ConfigurationController extends Controller
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
+ $siteOwner = env('SITE_OWNER');
- return view('admin.configuration.index', compact('subTitle', 'subTitleIcon', 'singleUserMode', 'mustConfirmAccount', 'isDemoSite'));
+ // email settings:
+ $sendErrorMessage = [
+ 'mail_for_lockout' => FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout'))->data,
+ 'mail_for_blocked_domain' => FireflyConfig::get('mail_for_blocked_domain', config('firefly.configuration.mail_for_blocked_domain'))->data,
+ 'mail_for_blocked_email' => FireflyConfig::get('mail_for_blocked_email', config('firefly.configuration.mail_for_blocked_email'))->data,
+ 'mail_for_bad_login' => FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login'))->data,
+ 'mail_for_blocked_login' => FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login'))->data,
+ ];
+
+ return view(
+ 'admin.configuration.index',
+ compact('subTitle', 'subTitleIcon', 'singleUserMode', 'mustConfirmAccount', 'isDemoSite', 'sendErrorMessage', 'siteOwner')
+ );
}
@@ -81,6 +94,13 @@ class ConfigurationController extends Controller
FireflyConfig::set('must_confirm_account', $data['must_confirm_account']);
FireflyConfig::set('is_demo_site', $data['is_demo_site']);
+ // email settings
+ FireflyConfig::set('mail_for_lockout', $data['mail_for_lockout']);
+ FireflyConfig::set('mail_for_blocked_domain', $data['mail_for_blocked_domain']);
+ FireflyConfig::set('mail_for_blocked_email', $data['mail_for_blocked_email']);
+ FireflyConfig::set('mail_for_bad_login', $data['mail_for_bad_login']);
+ FireflyConfig::set('mail_for_blocked_login', $data['mail_for_blocked_login']);
+
// flash message
Session::flash('success', strval(trans('firefly.configuration_updated')));
Preferences::mark();
diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php
index ac1f2866f0..cab96f62a2 100644
--- a/app/Http/Controllers/Admin/UserController.php
+++ b/app/Http/Controllers/Admin/UserController.php
@@ -16,9 +16,13 @@ namespace FireflyIII\Http\Controllers\Admin;
use FireflyConfig;
use FireflyIII\Http\Controllers\Controller;
+use FireflyIII\Http\Requests\UserFormRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Preferences;
+use Session;
+use URL;
+use View;
/**
* Class UserController
@@ -27,28 +31,56 @@ use Preferences;
*/
class UserController extends Controller
{
+ /**
+ *
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->middleware(
+ function ($request, $next) {
+ View::share('title', strval(trans('firefly.administration')));
+ View::share('mainTitleIcon', 'fa-hand-spock-o');
+
+ return $next($request);
+ }
+ );
+ }
+
/**
* @param User $user
*
- * @return int
+ * @return View
*/
public function edit(User $user)
{
- return $user->id;
+ // put previous url in session if not redirect from store (not "return_to_edit").
+ if (session('users.edit.fromUpdate') !== true) {
+ Session::put('users.edit.url', URL::previous());
+ }
+ Session::forget('users.edit.fromUpdate');
+
+ $subTitle = strval(trans('firefly.edit_user', ['email' => $user->email]));
+ $subTitleIcon = 'fa-user-o';
+ $codes = [
+ '' => strval(trans('firefly.no_block_code')),
+ 'bounced' => strval(trans('firefly.block_code_bounced')),
+ 'expired' => strval(trans('firefly.block_code_expired')),
+ ];
+
+ return view('admin.users.edit', compact('user', 'subTitle', 'subTitleIcon', 'codes'));
}
/**
* @param UserRepositoryInterface $repository
*
- * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+ * @return View
*/
public function index(UserRepositoryInterface $repository)
{
- $title = strval(trans('firefly.administration'));
- $mainTitleIcon = 'fa-hand-spock-o';
$subTitle = strval(trans('firefly.user_administration'));
$subTitleIcon = 'fa-users';
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
@@ -77,7 +109,7 @@ class UserController extends Controller
);
- return view('admin.users.index', compact('title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'users'));
+ return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users'));
}
@@ -128,5 +160,41 @@ class UserController extends Controller
);
}
+ /**
+ * @param UserFormRequest $request
+ * @param User $user
+ *
+ * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ */
+ public function update(UserFormRequest $request, User $user)
+ {
+ $data = $request->getUserData();
+
+ // update password
+ if (strlen($data['password']) > 0) {
+ $user->password = bcrypt($data['password']);
+ $user->save();
+ }
+
+ // change blocked status and code:
+ $user->blocked = $data['blocked'];
+ $user->blocked_code = $data['blocked_code'];
+ $user->save();
+
+ Session::flash('success', strval(trans('firefly.updated_user', ['email' => $user->email])));
+ Preferences::mark();
+
+ if (intval($request->get('return_to_edit')) === 1) {
+ // set value so edit routine will not overwrite URL:
+ Session::put('users.edit.fromUpdate', true);
+
+ return redirect(route('admin.users.edit', [$user->id]))->withInput(['return_to_edit' => 1]);
+ }
+
+ // redirect to previous URL.
+ return redirect(session('users.edit.url'));
+
+ }
+
}
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index fd38a15187..06faf92d07 100755
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -14,15 +14,14 @@ namespace FireflyIII\Http\Controllers\Auth;
use Config;
use FireflyConfig;
+use FireflyIII\Events\BlockedBadLogin;
+use FireflyIII\Events\BlockedUserLogin;
+use FireflyIII\Events\LockedOutUser;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
-use Illuminate\Mail\Message;
use Lang;
-use Log;
-use Mail;
-use Swift_TransportException;
/**
* Class LoginController
@@ -31,16 +30,6 @@ use Swift_TransportException;
*/
class LoginController extends Controller
{
- /*
- |--------------------------------------------------------------------------
- | Login Controller
- |--------------------------------------------------------------------------
- |
- | This controller handles authenticating users for the application and
- | redirecting them to your home screen. The controller uses a trait
- | to conveniently provide its functionality to your applications.
- |
- */
use AuthenticatesUsers;
@@ -49,7 +38,7 @@ class LoginController extends Controller
*
* @var string
*/
- protected $redirectTo = '/home';
+ protected $redirectTo = '/';
/**
* Create a new controller instance.
@@ -71,19 +60,17 @@ class LoginController extends Controller
public function login(Request $request)
{
$this->validateLogin($request);
-
- // If the class is using the ThrottlesLogins trait, we can automatically throttle
- // the login attempts for this application. We'll key this by the username and
- // the IP address of the client making these requests into this application.
$lockedOut = $this->hasTooManyLoginAttempts($request);
if ($lockedOut) {
$this->fireLockoutEvent($request);
+ event(new LockedOutUser($request->get('email'), $request->ip()));
+
return $this->sendLockoutResponse($request);
}
$credentials = $this->credentials($request);
- $credentials['blocked'] = 0; // most not be blocked.
+ $credentials['blocked'] = 0; // must not be blocked.
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
return $this->sendLoginResponse($request);
@@ -94,10 +81,15 @@ class LoginController extends Controller
/** @var User $foundUser */
$foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first();
if (!is_null($foundUser)) {
- // if it exists, show message:
+ // user exists, but is blocked:
$code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked';
$errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $credentials['email']]));
- $this->reportBlockedUserLoginAttempt($foundUser, $code, $request->ip());
+ event(new BlockedUserLogin($foundUser, $request->ip()));
+ }
+
+ // simply a bad login.
+ if (is_null($foundUser)) {
+ event(new BlockedBadLogin($credentials['email'], $request->ip()));
}
// If the login attempt was unsuccessful we will increment the number of attempts
@@ -167,34 +159,4 @@ class LoginController extends Controller
]
);
}
-
- /**
- * Send a message home about the blocked attempt to login.
- * Perhaps in a later stage, simply log these messages.
- *
- * @param User $user
- * @param string $code
- * @param string $ipAddress
- */
- private function reportBlockedUserLoginAttempt(User $user, string $code, string $ipAddress)
- {
-
- try {
- $email = env('SITE_OWNER', false);
- $fields = [
- 'user_id' => $user->id,
- 'user_address' => $user->email,
- 'code' => $code,
- 'ip' => $ipAddress,
- ];
-
- Mail::send(
- ['emails.blocked-login-html', 'emails.blocked-login-text'], $fields, function (Message $message) use ($email, $user) {
- $message->to($email, $email)->subject('Blocked a login attempt from ' . trim($user->email) . '.');
- }
- );
- } catch (Swift_TransportException $e) {
- Log::error($e->getMessage());
- }
- }
}
diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php
index 95a5c82132..02a4757bc2 100644
--- a/app/Http/Controllers/Auth/PasswordController.php
+++ b/app/Http/Controllers/Auth/PasswordController.php
@@ -31,16 +31,6 @@ use Illuminate\Support\Facades\Password;
*/
class PasswordController extends Controller
{
- /*
- |--------------------------------------------------------------------------
- | Password Reset Controller
- |--------------------------------------------------------------------------
- |
- | This controller is responsible for handling password reset requests
- | and uses a simple trait to include this behavior. You're free to
- | explore this trait and override any methods you wish to tweak.
- |
- */
use ResetsPasswords;
diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php
index 2a62f91ff8..b56978153a 100755
--- a/app/Http/Controllers/Auth/RegisterController.php
+++ b/app/Http/Controllers/Auth/RegisterController.php
@@ -14,9 +14,11 @@ namespace FireflyIII\Http\Controllers\Auth;
use Auth;
use Config;
+use FireflyConfig;
+use FireflyIII\Events\BlockedUseOfDomain;
+use FireflyIII\Events\BlockedUseOfEmail;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Http\Controllers\Controller;
-use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
@@ -34,16 +36,6 @@ use Validator;
*/
class RegisterController extends Controller
{
- /*
- |--------------------------------------------------------------------------
- | Register Controller
- |--------------------------------------------------------------------------
- |
- | This controller handles the registration of new users as well as their
- | validation and creation. By default this controller uses a trait to
- | provide this functionality without requiring any additional code.
- |
- */
use RegistersUsers;
@@ -93,8 +85,19 @@ class RegisterController extends Controller
if ($this->isBlockedDomain($data['email'])) {
$validator->getMessageBag()->add('email', (string)trans('validation.invalid_domain'));
- $this->reportBlockedDomainRegistrationAttempt($data['email'], $request->ip());
+ event(new BlockedUseOfDomain($data['email'], $request->ip()));
+ $this->throwValidationException($request, $validator);
+ }
+ // is user a deleted user?
+ $hash = hash('sha256', $data['email']);
+ $configuration = FireflyConfig::get('deleted_users', []);
+ $set = $configuration->data;
+ Log::debug(sprintf('Hash of email is %s', $hash));
+ Log::debug('Hashes of deleted users: ', $set);
+ if (in_array($hash, $set)) {
+ $validator->getMessageBag()->add('email', (string)trans('validation.deleted_user'));
+ event(new BlockedUseOfEmail($data['email'], $request->ip()));
$this->throwValidationException($request, $validator);
}
@@ -202,31 +205,4 @@ class RegisterController extends Controller
return false;
}
- /**
- * Send a message home about a blocked domain and the address attempted to register.
- *
- * @param string $registrationMail
- * @param string $ipAddress
- */
- private function reportBlockedDomainRegistrationAttempt(string $registrationMail, string $ipAddress)
- {
- try {
- $email = env('SITE_OWNER', false);
- $parts = explode('@', $registrationMail);
- $domain = $parts[1];
- $fields = [
- 'email_address' => $registrationMail,
- 'blocked_domain' => $domain,
- 'ip' => $ipAddress,
- ];
-
- Mail::send(
- ['emails.blocked-registration-html', 'emails.blocked-registration-text'], $fields, function (Message $message) use ($email, $domain) {
- $message->to($email, $email)->subject('Blocked a registration attempt with domain ' . $domain . '.');
- }
- );
- } catch (Swift_TransportException $e) {
- Log::error($e->getMessage());
- }
- }
}
diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php
index 3e8c162572..1d99a0c179 100755
--- a/app/Http/Controllers/Auth/ResetPasswordController.php
+++ b/app/Http/Controllers/Auth/ResetPasswordController.php
@@ -22,16 +22,6 @@ use Illuminate\Foundation\Auth\ResetsPasswords;
*/
class ResetPasswordController extends Controller
{
- /*
- |--------------------------------------------------------------------------
- | Password Reset Controller
- |--------------------------------------------------------------------------
- |
- | This controller is responsible for handling password reset requests
- | and uses a simple trait to include this behavior. You're free to
- | explore this trait and override any methods you wish to tweak.
- |
- */
use ResetsPasswords;
diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php
index 5ba51a5a6b..9605e0b8c6 100644
--- a/app/Http/Controllers/BudgetController.php
+++ b/app/Http/Controllers/BudgetController.php
@@ -299,12 +299,12 @@ class BudgetController extends Controller
public function show(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget)
{
/** @var Carbon $start */
- $start = session('first', Carbon::create()->startOfYear());
- $end = new Carbon;
- $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
- $pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
- $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
-
+ $start = session('first', Carbon::create()->startOfYear());
+ $end = new Carbon;
+ $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
+ $pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
+ $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
+ $repetition = null;
// collector:
$collector = new JournalCollector(auth()->user());
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page);
diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php
index 1ffb912259..e832f3245d 100644
--- a/app/Http/Controllers/CategoryController.php
+++ b/app/Http/Controllers/CategoryController.php
@@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollector;
+use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\CategoryFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category;
@@ -60,7 +61,6 @@ class CategoryController extends Controller
*/
public function create()
{
- // put previous url in session if not redirect from store (not "create another").
if (session('categories.create.fromStore') !== true) {
Session::put('categories.create.url', URL::previous());
}
@@ -190,7 +190,7 @@ class CategoryController extends Controller
$subTitleIcon = 'fa-bar-chart';
// use journal collector
- $collector = new JournalCollector(auth()->user());
+ $collector = app(JournalCollectorInterface::class);
$collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category);
$journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id);
@@ -257,7 +257,7 @@ class CategoryController extends Controller
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
// new collector:
- $collector = new JournalCollector(auth()->user());
+ $collector = app(JournalCollectorInterface::class);
$collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category);
$journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id . '/' . $date);
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index fb586715de..8bf0047e6f 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -16,7 +16,7 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
-use FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface;
+use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
@@ -42,7 +42,7 @@ use Steam;
class AccountController extends Controller
{
- /** @var \FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface */
+ /** @var GeneratorInterface */
protected $generator;
/**
@@ -51,8 +51,49 @@ class AccountController extends Controller
public function __construct()
{
parent::__construct();
- // create chart generator:
- $this->generator = app(AccountChartGeneratorInterface::class);
+ $this->generator = app(GeneratorInterface::class);
+ }
+
+ /**
+ * @param Account $account
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function all(Account $account)
+ {
+ $cache = new CacheProperties();
+ $cache->addProperty('chart.account.all');
+ $cache->addProperty($account->id);
+ if ($cache->has()) {
+ Log::debug('Return chart.account.all from cache.');
+
+ return Response::json($cache->get());
+ }
+ Log::debug('Regenerate chart.account.all from scratch.');
+
+ /** @var AccountRepositoryInterface $repository */
+ $repository = app(AccountRepositoryInterface::class);
+ $start = $repository->oldestJournalDate($account);
+ $end = new Carbon;
+ $format = (string)trans('config.month_and_day');
+ $range = Steam::balanceInRange($account, $start, $end);
+ $current = clone $start;
+ $previous = array_values($range)[0];
+ $chartData = [];
+
+ while ($end >= $current) {
+ $theDate = $current->format('Y-m-d');
+ $balance = $range[$theDate] ?? $previous;
+ $label = $current->formatLocalized($format);
+ $chartData[$label] = $balance;
+ $previous = $balance;
+ $current->addDay();
+ }
+
+ $data = $this->generator->singleSet($account->name, $chartData);
+ $cache->store($data);
+
+ return Response::json($data);
}
/**
@@ -69,36 +110,29 @@ class AccountController extends Controller
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('expenseAccounts');
- $cache->addProperty('accounts');
+ $cache->addProperty('chart.account.expense-accounts');
if ($cache->has()) {
return Response::json($cache->get());
}
- $accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
-
$start->subDay();
+
+ $accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
$ids = $accounts->pluck('id')->toArray();
$startBalances = Steam::balancesById($ids, $start);
$endBalances = Steam::balancesById($ids, $end);
+ $chartData = [];
- $accounts->each(
- function (Account $account) use ($startBalances, $endBalances) {
- $id = $account->id;
- $startBalance = $startBalances[$id] ?? '0';
- $endBalance = $endBalances[$id] ?? '0';
- $diff = bcsub($endBalance, $startBalance);
- $account->difference = round($diff, 2);
+ foreach ($accounts as $account) {
+ $id = $account->id;
+ $startBalance = $startBalances[$id] ?? '0';
+ $endBalance = $endBalances[$id] ?? '0';
+ $diff = bcsub($endBalance, $startBalance);
+ if (bccomp($diff, '0') !== 0) {
+ $chartData[$account->name] = round($diff, 2);
}
- );
-
-
- $accounts = $accounts->sortByDesc(
- function (Account $account) {
- return $account->difference;
- }
- );
-
- $data = $this->generator->expenseAccounts($accounts, $start, $end);
+ }
+ arsort($chartData);
+ $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data);
return Response::json($data);
@@ -118,28 +152,33 @@ class AccountController extends Controller
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('expenseByBudget');
+ $cache->addProperty('chart.account.expense-budget');
if ($cache->has()) {
return Response::json($cache->get());
}
-
-
- // grab all journals:
- $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionType::WITHDRAWAL]);
+ $collector->setAccounts(new Collection([$account]))
+ ->setRange($start, $end)
+ ->withBudgetInformation()
+ ->setTypes([TransactionType::WITHDRAWAL]);
$transactions = $collector->getJournals();
+ $chartData = [];
+ $result = [];
- $result = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
- $jrnlBudgetId = intval($transaction->transaction_journal_budget_id);
- $transBudgetId = intval($transaction->transaction_budget_id);
- $budgetId = max($jrnlBudgetId, $transBudgetId);
-
+ $jrnlBudgetId = intval($transaction->transaction_journal_budget_id);
+ $transBudgetId = intval($transaction->transaction_budget_id);
+ $budgetId = max($jrnlBudgetId, $transBudgetId);
$result[$budgetId] = $result[$budgetId] ?? '0';
$result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
}
+
$names = $this->getBudgetNames(array_keys($result));
- $data = $this->generator->pieChart($result, $names);
+ foreach ($result as $budgetId => $amount) {
+ $chartData[$names[$budgetId]] = $amount;
+ }
+
+ $data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data);
@@ -159,26 +198,30 @@ class AccountController extends Controller
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('expenseByCategory');
+ $cache->addProperty('chart.account.expense-category');
if ($cache->has()) {
return Response::json($cache->get());
}
- // grab all journals:
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]);
$transactions = $collector->getJournals();
$result = [];
+ $chartData = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
- $jrnlCatId = intval($transaction->transaction_journal_category_id);
- $transCatId = intval($transaction->transaction_category_id);
- $categoryId = max($jrnlCatId, $transCatId);
-
+ $jrnlCatId = intval($transaction->transaction_journal_category_id);
+ $transCatId = intval($transaction->transaction_category_id);
+ $categoryId = max($jrnlCatId, $transCatId);
$result[$categoryId] = $result[$categoryId] ?? '0';
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
}
+
$names = $this->getCategoryNames(array_keys($result));
- $data = $this->generator->pieChart($result, $names);
+ foreach ($result as $categoryId => $amount) {
+ $chartData[$names[$categoryId]] = $amount;
+ }
+
+ $data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data);
@@ -194,10 +237,18 @@ class AccountController extends Controller
*/
public function frontpage(AccountRepositoryInterface $repository)
{
- $start = clone session('start', Carbon::now()->startOfMonth());
- $end = clone session('end', Carbon::now()->endOfMonth());
- $frontPage = Preferences::get('frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray());
- $accounts = $repository->getAccountsById($frontPage->data);
+ $start = clone session('start', Carbon::now()->startOfMonth());
+ $end = clone session('end', Carbon::now()->endOfMonth());
+ $defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
+ Log::debug('Default set is ', $defaultSet);
+ $frontPage = Preferences::get('frontPageAccounts', $defaultSet);
+ Log::debug('Frontpage preference set is ', $frontPage->data);
+ if (count($frontPage->data) === 0) {
+ $frontPage->data = $defaultSet;
+ Log::debug('frontpage set is empty!');
+ $frontPage->save();
+ }
+ $accounts = $repository->getAccountsById($frontPage->data);
return Response::json($this->accountBalanceChart($accounts, $start, $end));
}
@@ -216,7 +267,7 @@ class AccountController extends Controller
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('incomeByCategory');
+ $cache->addProperty('chart.account.income-category');
if ($cache->has()) {
return Response::json($cache->get());
}
@@ -225,23 +276,74 @@ class AccountController extends Controller
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]);
$transactions = $collector->getJournals();
$result = [];
+ $chartData = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
- $jrnlCatId = intval($transaction->transaction_journal_category_id);
- $transCatId = intval($transaction->transaction_category_id);
- $categoryId = max($jrnlCatId, $transCatId);
-
+ $jrnlCatId = intval($transaction->transaction_journal_category_id);
+ $transCatId = intval($transaction->transaction_category_id);
+ $categoryId = max($jrnlCatId, $transCatId);
$result[$categoryId] = $result[$categoryId] ?? '0';
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
}
+
$names = $this->getCategoryNames(array_keys($result));
- $data = $this->generator->pieChart($result, $names);
+ foreach ($result as $categoryId => $amount) {
+ $chartData[$names[$categoryId]] = $amount;
+ }
+ $data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data);
}
+ /**
+ * @param Account $account
+ * @param string $date
+ *
+ * @return \Illuminate\Http\JsonResponse
+ * @throws FireflyException
+ */
+ public function period(Account $account, string $date)
+ {
+ try {
+ $start = new Carbon($date);
+ } catch (Exception $e) {
+ Log::error($e->getMessage());
+ throw new FireflyException('"' . e($date) . '" does not seem to be a valid date. Should be in the format YYYY-MM-DD');
+ }
+ $range = Preferences::get('viewRange', '1M')->data;
+ $end = Navigation::endOfPeriod($start, $range);
+ $cache = new CacheProperties();
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('chart.account.period');
+ $cache->addProperty($account->id);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+
+ $format = (string)trans('config.month_and_day');
+ $range = Steam::balanceInRange($account, $start, $end);
+ $current = clone $start;
+ $previous = array_values($range)[0];
+ $chartData = [];
+
+ while ($end >= $current) {
+ $theDate = $current->format('Y-m-d');
+ $balance = $range[$theDate] ?? $previous;
+ $label = $current->formatLocalized($format);
+ $chartData[$label] = $balance;
+ $previous = $balance;
+ $current->addDay();
+ }
+
+ $data = $this->generator->singleSet($account->name, $chartData);
+ $cache->store($data);
+
+ return Response::json($data);
+ }
+
/**
* Shows the balances for a given set of dates and accounts.
*
@@ -265,13 +367,13 @@ class AccountController extends Controller
*/
public function revenueAccounts(AccountRepositoryInterface $repository)
{
- $start = clone session('start', Carbon::now()->startOfMonth());
- $end = clone session('end', Carbon::now()->endOfMonth());
- $cache = new CacheProperties;
+ $start = clone session('start', Carbon::now()->startOfMonth());
+ $end = clone session('end', Carbon::now()->endOfMonth());
+ $chartData = [];
+ $cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('revenueAccounts');
- $cache->addProperty('accounts');
+ $cache->addProperty('chart.account.revenue-accounts');
if ($cache->has()) {
return Response::json($cache->get());
}
@@ -282,25 +384,19 @@ class AccountController extends Controller
$startBalances = Steam::balancesById($ids, $start);
$endBalances = Steam::balancesById($ids, $end);
- $accounts->each(
- function (Account $account) use ($startBalances, $endBalances) {
- $id = $account->id;
- $startBalance = $startBalances[$id] ?? '0';
- $endBalance = $endBalances[$id] ?? '0';
- $diff = bcsub($endBalance, $startBalance);
- $diff = bcmul($diff, '-1');
- $account->difference = round($diff, 2);
+ foreach ($accounts as $account) {
+ $id = $account->id;
+ $startBalance = $startBalances[$id] ?? '0';
+ $endBalance = $endBalances[$id] ?? '0';
+ $diff = bcsub($endBalance, $startBalance);
+ $diff = bcmul($diff, '-1');
+ if (bccomp($diff, '0') !== 0) {
+ $chartData[$account->name] = round($diff, 2);
}
- );
+ }
-
- $accounts = $accounts->sortByDesc(
- function (Account $account) {
- return $account->difference;
- }
- );
-
- $data = $this->generator->revenueAccounts($accounts, $start, $end);
+ arsort($chartData);
+ $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data);
return Response::json($data);
@@ -322,8 +418,7 @@ class AccountController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('frontpage');
- $cache->addProperty('single');
+ $cache->addProperty('chart.account.single');
$cache->addProperty($account->id);
if ($cache->has()) {
return Response::json($cache->get());
@@ -333,73 +428,18 @@ class AccountController extends Controller
$range = Steam::balanceInRange($account, $start, $end);
$current = clone $start;
$previous = array_values($range)[0];
- $labels = [];
$chartData = [];
while ($end >= $current) {
- $theDate = $current->format('Y-m-d');
- $balance = $range[$theDate] ?? $previous;
-
- $labels[] = $current->formatLocalized($format);
- $chartData[] = $balance;
- $previous = $balance;
+ $theDate = $current->format('Y-m-d');
+ $balance = $range[$theDate] ?? $previous;
+ $label = $current->formatLocalized($format);
+ $chartData[$label] = $balance;
+ $previous = $balance;
$current->addDay();
}
-
- $data = $this->generator->single($account, $labels, $chartData);
- $cache->store($data);
-
- return Response::json($data);
- }
-
- /**
- * @param Account $account
- * @param string $date
- *
- * @return \Illuminate\Http\JsonResponse
- * @throws FireflyException
- */
- public function specificPeriod(Account $account, string $date)
- {
- try {
- $start = new Carbon($date);
- } catch (Exception $e) {
- Log::error($e->getMessage());
- throw new FireflyException('"' . e($date) . '" does not seem to be a valid date. Should be in the format YYYY-MM-DD');
- }
- $range = Preferences::get('viewRange', '1M')->data;
- $end = Navigation::endOfPeriod($start, $range);
- // chart properties for cache:
- $cache = new CacheProperties();
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty('frontpage');
- $cache->addProperty('specificPeriod');
- $cache->addProperty($account->id);
- if ($cache->has()) {
- return Response::json($cache->get());
- }
-
- $format = (string)trans('config.month_and_day');
- $range = Steam::balanceInRange($account, $start, $end);
- $current = clone $start;
- $previous = array_values($range)[0];
- $labels = [];
- $chartData = [];
-
- while ($end >= $current) {
- $theDate = $current->format('Y-m-d');
- $balance = $range[$theDate] ?? $previous;
-
- $labels[] = $current->formatLocalized($format);
- $chartData[] = $balance;
- $previous = $balance;
- $current->addDay();
- }
-
-
- $data = $this->generator->single($account, $labels, $chartData);
+ $data = $this->generator->singleSet($account->name, $chartData);
$cache->store($data);
return Response::json($data);
@@ -418,27 +458,35 @@ class AccountController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('account-balance-chart');
+ $cache->addProperty('chart.account.account-balance-chart');
$cache->addProperty($accounts);
if ($cache->has()) {
+ Log::debug('Return chart.account.account-balance-chart from cache.');
+
return $cache->get();
}
+ Log::debug('Regenerate chart.account.account-balance-chart from scratch.');
+ $chartData = [];
foreach ($accounts as $account) {
- $balances = [];
- $current = clone $start;
- $range = Steam::balanceInRange($account, $start, clone $end);
- $previous = round(array_values($range)[0], 2);
- while ($current <= $end) {
- $format = $current->format('Y-m-d');
- $balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
- $previous = $balance;
- $balances[] = $balance;
- $current->addDay();
+ $currentSet = [
+ 'label' => $account->name,
+ 'entries' => [],
+ ];
+ $currentStart = clone $start;
+ $range = Steam::balanceInRange($account, $start, clone $end);
+ $previous = round(array_values($range)[0], 2);
+ while ($currentStart <= $end) {
+ $format = $currentStart->format('Y-m-d');
+ $label = $currentStart->formatLocalized(strval(trans('config.month_and_day')));
+ $balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
+ $previous = $balance;
+ $currentStart->addDay();
+ $currentSet['entries'][$label] = $balance;
}
- $account->balances = $balances;
+ $chartData[] = $currentSet;
}
- $data = $this->generator->frontpage($accounts, $start, $end);
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return $data;
diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php
index 51ca561e75..26ae9963c5 100644
--- a/app/Http/Controllers/Chart/BillController.php
+++ b/app/Http/Controllers/Chart/BillController.php
@@ -14,8 +14,8 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
-use FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface;
-use FireflyIII\Helpers\Collector\JournalCollector;
+use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
+use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Transaction;
@@ -32,7 +32,7 @@ use Response;
class BillController extends Controller
{
- /** @var \FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface */
+ /** @var GeneratorInterface */
protected $generator;
/**
@@ -41,8 +41,7 @@ class BillController extends Controller
public function __construct()
{
parent::__construct();
- // create chart generator:
- $this->generator = app(BillChartGeneratorInterface::class);
+ $this->generator = app(GeneratorInterface::class);
}
/**
@@ -54,45 +53,81 @@ class BillController extends Controller
*/
public function frontpage(BillRepositoryInterface $repository)
{
- $start = session('start', Carbon::now()->startOfMonth());
- $end = session('end', Carbon::now()->endOfMonth());
- $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
- $unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
- $data = $this->generator->frontpage($paid, $unpaid);
+ $start = session('start', Carbon::now()->startOfMonth());
+ $end = session('end', Carbon::now()->endOfMonth());
+ $cache = new CacheProperties;
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('chart.bill.frontpage');
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+
+ $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
+ $unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
+ $chartData = [
+ strval(trans('firefly.unpaid')) => $unpaid,
+ strval(trans('firefly.paid')) => $paid,
+ ];
+
+ $data = $this->generator->pieChart($chartData);
+ $cache->store($data);
return Response::json($data);
}
/**
- * Shows the overview for a bill. The min/max amount and matched journals.
+ * @param JournalCollectorInterface $collector
+ * @param Bill $bill
*
- * @param Bill $bill
- *
- * @return \Symfony\Component\HttpFoundation\Response
+ * @return \Illuminate\Http\JsonResponse
*/
- public function single(Bill $bill)
+ public function single(JournalCollectorInterface $collector, Bill $bill)
{
$cache = new CacheProperties;
- $cache->addProperty('single');
- $cache->addProperty('bill');
+ $cache->addProperty('chart.bill.single');
$cache->addProperty($bill->id);
if ($cache->has()) {
return Response::json($cache->get());
}
- // get first transaction or today for start:
- $collector = new JournalCollector(auth()->user());
- $collector->setAllAssetAccounts()->setBills(new Collection([$bill]));
- $results = $collector->getJournals();
-
- // resort:
+ $results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals();
$results = $results->sortBy(
function (Transaction $transaction) {
return $transaction->date->format('U');
}
);
- $data = $this->generator->single($bill, $results);
+ $chartData = [
+ [
+ 'type' => 'bar',
+ 'label' => trans('firefly.min-amount'),
+ 'entries' => [],
+ ],
+ [
+ 'type' => 'bar',
+ 'label' => trans('firefly.max-amount'),
+ 'entries' => [],
+ ],
+ [
+ 'type' => 'line',
+ 'label' => trans('firefly.journal-amount'),
+ 'entries' => [],
+ ],
+ ];
+
+ /** @var Transaction $entry */
+ foreach ($results as $entry) {
+ $date = $entry->date->formatLocalized(strval(trans('config.month_and_day')));
+ // minimum amount of bill:
+ $chartData[0]['entries'][$date] = $bill->amount_min;
+ // maximum amount of bill:
+ $chartData[1]['entries'][$date] = $bill->amount_max;
+ // amount of journal:
+ $chartData[2]['entries'][$date] = bcmul($entry->transaction_amount, '-1');
+ }
+
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data);
diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php
index 3764d04423..4fcb736e0f 100644
--- a/app/Http/Controllers/Chart/BudgetController.php
+++ b/app/Http/Controllers/Chart/BudgetController.php
@@ -14,7 +14,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
-use FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface;
+use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget;
@@ -36,7 +36,7 @@ use Response;
class BudgetController extends Controller
{
- /** @var BudgetChartGeneratorInterface */
+ /** @var GeneratorInterface */
protected $generator;
/**
@@ -45,8 +45,7 @@ class BudgetController extends Controller
public function __construct()
{
parent::__construct();
- // create chart generator:
- $this->generator = app(BudgetChartGeneratorInterface::class);
+ $this->generator = app(GeneratorInterface::class);
}
/**
@@ -66,7 +65,7 @@ class BudgetController extends Controller
$cache = new CacheProperties();
$cache->addProperty($first);
$cache->addProperty($last);
- $cache->addProperty('budget');
+ $cache->addProperty('chart.budget.budget');
if ($cache->has()) {
return Response::json($cache->get());
@@ -77,7 +76,7 @@ class BudgetController extends Controller
$budgetCollection = new Collection([$budget]);
$last = Navigation::endOfX($last, $range, $final); // not to overshoot.
- $entries = new Collection;
+ $entries = [];
while ($first < $last) {
// periodspecific dates:
@@ -85,13 +84,14 @@ class BudgetController extends Controller
$currentEnd = Navigation::endOfPeriod($first, $range);
// sub another day because reasons.
$currentEnd->subDay();
- $spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
- $entry = [$first, ($spent * -1)];
- $entries->push($entry);
- $first = Navigation::addPeriod($first, $range, 0);
+ $spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
+ $format = Navigation::periodShow($first, $range);
+ $entries[$format] = bcmul($spent, '-1');
+ $first = Navigation::addPeriod($first, $range, 0);
}
- $data = $this->generator->budgetLimit($entries, 'month');
+ $data = $this->generator->singleSet(strval(trans('firefly.spent')), $entries);
+
$cache->store($data);
return Response::json($data);
@@ -113,25 +113,25 @@ class BudgetController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('budget-limit');
- $cache->addProperty($budget->id);
+ $cache->addProperty('chart.budget.budget.limit');
$cache->addProperty($repetition->id);
if ($cache->has()) {
return Response::json($cache->get());
}
- $entries = new Collection;
+ $entries = [];
$amount = $repetition->amount;
$budgetCollection = new Collection([$budget]);
while ($start <= $end) {
- $spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start);
- $amount = bcadd($amount, $spent);
- $entries->push([clone $start, round($amount, 2)]);
+ $spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start);
+ $amount = bcadd($amount, $spent);
+ $format = $start->formatLocalized(strval(trans('config.month_and_day')));
+ $entries[$format] = $amount;
$start->addDay();
}
- $data = $this->generator->budgetLimit($entries, 'month_and_day');
+ $data = $this->generator->singleSet(strval(trans('firefly.left')), $entries);
$cache->store($data);
return Response::json($data);
@@ -152,14 +152,30 @@ class BudgetController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('budget');
- $cache->addProperty('all');
+ $cache->addProperty('chart.budget.frontpage');
if ($cache->has()) {
return Response::json($cache->get());
}
$budgets = $repository->getActiveBudgets();
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
- $allEntries = new Collection;
+ $chartData = [
+ [
+ 'label' => strval(trans('firefly.spent_in_budget')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ [
+ 'label' => strval(trans('firefly.left_to_spend')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ [
+ 'label' => strval(trans('firefly.overspent')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ ];
+
/** @var Budget $budget */
foreach ($budgets as $budget) {
@@ -167,17 +183,34 @@ class BudgetController extends Controller
$reps = $this->filterRepetitions($repetitions, $budget, $start, $end);
if ($reps->count() === 0) {
- $collection = $this->spentInPeriodSingle($repository, $budget, $start, $end);
- $allEntries = $allEntries->merge($collection);
+ $row = $this->spentInPeriodSingle($repository, $budget, $start, $end);
+ if (bccomp($row['spent'], '0') !== 0 || bccomp($row['repetition_left'], '0') !== 0) {
+ $chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1');
+ $chartData[1]['entries'][$row['name']] = $row['repetition_left'];
+ $chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1');
+ }
continue;
}
- $collection = $this->spentInPeriodMulti($repository, $budget, $reps);
- $allEntries = $allEntries->merge($collection);
+ $rows = $this->spentInPeriodMulti($repository, $budget, $reps);
+ foreach ($rows as $row) {
+ if (bccomp($row['spent'], '0') !== 0 || bccomp($row['repetition_left'], '0') !== 0) {
+ $chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1');
+ $chartData[1]['entries'][$row['name']] = $row['repetition_left'];
+ $chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1');
+ }
+ }
+ unset($rows, $row);
}
- $entry = $this->spentInPeriodWithout($start, $end);
- $allEntries->push($entry);
- $data = $this->generator->frontpage($allEntries);
+ // for no budget:
+ $row = $this->spentInPeriodWithout($start, $end);
+ if (bccomp($row['spent'], '0') !== 0 || bccomp($row['repetition_left'], '0') !== 0) {
+ $chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1');
+ $chartData[1]['entries'][$row['name']] = $row['repetition_left'];
+ $chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1');
+ }
+
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data);
@@ -201,20 +234,19 @@ class BudgetController extends Controller
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty($budget->id);
- $cache->addProperty('budget');
- $cache->addProperty('period');
+ $cache->addProperty('chart.budget.period');
if ($cache->has()) {
- return Response::json($cache->get());
+ return Response::json($cache->get());
}
- // the expenses:
- $periods = Navigation::listOfPeriods($start, $end);
- $entries = $repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end, false);
+ // get the expenses
$budgeted = [];
+ $periods = Navigation::listOfPeriods($start, $end);
+ $entries = $repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end);
$key = Navigation::preferredCarbonFormat($start, $end);
$range = Navigation::preferredRangeFormat($start, $end);
- // get budgeted:
+ // get the budget limits (if any)
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
$current = clone $start;
while ($current < $end) {
@@ -235,18 +267,29 @@ class BudgetController extends Controller
$current = clone $currentEnd;
}
- // join them:
- $result = [];
+ // join them into one set of data:
+ $chartData = [
+ [
+ 'label' => strval(trans('firefly.spent')),
+ 'type' => 'bar',
+ 'entries' => [],
+ ],
+ [
+ 'label' => strval(trans('firefly.budgeted')),
+ 'type' => 'bar',
+ 'entries' => [],
+ ],
+ ];
+
foreach (array_keys($periods) as $period) {
- $nice = $periods[$period];
- $result[$nice] = [
- 'spent' => isset($entries[$budget->id]['entries'][$period]) ? $entries[$budget->id]['entries'][$period] : '0',
- 'budgeted' => isset($entries[$period]) ? $budgeted[$period] : 0,
- ];
+ $label = $periods[$period];
+ $spent = isset($entries[$budget->id]['entries'][$period]) ? $entries[$budget->id]['entries'][$period] : '0';
+ $limit = isset($entries[$period]) ? $budgeted[$period] : 0;
+ $chartData[0]['entries'][$label] = bcmul($spent, '-1');
+ $chartData[1]['entries'][$label] = $limit;
+
}
-
- $data = $this->generator->period($result);
-
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data);
@@ -267,27 +310,23 @@ class BudgetController extends Controller
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
- $cache->addProperty('no-budget');
- $cache->addProperty('period');
+ $cache->addProperty('chart.budget.no-budget');
if ($cache->has()) {
return Response::json($cache->get());
}
// the expenses:
- $periods = Navigation::listOfPeriods($start, $end);
- $entries = $repository->getNoBudgetPeriodReport($accounts, $start, $end);
+ $periods = Navigation::listOfPeriods($start, $end);
+ $entries = $repository->getNoBudgetPeriodReport($accounts, $start, $end);
+ $chartData = [];
// join them:
- $result = [];
foreach (array_keys($periods) as $period) {
- $nice = $periods[$period];
- $result[$nice] = [
- 'spent' => isset($entries['entries'][$period]) ? $entries['entries'][$period] : '0',
- ];
+ $label = $periods[$period];
+ $spent = isset($entries['entries'][$period]) ? $entries['entries'][$period] : '0';
+ $chartData[$label] = bcmul($spent, '-1');
}
-
- $data = $this->generator->periodNoBudget($result);
-
+ $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data);
return Response::json($data);
@@ -316,17 +355,25 @@ class BudgetController extends Controller
}
/**
+ * Returns an array with the following values:
+ * 0 =>
+ * 'name' => name of budget + repetition
+ * 'repetition_left' => left in budget repetition (always zero)
+ * 'repetition_overspent' => spent more than budget repetition? (always zero)
+ * 'spent' => actually spent in period for budget
+ * 1 => (etc)
+ *
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
* @param Collection $repetitions
*
- * @return Collection
+ * @return array
*/
- private function spentInPeriodMulti(BudgetRepositoryInterface $repository, Budget $budget, Collection $repetitions): Collection
+ private function spentInPeriodMulti(BudgetRepositoryInterface $repository, Budget $budget, Collection $repetitions): array
{
- $format = strval(trans('config.month_and_day'));
- $collection = new Collection;
- $name = $budget->name;
+ $return = [];
+ $format = strval(trans('config.month_and_day'));
+ $name = $budget->name;
/** @var LimitRepetition $repetition */
foreach ($repetitions as $repetition) {
$expenses = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate);
@@ -341,35 +388,52 @@ class BudgetController extends Controller
$left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses);
$spent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses;
$overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';
- $array = [$name, $left, $spent, $overspent, $amount, $spent];
- $collection->push($array);
+ $return[] = [
+ 'name' => $name,
+ 'repetition_left' => $left,
+ 'repetition_overspent' => $overspent,
+ 'spent' => $spent,
+ ];
}
- return $collection;
+ return $return;
}
/**
+ * Returns an array with the following values:
+ * 'name' => name of budget
+ * 'repetition_left' => left in budget repetition (always zero)
+ * 'repetition_overspent' => spent more than budget repetition? (always zero)
+ * 'spent' => actually spent in period for budget
+ *
+ *
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
- * @return Collection
+ * @return array
*/
- private function spentInPeriodSingle(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end): Collection
+ private function spentInPeriodSingle(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end): array
{
- $collection = new Collection;
- $amount = '0';
- $left = '0';
- $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
- $overspent = '0';
- $array = [$budget->name, $left, $spent, $overspent, $amount, $spent];
- $collection->push($array);
+ $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
+ $array = [
+ 'name' => $budget->name,
+ 'repetition_left' => '0',
+ 'repetition_overspent' => '0',
+ 'spent' => $spent,
+ ];
- return $collection;
+ return $array;
}
/**
+ * Returns an array with the following values:
+ * 'name' => "no budget" in local language
+ * 'repetition_left' => left in budget repetition (always zero)
+ * 'repetition_overspent' => spent more than budget repetition? (always zero)
+ * 'spent' => actually spent in period for budget
+ *
* @param Carbon $start
* @param Carbon $end
*
@@ -387,7 +451,13 @@ class BudgetController extends Controller
foreach ($journals as $entry) {
$sum = bcadd($entry->transaction_amount, $sum);
}
+ $array = [
+ 'name' => strval(trans('firefly.no_budget')),
+ 'repetition_left' => '0',
+ 'repetition_overspent' => '0',
+ 'spent' => $sum,
+ ];
- return [trans('firefly.no_budget'), '0', '0', $sum, '0', '0'];
+ return $array;
}
}
diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php
new file mode 100644
index 0000000000..e053189066
--- /dev/null
+++ b/app/Http/Controllers/Chart/BudgetReportController.php
@@ -0,0 +1,292 @@
+middleware(
+ function ($request, $next) {
+ $this->generator = app(GeneratorInterface::class);
+ $this->budgetRepository = app(BudgetRepositoryInterface::class);
+ $this->accountRepository = app(AccountRepositoryInterface::class);
+
+ return $next($request);
+ }
+ );
+ }
+
+ /**
+ * @param Collection $accounts
+ * @param Collection $budgets
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param string $others
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function accountExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others)
+ {
+ /** @var bool $others */
+ $others = intval($others) === 1;
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.budget.report.account-expense');
+ $cache->addProperty($accounts);
+ $cache->addProperty($budgets);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+
+ $names = [];
+ $set = $this->getExpenses($accounts, $budgets, $start, $end);
+ $grouped = $this->groupByOpposingAccount($set);
+ $chartData = [];
+ $total = '0';
+
+ foreach ($grouped as $accountId => $amount) {
+ if (!isset($names[$accountId])) {
+ $account = $this->accountRepository->find(intval($accountId));
+ $names[$accountId] = $account->name;
+ }
+ $amount = bcmul($amount, '-1');
+ $total = bcadd($total, $amount);
+ $chartData[$names[$accountId]] = $amount;
+ }
+
+ // also collect all transactions NOT in these budgets.
+ if ($others) {
+ $collector = new JournalCollector(auth()->user());
+ $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
+ $journals = $collector->getJournals();
+ $sum = strval($journals->sum('transaction_amount'));
+ $sum = bcmul($sum, '-1');
+ $sum = bcsub($sum, $total);
+ $chartData[strval(trans('firefly.everything_else'))] = $sum;
+ }
+
+ $data = $this->generator->pieChart($chartData);
+ $cache->store($data);
+
+ return Response::json($data);
+ }
+
+ /**
+ * @param Collection $accounts
+ * @param Collection $budgets
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param string $others
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function budgetExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others)
+ {
+ /** @var bool $others */
+ $others = intval($others) === 1;
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.budget.report.budget-expense');
+ $cache->addProperty($accounts);
+ $cache->addProperty($budgets);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+
+ $names = [];
+ $set = $this->getExpenses($accounts, $budgets, $start, $end);
+ $grouped = $this->groupByBudget($set);
+ $total = '0';
+ $chartData = [];
+
+ foreach ($grouped as $budgetId => $amount) {
+ if (!isset($names[$budgetId])) {
+ $budget = $this->budgetRepository->find(intval($budgetId));
+ $names[$budgetId] = $budget->name;
+ }
+ $amount = bcmul($amount, '-1');
+ $total = bcadd($total, $amount);
+ $chartData[$names[$budgetId]] = $amount;
+ }
+
+ // also collect all transactions NOT in these budgets.
+ if ($others) {
+ $collector = new JournalCollector(auth()->user());
+ $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
+ $journals = $collector->getJournals();
+ $sum = strval($journals->sum('transaction_amount'));
+ $sum = bcmul($sum, '-1');
+ $sum = bcsub($sum, $total);
+ $chartData[strval(trans('firefly.everything_else'))] = $sum;
+ }
+
+ $data = $this->generator->pieChart($chartData);
+ $cache->store($data);
+
+ return Response::json($data);
+ }
+
+ /**
+ * @param Collection $accounts
+ * @param Collection $budgets
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function mainChart(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end)
+ {
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.budget.report.main');
+ $cache->addProperty($accounts);
+ $cache->addProperty($budgets);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+
+ $format = Navigation::preferredCarbonLocalizedFormat($start, $end);
+ $function = Navigation::preferredEndOfPeriod($start, $end);
+ $chartData = [];
+ $currentStart = clone $start;
+
+ // prep chart data:
+ foreach ($budgets as $budget) {
+ $chartData[$budget->id] = [
+ 'label' => $budget->name,
+ 'type' => 'bar',
+ 'entries' => [],
+ ];
+ }
+
+ while ($currentStart < $end) {
+ $currentEnd = clone $currentStart;
+ $currentEnd = $currentEnd->$function();
+ $expenses = $this->groupByBudget($this->getExpenses($accounts, $budgets, $currentStart, $currentEnd));
+ $label = $currentStart->formatLocalized($format);
+
+ /** @var Budget $budget */
+ foreach ($budgets as $budget) {
+ $chartData[$budget->id]['entries'][$label] = $expenses[$budget->id] ?? '0';
+ }
+ $currentStart = clone $currentEnd;
+ $currentStart->addDay();
+ }
+
+ $data = $this->generator->multiSet($chartData);
+ $cache->store($data);
+
+ return Response::json($data);
+ }
+
+
+ /**
+ * @param Collection $accounts
+ * @param Collection $budgets
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ private function getExpenses(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): Collection
+ {
+ $collector = new JournalCollector(auth()->user());
+ $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
+ ->setBudgets($budgets)->withOpposingAccount()->disableFilter();
+ $accountIds = $accounts->pluck('id')->toArray();
+ $transactions = $collector->getJournals();
+ $set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
+
+ return $set;
+ }
+
+ /**
+ * @param Collection $set
+ *
+ * @return array
+ */
+ private function groupByBudget(Collection $set): array
+ {
+ // group by category ID:
+ $grouped = [];
+ /** @var Transaction $transaction */
+ foreach ($set as $transaction) {
+ $jrnlBudId = intval($transaction->transaction_journal_budget_id);
+ $transBudId = intval($transaction->transaction_budget_id);
+ $budgetId = max($jrnlBudId, $transBudId);
+ $grouped[$budgetId] = $grouped[$budgetId] ?? '0';
+ $grouped[$budgetId] = bcadd($transaction->transaction_amount, $grouped[$budgetId]);
+ }
+
+ return $grouped;
+ }
+
+ /**
+ * @param Collection $set
+ *
+ * @return array
+ */
+ private function groupByOpposingAccount(Collection $set): array
+ {
+ $grouped = [];
+ /** @var Transaction $transaction */
+ foreach ($set as $transaction) {
+ $accountId = $transaction->opposing_account_id;
+ $grouped[$accountId] = $grouped[$accountId] ?? '0';
+ $grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]);
+ }
+
+ return $grouped;
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php
index a2ff4338c7..c0ff85e2e3 100644
--- a/app/Http/Controllers/Chart/CategoryController.php
+++ b/app/Http/Controllers/Chart/CategoryController.php
@@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
-use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface;
+use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category;
@@ -26,7 +26,6 @@ use Illuminate\Support\Collection;
use Navigation;
use Preferences;
use Response;
-use stdClass;
/**
* Class CategoryController
@@ -35,7 +34,7 @@ use stdClass;
*/
class CategoryController extends Controller
{
- /** @var CategoryChartGeneratorInterface */
+ /** @var GeneratorInterface */
protected $generator;
/**
@@ -45,7 +44,7 @@ class CategoryController extends Controller
{
parent::__construct();
// create chart generator:
- $this->generator = app(CategoryChartGeneratorInterface::class);
+ $this->generator = app(GeneratorInterface::class);
}
/**
@@ -59,34 +58,42 @@ class CategoryController extends Controller
*/
public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category)
{
- $start = $repository->firstUseDate($category);
- $range = Preferences::get('viewRange', '1M')->data;
- $start = Navigation::startOfPeriod($start, $range);
- $categoryCollection = new Collection([$category]);
- $end = new Carbon;
- $entries = new Collection;
- $cache = new CacheProperties;
- $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty('all');
- $cache->addProperty('categories');
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.category.all');
+ $cache->addProperty($category->id);
if ($cache->has()) {
return Response::json($cache->get());
}
+ $start = $repository->firstUseDate($category);
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = Navigation::startOfPeriod($start, $range);
+ $end = new Carbon;
+ $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
+ $chartData = [
+ [
+ 'label' => strval(trans('firefly.spent')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ [
+ 'label' => strval(trans('firefly.earned')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ ];
+
while ($start <= $end) {
- $currentEnd = Navigation::endOfPeriod($start, $range);
- $spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $currentEnd);
- $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $currentEnd);
- $date = Navigation::periodShow($start, $range);
- $entries->push([clone $start, $date, $spent, $earned]);
- $start = Navigation::addPeriod($start, $range, 0);
+ $currentEnd = Navigation::endOfPeriod($start, $range);
+ $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
+ $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
+ $label = Navigation::periodShow($start, $range);
+ $chartData[0]['entries'][$label] = bcmul($spent, '-1');
+ $chartData[1]['entries'][$label] = $earned;
+ $start = Navigation::addPeriod($start, $range, 0);
}
- $entries = $entries->reverse();
- $entries = $entries->slice(0, 48);
- $entries = $entries->reverse();
- $data = $this->generator->all($entries);
+
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data);
@@ -122,34 +129,29 @@ class CategoryController extends Controller
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('category');
- $cache->addProperty('frontpage');
+ $cache->addProperty('chart.category.frontpage');
if ($cache->has()) {
return Response::json($cache->get());
}
+ $chartData = [];
$categories = $repository->getCategories();
$accounts = $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
- $set = new Collection;
/** @var Category $category */
foreach ($categories as $category) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
if (bccomp($spent, '0') === -1) {
- $category->spent = $spent;
- $set->push($category);
+ $chartData[$category->name] = bcmul($spent, '-1');
}
}
- // this is a "fake" entry for the "no category" entry.
- $entry = new stdClass;
- $entry->name = trans('firefly.no_category');
- $entry->spent = $repository->spentInPeriodWithoutCategory(new Collection, $start, $end);
- $set->push($entry);
+ $chartData[strval(trans('firefly.no_category'))] = bcmul($repository->spentInPeriodWithoutCategory(new Collection, $start, $end), '-1');
- $set = $set->sortBy('spent');
- $data = $this->generator->frontpage($set);
+ // sort
+ arsort($chartData);
+
+ $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data);
return Response::json($data);
-
}
/**
@@ -166,35 +168,43 @@ class CategoryController extends Controller
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('category-period-chart');
+ $cache->addProperty('chart.category.period');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($category);
if ($cache->has()) {
-
return $cache->get();
}
- $expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
- $income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
- $periods = Navigation::listOfPeriods($start, $end);
+ $expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
+ $income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
+ $periods = Navigation::listOfPeriods($start, $end);
+ $chartData = [
+ [
+ 'label' => strval(trans('firefly.spent')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ [
+ 'label' => strval(trans('firefly.earned')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ ];
-
- // join them:
- $result = [];
foreach (array_keys($periods) as $period) {
- $nice = $periods[$period];
- $result[$nice] = [
- 'earned' => $income[$category->id]['entries'][$period] ?? '0',
- 'spent' => $expenses[$category->id]['entries'][$period] ?? '0',
- ];
+ $label = $periods[$period];
+ $spent = $expenses[$category->id]['entries'][$period] ?? '0';
+ $chartData[0]['entries'][$label] = bcmul($spent, '-1');
+ $chartData[1]['entries'][$label] = $income[$category->id]['entries'][$period] ?? '0';
}
- $data = $this->generator->reportPeriod($result);
+
+ $data = $this->generator->multiSet($chartData);
+ $cache->store($data);
return Response::json($data);
}
/**
* @param CRI $repository
- * @param Category $category
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
@@ -206,26 +216,36 @@ class CategoryController extends Controller
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('no-category-period-chart');
+ $cache->addProperty('chart.category.period.no-cat');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
-
return $cache->get();
}
- $expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
- $income = $repository->periodIncomeNoCategory($accounts, $start, $end);
- $periods = Navigation::listOfPeriods($start, $end);
+ $expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
+ $income = $repository->periodIncomeNoCategory($accounts, $start, $end);
+ $periods = Navigation::listOfPeriods($start, $end);
+ $chartData = [
+ [
+ 'label' => strval(trans('firefly.spent')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ [
+ 'label' => strval(trans('firefly.earned')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ ];
- // join them:
- $result = [];
foreach (array_keys($periods) as $period) {
- $nice = $periods[$period];
- $result[$nice] = [
- 'earned' => $income['entries'][$period] ?? '0',
- 'spent' => $expenses['entries'][$period] ?? '0',
- ];
+ $label = $periods[$period];
+ $spent = $expenses['entries'][$period] ?? '0';
+ $chartData[0]['entries'][$label] = bcmul($spent, '-1');
+ $chartData[1]['entries'][$label] = $income['entries'][$period] ?? '0';
+
}
- $data = $this->generator->reportPeriod($result);
+ $data = $this->generator->multiSet($chartData);
+ $cache->store($data);
return Response::json($data);
}
@@ -260,33 +280,47 @@ class CategoryController extends Controller
*/
private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end)
{
- $categoryCollection = new Collection([$category]);
- $cache = new CacheProperties;
+ $cache = new CacheProperties;
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty($category->id);
+ $cache->addProperty('chart.category.period-chart');
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty($accounts);
- $cache->addProperty($category->id);
- $cache->addProperty('specific-period');
-
-
if ($cache->has()) {
return $cache->get();
}
- $entries = new Collection;
+
+ // chart data
+ $chartData = [
+ [
+ 'label' => strval(trans('firefly.spent')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ [
+ 'label' => strval(trans('firefly.earned')),
+ 'entries' => [],
+ 'type' => 'bar',
+ ],
+ ];
+
while ($start <= $end) {
- $spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $start);
- $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $start);
- $date = Navigation::periodShow($start, '1D');
- $entries->push([clone $start, $date, $spent, $earned]);
+ $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
+ $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
+ $label = Navigation::periodShow($start, '1D');
+
+ $chartData[0]['entries'][$label] = bcmul($spent, '-1');
+ $chartData[1]['entries'][$label] = $earned;
+
+
$start->addDay();
}
- $data = $this->generator->period($entries);
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return $data;
diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php
index 0cabf7ad52..003648218c 100644
--- a/app/Http/Controllers/Chart/CategoryReportController.php
+++ b/app/Http/Controllers/Chart/CategoryReportController.php
@@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
-use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface;
+use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Controllers\Controller;
@@ -24,8 +24,9 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
+use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
-use Log;
+use Navigation;
use Response;
@@ -43,7 +44,7 @@ class CategoryReportController extends Controller
private $accountRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
- /** @var CategoryChartGeneratorInterface */
+ /** @var GeneratorInterface */
private $generator;
/**
@@ -54,7 +55,7 @@ class CategoryReportController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
- $this->generator = app(CategoryChartGeneratorInterface::class);
+ $this->generator = app(GeneratorInterface::class);
$this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
@@ -76,38 +77,45 @@ class CategoryReportController extends Controller
{
/** @var bool $others */
$others = intval($others) === 1;
- $names = [];
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.category.report.account-expense');
+ $cache->addProperty($accounts);
+ $cache->addProperty($categories);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
- // collect journals (just like the category report does):
- $set = $this->getExpenses($accounts, $categories, $start, $end);
- $grouped = $this->groupByOpposingAccount($set);
+ $names = [];
+ $set = $this->getExpenses($accounts, $categories, $start, $end);
+ $grouped = $this->groupByOpposingAccount($set);
+ $chartData = [];
+ $total = '0';
- // show the grouped results:
- $result = [];
- $total = '0';
foreach ($grouped as $accountId => $amount) {
if (!isset($names[$accountId])) {
$account = $this->accountRepository->find(intval($accountId));
$names[$accountId] = $account->name;
}
- $amount = bcmul($amount, '-1');
- $total = bcadd($total, $amount);
- $result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount];
+ $amount = bcmul($amount, '-1');
+ $total = bcadd($total, $amount);
+ $chartData[$names[$accountId]] = $amount;
}
// also collect all transactions NOT in these categories.
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
- $journals = $collector->getJournals();
- $sum = strval($journals->sum('transaction_amount'));
- $sum = bcmul($sum, '-1');
- Log::debug(sprintf('Sum of others in accountExpense is %f', $sum));
- $sum = bcsub($sum, $total);
- $result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
+ $journals = $collector->getJournals();
+ $sum = strval($journals->sum('transaction_amount'));
+ $sum = bcmul($sum, '-1');
+ $sum = bcsub($sum, $total);
+ $chartData[strval(trans('firefly.everything_else'))] = $sum;
}
- $data = $this->generator->pieChart($result);
+ $data = $this->generator->pieChart($chartData);
+ $cache->store($data);
return Response::json($data);
}
@@ -125,36 +133,44 @@ class CategoryReportController extends Controller
{
/** @var bool $others */
$others = intval($others) === 1;
- $names = [];
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.category.report.account-income');
+ $cache->addProperty($accounts);
+ $cache->addProperty($categories);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
- // collect journals (just like the category report does):
- $set = $this->getIncome($accounts, $categories, $start, $end);
- $grouped = $this->groupByOpposingAccount($set);
- // loop and show the grouped results:
- $result = [];
- $total = '0';
+ $names = [];
+ $set = $this->getIncome($accounts, $categories, $start, $end);
+ $grouped = $this->groupByOpposingAccount($set);
+ $chartData = [];
+ $total = '0';
+
foreach ($grouped as $accountId => $amount) {
if (!isset($names[$accountId])) {
$account = $this->accountRepository->find(intval($accountId));
$names[$accountId] = $account->name;
}
- $total = bcadd($total, $amount);
- $result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount];
+ $total = bcadd($total, $amount);
+ $chartData[$names[$accountId]] = $amount;
}
// also collect others?
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
- $journals = $collector->getJournals();
- $sum = strval($journals->sum('transaction_amount'));
- Log::debug(sprintf('Sum of others in accountIncome is %f', $sum));
- $sum = bcsub($sum, $total);
- $result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
+ $journals = $collector->getJournals();
+ $sum = strval($journals->sum('transaction_amount'));
+ $sum = bcsub($sum, $total);
+ $chartData[strval(trans('firefly.everything_else'))] = $sum;
}
- $data = $this->generator->pieChart($result);
+ $data = $this->generator->pieChart($chartData);
+ $cache->store($data);
return Response::json($data);
}
@@ -172,38 +188,45 @@ class CategoryReportController extends Controller
{
/** @var bool $others */
$others = intval($others) === 1;
- $names = [];
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.category.report.category-expense');
+ $cache->addProperty($accounts);
+ $cache->addProperty($categories);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
- // collect journals (just like the category report does):
- $set = $this->getExpenses($accounts, $categories, $start, $end);
- $grouped = $this->groupByCategory($set);
+ $names = [];
+ $set = $this->getExpenses($accounts, $categories, $start, $end);
+ $grouped = $this->groupByCategory($set);
+ $total = '0';
+ $chartData = [];
- // show the grouped results:
- $result = [];
- $total = '0';
foreach ($grouped as $categoryId => $amount) {
if (!isset($names[$categoryId])) {
$category = $this->categoryRepository->find(intval($categoryId));
$names[$categoryId] = $category->name;
}
- $amount = bcmul($amount, '-1');
- $total = bcadd($total, $amount);
- $result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount];
+ $amount = bcmul($amount, '-1');
+ $total = bcadd($total, $amount);
+ $chartData[$names[$categoryId]] = $amount;
}
// also collect all transactions NOT in these categories.
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
- $journals = $collector->getJournals();
- $sum = strval($journals->sum('transaction_amount'));
- $sum = bcmul($sum, '-1');
- Log::debug(sprintf('Sum of others in categoryExpense is %f', $sum));
- $sum = bcsub($sum, $total);
- $result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
+ $journals = $collector->getJournals();
+ $sum = strval($journals->sum('transaction_amount'));
+ $sum = bcmul($sum, '-1');
+ $sum = bcsub($sum, $total);
+ $chartData[strval(trans('firefly.everything_else'))] = $sum;
}
- $data = $this->generator->pieChart($result);
+ $data = $this->generator->pieChart($chartData);
+ $cache->store($data);
return Response::json($data);
}
@@ -221,36 +244,42 @@ class CategoryReportController extends Controller
{
/** @var bool $others */
$others = intval($others) === 1;
- $names = [];
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.category.report.category-income');
+ $cache->addProperty($accounts);
+ $cache->addProperty($categories);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
- // collect journals (just like the category report does):
- $set = $this->getIncome($accounts, $categories, $start, $end);
- $grouped = $this->groupByCategory($set);
+ $names = [];
+ $set = $this->getIncome($accounts, $categories, $start, $end);
+ $grouped = $this->groupByCategory($set);
+ $total = '0';
+ $chartData = [];
- // loop and show the grouped results:
- $result = [];
- $total = '0';
foreach ($grouped as $categoryId => $amount) {
if (!isset($names[$categoryId])) {
$category = $this->categoryRepository->find(intval($categoryId));
$names[$categoryId] = $category->name;
}
- $total = bcadd($total, $amount);
- $result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount];
+ $total = bcadd($total, $amount);
+ $chartData[$names[$categoryId]] = $amount;
}
- // also collect others?
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
- $journals = $collector->getJournals();
- $sum = strval($journals->sum('transaction_amount'));
- Log::debug(sprintf('Sum of others in categoryIncome is %f', $sum));
- $sum = bcsub($sum, $total);
- $result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
+ $journals = $collector->getJournals();
+ $sum = strval($journals->sum('transaction_amount'));
+ $sum = bcsub($sum, $total);
+ $chartData[strval(trans('firefly.everything_else'))] = $sum;
}
- $data = $this->generator->pieChart($result);
+ $data = $this->generator->pieChart($chartData);
+ $cache->store($data);
return Response::json($data);
}
@@ -265,52 +294,56 @@ class CategoryReportController extends Controller
*/
public function mainChart(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
{
- // determin optimal period:
- $period = '1D';
- $format = 'month_and_day';
- $function = 'endOfDay';
- if ($start->diffInMonths($end) > 1) {
- $period = '1M';
- $format = 'month';
- $function = 'endOfMonth';
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.category.report.main');
+ $cache->addProperty($accounts);
+ $cache->addProperty($categories);
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return Response::json($cache->get());
}
- if ($start->diffInMonths($end) > 13) {
- $period = '1Y';
- $format = 'year';
- $function = 'endOfYear';
- }
- Log::debug(sprintf('Period is %s', $period));
- $data = [];
+
+ $format = Navigation::preferredCarbonLocalizedFormat($start, $end);
+ $function = Navigation::preferredEndOfPeriod($start, $end);
+ $chartData = [];
$currentStart = clone $start;
+
+ // prep chart data:
+ foreach ($categories as $category) {
+ $chartData[$category->id . '-in'] = [
+ 'label' => $category->name . ' (' . strtolower(strval(trans('firefly.income'))) . ')',
+ 'type' => 'bar',
+ 'entries' => [],
+ ];
+ $chartData[$category->id . '-out'] = [
+ 'label' => $category->name . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')',
+ 'type' => 'bar',
+ 'entries' => [],
+ ];
+ }
+
while ($currentStart < $end) {
$currentEnd = clone $currentStart;
- Log::debug(sprintf('Function is %s', $function));
$currentEnd = $currentEnd->$function();
$expenses = $this->groupByCategory($this->getExpenses($accounts, $categories, $currentStart, $currentEnd));
$income = $this->groupByCategory($this->getIncome($accounts, $categories, $currentStart, $currentEnd));
- $label = $currentStart->formatLocalized(strval(trans('config.' . $format)));
-
- Log::debug(sprintf('Now grabbing CMC expenses between %s and %s', $currentStart->format('Y-m-d'), $currentEnd->format('Y-m-d')));
-
- $data[$label] = [
- 'in' => [],
- 'out' => [],
- ];
+ $label = $currentStart->formatLocalized($format);
/** @var Category $category */
foreach ($categories as $category) {
+ $labelIn = $category->id . '-in';
+ $labelOut = $category->id . '-out';
// get sum, and get label:
- $categoryId = $category->id;
- $data[$label]['name'][$categoryId] = $category->name;
- $data[$label]['in'][$categoryId] = $income[$categoryId] ?? '0';
- $data[$label]['out'][$categoryId] = $expenses[$categoryId] ?? '0';
+ $chartData[$labelIn]['entries'][$label] = $income[$category->id] ?? '0';
+ $chartData[$labelOut]['entries'][$label] = $expenses[$category->id] ?? '0';
}
-
$currentStart = clone $currentEnd;
$currentStart->addDay();
}
- $data = $this->generator->mainReportChart($data);
+ $data = $this->generator->multiSet($chartData);
+ $cache->store($data);
return Response::json($data);
}
diff --git a/app/Http/Controllers/Chart/PiggyBankController.php b/app/Http/Controllers/Chart/PiggyBankController.php
index bdcf1408f5..aa11d8bbf0 100644
--- a/app/Http/Controllers/Chart/PiggyBankController.php
+++ b/app/Http/Controllers/Chart/PiggyBankController.php
@@ -13,13 +13,12 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Chart;
-use FireflyIII\Generator\Chart\PiggyBank\PiggyBankChartGeneratorInterface;
+use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\CacheProperties;
-use Illuminate\Support\Collection;
use Response;
@@ -31,7 +30,7 @@ use Response;
class PiggyBankController extends Controller
{
- /** @var PiggyBankChartGeneratorInterface */
+ /** @var GeneratorInterface */
protected $generator;
/**
@@ -41,7 +40,7 @@ class PiggyBankController extends Controller
{
parent::__construct();
// create chart generator:
- $this->generator = app(PiggyBankChartGeneratorInterface::class);
+ $this->generator = app(GeneratorInterface::class);
}
/**
@@ -56,26 +55,24 @@ class PiggyBankController extends Controller
{
// chart properties for cache:
$cache = new CacheProperties;
- $cache->addProperty('piggy-history');
+ $cache->addProperty('chart.piggy-bank.history');
$cache->addProperty($piggyBank->id);
if ($cache->has()) {
return Response::json($cache->get());
}
- $set = $repository->getEvents($piggyBank);
- $set = $set->reverse();
- $collection = [];
+ $set = $repository->getEvents($piggyBank);
+ $set = $set->reverse();
+ $chartData = [];
+ $sum = '0';
/** @var PiggyBankEvent $entry */
foreach ($set as $entry) {
- $date = $entry->date->format('Y-m-d');
- $amount = $entry->amount;
- if (isset($collection[$date])) {
- $amount = bcadd($amount, $collection[$date]);
- }
- $collection[$date] = $amount;
+ $label = $entry->date->formatLocalized(strval(trans('config.month_and_day')));
+ $sum = bcadd($sum, $entry->amount);
+ $chartData[$label] = $sum;
}
- $data = $this->generator->history(new Collection($collection));
+ $data = $this->generator->singleSet($piggyBank->name, $chartData);
$cache->store($data);
return Response::json($data);
diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php
index d490481db2..03833b4504 100644
--- a/app/Http/Controllers/Chart/ReportController.php
+++ b/app/Http/Controllers/Chart/ReportController.php
@@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
-use FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface;
+use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties;
@@ -32,7 +32,7 @@ use Steam;
class ReportController extends Controller
{
- /** @var ReportChartGeneratorInterface */
+ /** @var GeneratorInterface */
protected $generator;
/**
@@ -42,7 +42,7 @@ class ReportController extends Controller
{
parent::__construct();
// create chart generator:
- $this->generator = app(ReportChartGeneratorInterface::class);
+ $this->generator = app(GeneratorInterface::class);
}
/**
@@ -50,38 +50,34 @@ class ReportController extends Controller
* which means that giving it a 2 week "period" should be enough granularity.
*
* @param Collection $accounts
- * @param Carbon $start
+ * @param Carbon $start
* @param Carbon $end
+ *
* @return \Illuminate\Http\JsonResponse
*/
public function netWorth(Collection $accounts, Carbon $start, Carbon $end)
{
// chart properties for cache:
$cache = new CacheProperties;
- $cache->addProperty('netWorth');
+ $cache->addProperty('chart.report.net-worth');
$cache->addProperty($start);
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get());
}
- $ids = $accounts->pluck('id')->toArray();
- $current = clone $start;
- $entries = new Collection;
+ $ids = $accounts->pluck('id')->toArray();
+ $current = clone $start;
+ $chartData = [];
while ($current < $end) {
- $balances = Steam::balancesById($ids, $current);
- $sum = $this->arraySum($balances);
- $entries->push(
- [
- 'date' => clone $current,
- 'net-worth' => $sum,
- ]
- );
-
+ $balances = Steam::balancesById($ids, $current);
+ $sum = $this->arraySum($balances);
+ $label = $current->formatLocalized(strval(trans('config.month_and_day')));
+ $chartData[$label] = $sum;
$current->addDays(7);
}
- $data = $this->generator->netWorth($entries);
+ $data = $this->generator->singleSet(strval(trans('firefly.net_worth')), $chartData);
$cache->store($data);
return Response::json($data);
@@ -102,35 +98,52 @@ class ReportController extends Controller
{
// chart properties for cache:
$cache = new CacheProperties;
- $cache->addProperty('yearInOut');
+ $cache->addProperty('chart.report.operations');
$cache->addProperty($start);
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get());
}
+ $format = Navigation::preferredCarbonLocalizedFormat($start, $end);
+ $source = $this->getChartData($accounts, $start, $end);
+ $chartData = [
+ [
+ 'label' => trans('firefly.income'),
+ 'type' => 'bar',
+ 'entries' => [],
+ ],
+ [
+ 'label' => trans('firefly.expenses'),
+ 'type' => 'bar',
+ 'entries' => [],
+ ],
+ ];
- $chartSource = $this->getYearData($accounts, $start, $end);
-
- if ($start->diffInMonths($end) > 12) {
- // data = method X
- $data = $this->multiYearOperations($chartSource['earned'], $chartSource['spent'], $start, $end);
- $cache->store($data);
-
- return Response::json($data);
+ foreach ($source['earned'] as $date => $amount) {
+ $carbon = new Carbon($date);
+ $label = $carbon->formatLocalized($format);
+ $earned = $chartData[0]['entries'][$label] ?? '0';
+ $chartData[0]['entries'][$label] = bcadd($earned, $amount);
+ }
+ foreach ($source['spent'] as $date => $amount) {
+ $carbon = new Carbon($date);
+ $label = $carbon->formatLocalized($format);
+ $spent = $chartData[1]['entries'][$label] ?? '0';
+ $chartData[1]['entries'][$label] = bcadd($spent, $amount);
}
- // data = method Y
- $data = $this->singleYearOperations($chartSource['earned'], $chartSource['spent'], $start, $end);
+
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data);
-
}
/**
* Shows sum income and expense, debet/credit: operations
+ *
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
@@ -140,168 +153,73 @@ class ReportController extends Controller
public function sum(Collection $accounts, Carbon $start, Carbon $end)
{
+
// chart properties for cache:
$cache = new CacheProperties;
- $cache->addProperty('yearInOutSummarized');
+ $cache->addProperty('chart.report.sum');
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
if ($cache->has()) {
return Response::json($cache->get());
}
- $chartSource = $this->getYearData($accounts, $start, $end);
-
- if ($start->diffInMonths($end) > 12) {
- // per year
- $data = $this->multiYearSum($chartSource['earned'], $chartSource['spent'], $start, $end);
- $cache->store($data);
-
- return Response::json($data);
+ $source = $this->getChartData($accounts, $start, $end);
+ $numbers = [
+ 'sum_earned' => '0',
+ 'avg_earned' => '0',
+ 'count_earned' => 0,
+ 'sum_spent' => '0',
+ 'avg_spent' => '0',
+ 'count_spent' => 0,
+ ];
+ foreach ($source['earned'] as $amount) {
+ $numbers['sum_earned'] = bcadd($amount, $numbers['sum_earned']);
+ $numbers['count_earned']++;
}
- // per month!
- $data = $this->singleYearSum($chartSource['earned'], $chartSource['spent'], $start, $end);
+ if ($numbers['count_earned'] > 0) {
+ $numbers['avg_earned'] = $numbers['sum_earned'] / $numbers['count_earned'];
+ }
+ foreach ($source['spent'] as $amount) {
+ $numbers['sum_spent'] = bcadd($amount, $numbers['sum_spent']);
+ $numbers['count_spent']++;
+ }
+ if ($numbers['count_spent'] > 0) {
+ $numbers['avg_spent'] = $numbers['sum_spent'] / $numbers['count_spent'];
+ }
+
+ $chartData = [
+ [
+ 'label' => strval(trans('firefly.income')),
+ 'type' => 'bar',
+ 'entries' => [
+ strval(trans('firefly.sum_of_period')) => $numbers['sum_earned'],
+ strval(trans('firefly.average_in_period')) => $numbers['avg_earned'],
+ ],
+ ],
+ [
+ 'label' => trans('firefly.expenses'),
+ 'type' => 'bar',
+ 'entries' => [
+ strval(trans('firefly.sum_of_period')) => $numbers['sum_spent'],
+ strval(trans('firefly.average_in_period')) => $numbers['avg_spent'],
+ ],
+ ],
+ ];
+
+
+ $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data);
}
- /**
- * @param array $earned
- * @param array $spent
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- protected function multiYearOperations(array $earned, array $spent, Carbon $start, Carbon $end)
- {
- $entries = new Collection;
- while ($start < $end) {
-
- $incomeSum = $this->pluckFromArray($start->year, $earned);
- $expenseSum = $this->pluckFromArray($start->year, $spent);
-
- $entries->push([clone $start, $incomeSum, $expenseSum]);
- $start->addYear();
- }
-
- $data = $this->generator->multiYearOperations($entries);
-
- return $data;
- }
-
- /**
- * @param array $earned
- * @param array $spent
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- protected function multiYearSum(array $earned, array $spent, Carbon $start, Carbon $end)
- {
- $income = '0';
- $expense = '0';
- $count = 0;
- while ($start < $end) {
-
- $currentIncome = $this->pluckFromArray($start->year, $earned);
- $currentExpense = $this->pluckFromArray($start->year, $spent);
- $income = bcadd($income, $currentIncome);
- $expense = bcadd($expense, $currentExpense);
-
- $count++;
- $start->addYear();
- }
-
- $data = $this->generator->multiYearSum($income, $expense, $count);
-
- return $data;
- }
-
- /**
- * @param int $year
- * @param array $set
- *
- * @return string
- */
- protected function pluckFromArray($year, array $set)
- {
- $sum = '0';
- foreach ($set as $date => $amount) {
- if (substr($date, 0, 4) == $year) {
- $sum = bcadd($sum, $amount);
- }
- }
-
- return $sum;
-
- }
-
- /**
- * @param array $earned
- * @param array $spent
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- protected function singleYearOperations(array $earned, array $spent, Carbon $start, Carbon $end)
- {
- // per month? simply use each month.
-
- $entries = new Collection;
- while ($start < $end) {
- // total income and total expenses:
- $date = $start->format('Y-m');
- $incomeSum = isset($earned[$date]) ? $earned[$date] : 0;
- $expenseSum = isset($spent[$date]) ? $spent[$date] : 0;
-
- $entries->push([clone $start, $incomeSum, $expenseSum]);
- $start->addMonth();
- }
-
- $data = $this->generator->yearOperations($entries);
-
- return $data;
- }
-
- /**
- * @param array $earned
- * @param array $spent
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- protected function singleYearSum(array $earned, array $spent, Carbon $start, Carbon $end)
- {
- $income = '0';
- $expense = '0';
- $count = 0;
- while ($start < $end) {
- $date = $start->format('Y-m');
- $currentIncome = isset($earned[$date]) ? $earned[$date] : 0;
- $currentExpense = isset($spent[$date]) ? $spent[$date] : 0;
- $income = bcadd($income, $currentIncome);
- $expense = bcadd($expense, $currentExpense);
-
- $count++;
- $start->addMonth();
- }
-
- $data = $this->generator->yearSum($income, $expense, $count);
-
- return $data;
- }
-
/**
* @param $array
*
* @return string
*/
- private function arraySum($array) : string
+ private function arraySum($array): string
{
$sum = '0';
foreach ($array as $entry) {
@@ -312,31 +230,45 @@ class ReportController extends Controller
}
/**
+ * Collects the incomes and expenses for the given periods, grouped per month. Will cache its results
+ *
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
- private function getYearData(Collection $accounts, Carbon $start, Carbon $end): array
+ private function getChartData(Collection $accounts, Carbon $start, Carbon $end): array
{
+ $cache = new CacheProperties;
+ $cache->addProperty('chart.report.get-chart-data');
+ $cache->addProperty($start);
+ $cache->addProperty($accounts);
+ $cache->addProperty($end);
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+
$tasker = app(AccountTaskerInterface::class);
$currentStart = clone $start;
$spentArray = [];
$earnedArray = [];
while ($currentStart <= $end) {
- $currentEnd = Navigation::endOfPeriod($currentStart, '1M');
- $date = $currentStart->format('Y-m');
- $spent = $tasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
- $earned = $tasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
- $spentArray[$date] = bcmul($spent, '-1');
- $earnedArray[$date] = $earned;
- $currentStart = Navigation::addPeriod($currentStart, '1M', 0);
+ $currentEnd = Navigation::endOfPeriod($currentStart, '1M');
+ $label = $currentStart->format('Y-m') . '-01';
+ $spent = $tasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
+ $earned = $tasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
+ $spentArray[$label] = bcmul($spent, '-1');
+ $earnedArray[$label] = $earned;
+ $currentStart = Navigation::addPeriod($currentStart, '1M', 0);
}
-
- return [
+ $result = [
'spent' => $spentArray,
'earned' => $earnedArray,
];
+ $cache->store($result);
+
+ return $result;
}
}
diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php
index f5948f4785..c57e86b7ea 100644
--- a/app/Http/Controllers/CurrencyController.php
+++ b/app/Http/Controllers/CurrencyController.php
@@ -89,14 +89,16 @@ class CurrencyController extends Controller
}
+
/**
- * @param TransactionCurrency $currency
+ * @param CurrencyRepositoryInterface $repository
+ * @param TransactionCurrency $currency
*
- * @return \Illuminate\Http\RedirectResponse|View
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
- public function delete(TransactionCurrency $currency)
+ public function delete(CurrencyRepositoryInterface $repository, TransactionCurrency $currency)
{
- if (!$this->canDeleteCurrency($currency)) {
+ if (!$repository->canDeleteCurrency($currency)) {
Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
return redirect(route('currencies.index'));
@@ -114,23 +116,21 @@ class CurrencyController extends Controller
}
/**
- * @param TransactionCurrency $currency
+ * @param CurrencyRepositoryInterface $repository
+ * @param TransactionCurrency $currency
*
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Exception
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
- public function destroy(TransactionCurrency $currency)
+ public function destroy(CurrencyRepositoryInterface $repository, TransactionCurrency $currency)
{
- if (!$this->canDeleteCurrency($currency)) {
+ if (!$repository->canDeleteCurrency($currency)) {
Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
return redirect(route('currencies.index'));
}
+ $repository->destroy($currency);
Session::flash('success', trans('firefly.deleted_currency', ['name' => $currency->name]));
- if (auth()->user()->hasRole('owner')) {
- $currency->forceDelete();
- }
return redirect(session('currencies.delete.url'));
}
@@ -170,7 +170,7 @@ class CurrencyController extends Controller
if (!auth()->user()->hasRole('owner')) {
- Session::flash('warning', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
+ Session::flash('warning', trans('firefly.ask_site_owner', ['site_owner' => env('SITE_OWNER')]));
}
@@ -235,40 +235,4 @@ class CurrencyController extends Controller
return redirect(session('currencies.edit.url'));
}
-
- /**
- * @param TransactionCurrency $currency
- *
- * @return bool
- */
- private function canDeleteCurrency(TransactionCurrency $currency): bool
- {
- $repository = app(CurrencyRepositoryInterface::class);
-
- // has transactions still
- if ($repository->countJournals($currency) > 0) {
- return false;
- }
-
- // is the only currency left
- if ($repository->get()->count() === 1) {
- return false;
- }
-
- // is the default currency for the user or the system
- $defaultCode = Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'))->data;
- if ($currency->code === $defaultCode) {
- return false;
- }
-
- // is the default currency for the system
- $defaultSystemCode = config('firefly.default_currency', 'EUR');
- if ($currency->code === $defaultSystemCode) {
- return false;
- }
-
- // can be deleted
- return true;
- }
-
}
diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php
index 35920e268f..ef293cf7b7 100644
--- a/app/Http/Controllers/ExportController.php
+++ b/app/Http/Controllers/ExportController.php
@@ -17,7 +17,7 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use ExpandedForm;
use FireflyIII\Exceptions\FireflyException;
-use FireflyIII\Export\Processor;
+use FireflyIII\Export\ProcessorInterface;
use FireflyIII\Http\Requests\ExportFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ExportJob;
@@ -71,7 +71,6 @@ class ExportController extends Controller
throw new FireflyException('Against all expectations, zip file "' . $file . '" does not exist.');
}
-
$job->change('export_downloaded');
return response($disk->get($file), 200)
@@ -133,7 +132,6 @@ class ExportController extends Controller
*/
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, EJRI $jobs)
{
- set_time_limit(0);
$job = $jobs->findByKey($request->get('job'));
$settings = [
'accounts' => $repository->getAccountsById($request->get('accounts')),
@@ -146,7 +144,9 @@ class ExportController extends Controller
];
$job->change('export_status_make_exporter');
- $processor = new Processor($settings);
+
+ /** @var ProcessorInterface $processor */
+ $processor = app(ProcessorInterface::class, [$settings]);
/*
* Collect journals:
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index c41e03e366..6882a0f14a 100644
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -166,9 +166,51 @@ class HomeController extends Controller
public function routes()
{
// these routes are not relevant for the help pages:
- $ignore = ['login', 'registe', 'logout', 'two-fac', 'lost-two', 'confirm', 'resend', 'do_confirm', 'testFla', 'json.', 'piggy-banks.add',
- 'piggy-banks.remove', 'preferences.', 'rules.rule.up', 'rules.rule.down', 'rules.rule-group.up', 'rules.rule-group.down', 'popup.report',
- 'admin.users.domains.block-', 'import.json', 'help.',
+ $ignore = [
+ // login and two-factor routes:
+ 'login',
+ 'registe',
+ 'password.rese',
+ 'logout',
+ 'two-fac',
+ 'lost-two',
+ 'confirm',
+ 'resend',
+ 'do_confirm',
+ // test troutes
+ 'test-flash',
+ 'all-routes',
+ // json routes
+ 'json.',
+ // routes that point to modals or that redirect immediately.
+ 'piggy-banks.add',
+ 'piggy-banks.remove',
+ 'rules.rule.up',
+ 'attachments.download',
+ 'bills.rescan',
+ 'rules.rule.down',
+ 'rules.rule-group.up',
+ 'rules.rule-group.down',
+ 'popup.',
+ 'error',
+ 'flush',
+ //'preferences.',
+ 'admin.users.domains.block-',
+ 'help.',
+ // ajax routes:
+ 'import.json',
+ // charts:
+ 'chart.',
+ // report data:
+ 'report-data.',
+
+ // others:
+ 'debugbar',
+ 'attachments.preview',
+ 'budgets.income',
+ 'currencies.default',
+
+
];
$routes = Route::getRoutes();
$return = '
';
diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php
index bbf09436c2..270afe99d1 100644
--- a/app/Http/Controllers/ImportController.php
+++ b/app/Http/Controllers/ImportController.php
@@ -145,10 +145,13 @@ class ImportController extends Controller
return $this->redirectToCorrectStep($job);
}
+ // if there is a tag (there might not be), we can link to it:
+ $tagId = $job->extended_status['importTag'] ?? 0;
+
$subTitle = trans('firefly.import_finished');
$subTitleIcon = 'fa-star';
- return view('import.finished', compact('job', 'subTitle', 'subTitleIcon'));
+ return view('import.finished', compact('job', 'subTitle', 'subTitleIcon', 'tagId'));
}
/**
@@ -318,7 +321,8 @@ class ImportController extends Controller
{
set_time_limit(0);
if ($job->status == 'settings_complete') {
- ImportProcedure::runImport($job);
+ $importProcedure = new ImportProcedure;
+ $importProcedure->runImport($job);
}
}
diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php
index fd9622228d..e8ddd70eb3 100644
--- a/app/Http/Controllers/PreferencesController.php
+++ b/app/Http/Controllers/PreferencesController.php
@@ -212,7 +212,7 @@ class PreferencesController extends Controller
/**
* @return string
*/
- private function getDomain() : string
+ private function getDomain(): string
{
$url = url()->to('/');
$parts = parse_url($url);
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index e14fabe16b..8f8fbc6b07 100644
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -15,9 +15,9 @@ namespace FireflyIII\Http\Controllers;
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest;
-use FireflyIII\User;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
use Hash;
-use Preferences;
+use Log;
use Session;
use View;
@@ -112,12 +112,12 @@ class ProfileController extends Controller
}
/**
+ * @param UserRepositoryInterface $repository
* @param DeleteAccountFormRequest $request
*
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Exception
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
- public function postDeleteAccount(DeleteAccountFormRequest $request)
+ public function postDeleteAccount(UserRepositoryInterface $repository, DeleteAccountFormRequest $request)
{
// old, new1, new2
if (!Hash::check($request->get('password'), auth()->user()->password)) {
@@ -125,34 +125,16 @@ class ProfileController extends Controller
return redirect(route('profile.delete-account'));
}
-
- // store some stuff for the future:
- $registration = Preferences::get('registration_ip_address')->data;
- $confirmation = Preferences::get('confirmation_ip_address')->data;
-
- // DELETE!
- $email = auth()->user()->email;
- auth()->user()->delete();
+ $user = auth()->user();
+ Log::info(sprintf('User #%d has opted to delete their account', auth()->user()->id));
+ // make repository delete user:
+ auth()->logout();
Session::flush();
+ $repository->destroy($user);
+
Session::flash('gaEventCategory', 'user');
Session::flash('gaEventAction', 'delete-account');
- // create a new user with the same email address so re-registration is blocked.
- $newUser = User::create(
- [
- 'email' => $email,
- 'password' => 'deleted',
- 'blocked' => 1,
- 'blocked_code' => 'deleted',
- ]
- );
- if (strlen($registration) > 0) {
- Preferences::setForUser($newUser, 'registration_ip_address', $registration);
-
- }
- if (strlen($confirmation) > 0) {
- Preferences::setForUser($newUser, 'confirmation_ip_address', $confirmation);
- }
return redirect(route('index'));
}
diff --git a/app/Http/Controllers/Report/BalanceController.php b/app/Http/Controllers/Report/BalanceController.php
index 905aa429c4..664217472a 100644
--- a/app/Http/Controllers/Report/BalanceController.php
+++ b/app/Http/Controllers/Report/BalanceController.php
@@ -36,7 +36,7 @@ class BalanceController extends Controller
*
* @return mixed|string
*/
- public function general(BalanceReportHelperInterface $helper,Collection $accounts, Carbon $start, Carbon $end)
+ public function general(BalanceReportHelperInterface $helper, Collection $accounts, Carbon $start, Carbon $end)
{
diff --git a/app/Http/Controllers/Report/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php
index 475e0ce67f..0d5f3e3f07 100644
--- a/app/Http/Controllers/Report/CategoryController.php
+++ b/app/Http/Controllers/Report/CategoryController.php
@@ -99,12 +99,12 @@ class CategoryController extends Controller
}
/**
- * @param ReportHelperInterface $helper
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
*
* @return mixed|string
+ * @internal param ReportHelperInterface $helper
*/
public function operations(Collection $accounts, Carbon $start, Carbon $end)
{
diff --git a/app/Http/Controllers/Report/OperationsController.php b/app/Http/Controllers/Report/OperationsController.php
index 7fb7084906..3282e98d80 100644
--- a/app/Http/Controllers/Report/OperationsController.php
+++ b/app/Http/Controllers/Report/OperationsController.php
@@ -16,7 +16,6 @@ namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
-use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
@@ -57,6 +56,33 @@ class OperationsController extends Controller
}
+ /**
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ public function income(Collection $accounts, Carbon $start, Carbon $end)
+ {
+ // chart properties for cache:
+ $cache = new CacheProperties;
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('income-report');
+ $cache->addProperty($accounts->pluck('id')->toArray());
+ if ($cache->has()) {
+ return $cache->get();
+ }
+ $income = $this->getIncomeReport($start, $end, $accounts);
+
+ $result = view('reports.partials.income', compact('income'))->render();
+ $cache->store($result);
+
+ return $result;
+
+ }
+
/**
* @param Collection $accounts
* @param Carbon $start
@@ -101,33 +127,6 @@ class OperationsController extends Controller
}
- /**
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return string
- */
- public function income(Collection $accounts, Carbon $start, Carbon $end)
- {
- // chart properties for cache:
- $cache = new CacheProperties;
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty('income-report');
- $cache->addProperty($accounts->pluck('id')->toArray());
- if ($cache->has()) {
- //return $cache->get();
- }
- $income = $this->getIncomeReport($start, $end, $accounts);
-
- $result = view('reports.partials.income', compact('income'))->render();
- $cache->store($result);
-
- return $result;
-
- }
-
/**
* @param Carbon $start
* @param Carbon $end
diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php
index 655c3b2a22..e2348dc6d8 100644
--- a/app/Http/Controllers/ReportController.php
+++ b/app/Http/Controllers/ReportController.php
@@ -20,6 +20,7 @@ use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Requests\ReportFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
+use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Collection;
@@ -95,6 +96,42 @@ class ReportController extends Controller
}
+ /**
+ * @param Collection $accounts
+ * @param Collection $budgets
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ public function budgetReport(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end)
+ {
+ if ($end < $start) {
+ return view('error')->with('message', trans('firefly.end_after_start_date'));
+ }
+ if ($start < session('first')) {
+ $start = session('first');
+ }
+
+ View::share(
+ 'subTitle', trans(
+ 'firefly.report_budget',
+ [
+ 'start' => $start->formatLocalized($this->monthFormat),
+ 'end' => $end->formatLocalized($this->monthFormat),
+ ]
+ )
+ );
+
+ $generator = ReportGeneratorFactory::reportGenerator('Budget', $start, $end);
+ $generator->setAccounts($accounts);
+ $generator->setBudgets($budgets);
+ $result = $generator->generate();
+
+ return $result;
+
+ }
+
/**
* @param Collection $accounts
* @param Collection $categories
@@ -198,6 +235,9 @@ class ReportController extends Controller
case 'category':
$result = $this->categoryReportOptions();
break;
+ case 'budget':
+ $result = $this->budgetReportOptions();
+ break;
}
return Response::json(['html' => $result]);
@@ -217,6 +257,7 @@ class ReportController extends Controller
$end = $request->getEndDate()->format('Ymd');
$accounts = join(',', $request->getAccountList()->pluck('id')->toArray());
$categories = join(',', $request->getCategoryList()->pluck('id')->toArray());
+ $budgets = join(',', $request->getBudgetList()->pluck('id')->toArray());
if ($request->getAccountList()->count() === 0) {
Session::flash('error', trans('firefly.select_more_than_one_account'));
@@ -230,6 +271,12 @@ class ReportController extends Controller
return redirect(route('reports.index'));
}
+ if ($request->getBudgetList()->count() === 0 && $reportType === 'budget') {
+ Session::flash('error', trans('firefly.select_more_than_one_budget'));
+
+ return redirect(route('reports.index'));
+ }
+
if ($end < $start) {
return view('error')->with('message', trans('firefly.end_after_start_date'));
}
@@ -251,11 +298,28 @@ class ReportController extends Controller
case 'audit':
$uri = route('reports.report.audit', [$accounts, $start, $end]);
break;
+ case 'budget':
+ $uri = route('reports.report.budget', [$accounts, $budgets, $start, $end]);
+ break;
}
return redirect($uri);
}
+ /**
+ * @return string
+ */
+ private function budgetReportOptions(): string
+ {
+ /** @var BudgetRepositoryInterface $repository */
+ $repository = app(BudgetRepositoryInterface::class);
+ $budgets = $repository->getBudgets();
+ $result = view('reports.options.budget', compact('budgets'))->render();
+
+ return $result;
+
+ }
+
/**
* @return string
*/
diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php
index c06fa91ff4..f83a317229 100644
--- a/app/Http/Controllers/Transaction/ConvertController.php
+++ b/app/Http/Controllers/Transaction/ConvertController.php
@@ -172,14 +172,14 @@ class ConvertController extends Controller
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined);
- case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: # one
+ case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one
$destination = $sourceAccount;
break;
- case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: # two
+ case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: // two
$destination = $accountRepository->find(intval($data['destination_account_asset']));
break;
- case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: # three
- case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: #five
+ case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three
+ case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five
$data = [
'name' => $data['destination_account_expense'],
'accountType' => 'expense',
@@ -189,8 +189,8 @@ class ConvertController extends Controller
];
$destination = $accountRepository->store($data);
break;
- case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: # four
- case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: #six
+ case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: // four
+ case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six
$destination = $destinationAccount;
break;
}
diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php
index 54e886abab..8b234d3e36 100644
--- a/app/Http/Controllers/Transaction/MassController.php
+++ b/app/Http/Controllers/Transaction/MassController.php
@@ -58,7 +58,7 @@ class MassController extends Controller
*
* @return View
*/
- public function massDelete(Collection $journals)
+ public function delete(Collection $journals)
{
$subTitle = trans('firefly.mass_delete_journals');
@@ -77,7 +77,7 @@ class MassController extends Controller
*
* @return mixed
*/
- public function massDestroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository)
+ public function destroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository)
{
$ids = $request->get('confirm_mass_delete');
$set = new Collection;
@@ -114,7 +114,7 @@ class MassController extends Controller
*
* @return View
*/
- public function massEdit(Collection $journals)
+ public function edit(Collection $journals)
{
$subTitle = trans('firefly.mass_edit_journals');
@@ -187,7 +187,7 @@ class MassController extends Controller
*
* @return mixed
*/
- public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository)
+ public function update(MassEditJournalRequest $request, JournalRepositoryInterface $repository)
{
$journalIds = $request->get('journals');
$count = 0;
diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php
index ddf9115e9a..98ba1458de 100644
--- a/app/Http/Controllers/Transaction/SingleController.php
+++ b/app/Http/Controllers/Transaction/SingleController.php
@@ -184,7 +184,7 @@ class SingleController extends Controller
$what = strtolower(TransactionJournal::transactionTypeStr($journal));
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
- $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
+ $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getBudgets());
// view related code
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php
index 595fb2ddbe..eb061a862f 100644
--- a/app/Http/Controllers/Transaction/SplitController.php
+++ b/app/Http/Controllers/Transaction/SplitController.php
@@ -131,6 +131,7 @@ class SplitController extends Controller
*/
public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
{
+
if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal);
}
diff --git a/app/Http/Middleware/IsNotConfirmed.php b/app/Http/Middleware/IsNotConfirmed.php
index 237f28c17a..0037c00e7a 100644
--- a/app/Http/Middleware/IsNotConfirmed.php
+++ b/app/Http/Middleware/IsNotConfirmed.php
@@ -17,6 +17,7 @@ use Closure;
use FireflyConfig;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
+use Log;
use Preferences;
/**
@@ -47,9 +48,13 @@ class IsNotConfirmed
}
// must the user be confirmed in the first place?
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
+ Log::debug(sprintf('mustConfirmAccount is %s', $mustConfirmAccount));
// user must be logged in, then continue:
$isConfirmed = Preferences::get('user_confirmed', false)->data;
+ Log::debug(sprintf('isConfirmed is %s', $isConfirmed));
if ($isConfirmed || $mustConfirmAccount === false) {
+ Log::debug('User is confirmed or user does not have to confirm account. Redirect home.');
+
// user account is confirmed, simply send them home.
return redirect(route('home'));
}
diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php
index faac1971d8..1e219707ee 100644
--- a/app/Http/Requests/AccountFormRequest.php
+++ b/app/Http/Requests/AccountFormRequest.php
@@ -39,15 +39,15 @@ class AccountFormRequest extends Request
public function getAccountData(): array
{
return [
- 'name' => trim($this->input('name')),
+ 'name' => trim(strval($this->input('name'))),
'active' => intval($this->input('active')) === 1,
'accountType' => $this->input('what'),
'currency_id' => intval($this->input('currency_id')),
'virtualBalance' => round($this->input('virtualBalance'), 2),
'virtualBalanceCurrency' => intval($this->input('amount_currency_id_virtualBalance')),
- 'iban' => trim($this->input('iban')),
- 'BIC' => trim($this->input('BIC')),
- 'accountNumber' => trim($this->input('accountNumber')),
+ 'iban' => trim(strval($this->input('iban'))),
+ 'BIC' => trim(strval($this->input('BIC'))),
+ 'accountNumber' => trim(strval($this->input('accountNumber'))),
'accountRole' => $this->input('accountRole'),
'openingBalance' => round($this->input('openingBalance'), 2),
'openingBalanceDate' => new Carbon((string)$this->input('openingBalanceDate')),
@@ -80,7 +80,7 @@ class AccountFormRequest extends Request
'name' => $nameRule,
'openingBalance' => 'numeric',
'iban' => 'iban',
- 'BIC' => 'bic',
+ 'BIC' => 'bic',
'virtualBalance' => 'numeric',
'openingBalanceDate' => 'date',
'currency_id' => 'exists:transaction_currencies,id',
diff --git a/app/Http/Requests/ConfigurationRequest.php b/app/Http/Requests/ConfigurationRequest.php
index c2f2d3becd..1647835a3b 100644
--- a/app/Http/Requests/ConfigurationRequest.php
+++ b/app/Http/Requests/ConfigurationRequest.php
@@ -36,9 +36,14 @@ class ConfigurationRequest extends Request
public function getConfigurationData(): array
{
return [
- 'single_user_mode' => intval($this->get('single_user_mode')) === 1,
- 'must_confirm_account' => intval($this->get('must_confirm_account')) === 1,
- 'is_demo_site' => intval($this->get('is_demo_site')) === 1,
+ 'single_user_mode' => intval($this->get('single_user_mode')) === 1,
+ 'must_confirm_account' => intval($this->get('must_confirm_account')) === 1,
+ 'is_demo_site' => intval($this->get('is_demo_site')) === 1,
+ 'mail_for_lockout' => intval($this->get('mail_for_lockout')) === 1,
+ 'mail_for_blocked_domain' => intval($this->get('mail_for_blocked_domain')) === 1,
+ 'mail_for_blocked_email' => intval($this->get('mail_for_blocked_email')) === 1,
+ 'mail_for_bad_login' => intval($this->get('mail_for_bad_login')) === 1,
+ 'mail_for_blocked_login' => intval($this->get('mail_for_blocked_login')) === 1,
];
}
@@ -48,9 +53,14 @@ class ConfigurationRequest extends Request
public function rules()
{
$rules = [
- 'single_user_mode' => 'between:0,1|numeric',
- 'must_confirm_account' => 'between:0,1|numeric',
- 'is_demo_site' => 'between:0,1|numeric',
+ 'single_user_mode' => 'between:0,1|numeric',
+ 'must_confirm_account' => 'between:0,1|numeric',
+ 'is_demo_site' => 'between:0,1|numeric',
+ 'mail_for_lockout' => 'between:0,1|numeric',
+ 'mail_for_blocked_domain' => 'between:0,1|numeric',
+ 'mail_for_blocked_email' => 'between:0,1|numeric',
+ 'mail_for_bad_login' => 'between:0,1|numeric',
+ 'mail_for_blocked_login' => 'between:0,1|numeric',
];
return $rules;
diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php
index eb1f2f1ed6..7f582042a7 100644
--- a/app/Http/Requests/JournalFormRequest.php
+++ b/app/Http/Requests/JournalFormRequest.php
@@ -161,6 +161,7 @@ class JournalFormRequest extends Request
private function getFieldOrEmptyString(string $field): string
{
$string = $this->get($field) ?? '';
+
return trim($string);
}
}
diff --git a/app/Http/Requests/ReportFormRequest.php b/app/Http/Requests/ReportFormRequest.php
index 7d97a37b76..763af9dd6a 100644
--- a/app/Http/Requests/ReportFormRequest.php
+++ b/app/Http/Requests/ReportFormRequest.php
@@ -17,6 +17,7 @@ use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
+use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use Illuminate\Support\Collection;
@@ -40,7 +41,7 @@ class ReportFormRequest extends Request
/**
* @return Collection
*/
- public function getAccountList():Collection
+ public function getAccountList(): Collection
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
@@ -58,6 +59,27 @@ class ReportFormRequest extends Request
return $collection;
}
+ /**
+ * @return Collection
+ */
+ public function getBudgetList(): Collection
+ {
+ /** @var BudgetRepositoryInterface $repository */
+ $repository = app(BudgetRepositoryInterface::class);
+ $set = $this->get('budget');
+ $collection = new Collection;
+ if (is_array($set)) {
+ foreach ($set as $budgetId) {
+ $budget = $repository->find(intval($budgetId));
+ if (!is_null($budget->id)) {
+ $collection->push($budget);
+ }
+ }
+ }
+
+ return $collection;
+ }
+
/**
* @return Collection
*/
@@ -125,7 +147,7 @@ class ReportFormRequest extends Request
public function rules(): array
{
return [
- 'report_type' => 'in:audit,default,category',
+ 'report_type' => 'in:audit,default,category,budget',
];
}
diff --git a/app/Http/Requests/TagFormRequest.php b/app/Http/Requests/TagFormRequest.php
index c0501c1798..d7bdb63f54 100644
--- a/app/Http/Requests/TagFormRequest.php
+++ b/app/Http/Requests/TagFormRequest.php
@@ -35,7 +35,7 @@ class TagFormRequest extends Request
/**
* @return array
*/
- public function collectTagData() :array
+ public function collectTagData(): array
{
if ($this->get('setTag') == 'true') {
$latitude = $this->get('latitude');
diff --git a/app/Http/Requests/UserFormRequest.php b/app/Http/Requests/UserFormRequest.php
new file mode 100644
index 0000000000..faf12136e1
--- /dev/null
+++ b/app/Http/Requests/UserFormRequest.php
@@ -0,0 +1,60 @@
+check();
+ }
+
+ /**
+ * @return array
+ */
+ public function getUserData(): array
+ {
+ return [
+ 'email' => trim($this->get('email')),
+ 'blocked' => intval($this->get('blocked')),
+ 'blocked_code' => trim($this->get('blocked_code')),
+ 'password' => trim($this->get('password')),
+
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+ 'id' => 'required|exists:users,id',
+ 'email' => 'required',
+ 'password' => 'confirmed',
+ 'blocked_code' => 'between:0,30',
+ 'blocked' => 'between:0,1|numeric',
+ ];
+ }
+}
diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php
index abf249d27a..5b99d097ee 100644
--- a/app/Http/breadcrumbs.php
+++ b/app/Http/breadcrumbs.php
@@ -18,6 +18,7 @@ use FireflyIII\Models\Attachment;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
+use FireflyIII\Models\ImportJob;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Rule;
@@ -27,6 +28,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
+use Illuminate\Support\Collection;
/**
* HOME
@@ -67,29 +69,33 @@ Breadcrumbs::register(
Breadcrumbs::register(
'accounts.show', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
$what = config('firefly.shortNamesByFullName.' . $account->accountType->type);
+
$breadcrumbs->parent('accounts.index', $what);
- $breadcrumbs->push(e($account->name), route('accounts.show', [$account->id]));
+ $breadcrumbs->push($account->name, route('accounts.show', [$account->id]));
}
);
Breadcrumbs::register(
- 'accounts.show.date', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $date) {
+ 'accounts.show.date', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $start, Carbon $end) {
+
+ $startString = $start->formatLocalized(strval(trans('config.month_and_day')));
+ $endString = $end->formatLocalized(strval(trans('config.month_and_day')));
+ $title = sprintf('%s (%s)', $account->name, trans('firefly.from_to', ['start' => $startString, 'end' => $endString]));
+
$breadcrumbs->parent('accounts.show', $account);
-
- $range = Preferences::get('viewRange', '1M')->data;
- $title = $account->name . ' (' . Navigation::periodShow($date, $range) . ')';
-
- $breadcrumbs->push($title, route('accounts.show.date', [$account->id, $date->format('Y-m-d')]));
+ $breadcrumbs->push($title, route('accounts.show.date', [$account->id, $start->format('Y-m-d')]));
}
);
Breadcrumbs::register(
- 'accounts.show.all', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
+ 'accounts.show.all', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $start, Carbon $end) {
+
+ $startString = $start->formatLocalized(strval(trans('config.month_and_day')));
+ $endString = $end->formatLocalized(strval(trans('config.month_and_day')));
+ $title = sprintf('%s (%s)', $account->name, trans('firefly.from_to', ['start' => $startString, 'end' => $endString]));
+
$breadcrumbs->parent('accounts.show', $account);
-
- $title = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
-
- $breadcrumbs->push($title, route('accounts.show.all', [$account->id]));
+ $breadcrumbs->push($title, route('accounts.show.all', [$account->id, $start->format('Y-m-d')]));
}
);
@@ -134,6 +140,12 @@ Breadcrumbs::register(
$breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', [$user->id]));
}
);
+Breadcrumbs::register(
+ 'admin.users.edit', function (BreadCrumbGenerator $breadcrumbs, User $user) {
+ $breadcrumbs->parent('admin.users');
+ $breadcrumbs->push(trans('firefly.edit_user', ['email' => $user->email]), route('admin.users.edit', [$user->id]));
+}
+);
Breadcrumbs::register(
'admin.users.domains', function (BreadCrumbGenerator $breadcrumbs) {
@@ -248,21 +260,26 @@ Breadcrumbs::register(
);
Breadcrumbs::register(
- 'budgets.noBudget', function (BreadCrumbGenerator $breadcrumbs, $subTitle) {
+ 'budgets.no-budget', function (BreadCrumbGenerator $breadcrumbs, $subTitle) {
$breadcrumbs->parent('budgets.index');
- $breadcrumbs->push($subTitle, route('budgets.noBudget'));
+ $breadcrumbs->push($subTitle, route('budgets.no-budget'));
}
);
Breadcrumbs::register(
- 'budgets.show', function (BreadCrumbGenerator $breadcrumbs, Budget $budget, LimitRepetition $repetition = null) {
+ 'budgets.show', function (BreadCrumbGenerator $breadcrumbs, Budget $budget) {
$breadcrumbs->parent('budgets.index');
$breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id]));
- if (!is_null($repetition) && !is_null($repetition->id)) {
- $breadcrumbs->push(
- Navigation::periodShow($repetition->startdate, $repetition->budgetLimit->repeat_freq), route('budgets.show', [$budget->id, $repetition->id])
- );
- }
+}
+);
+
+Breadcrumbs::register(
+ 'budgets.show.repetition', function (BreadCrumbGenerator $breadcrumbs, Budget $budget, LimitRepetition $repetition) {
+ $breadcrumbs->parent('budgets.index');
+ $breadcrumbs->push(e($budget->name), route('budgets.show.repetition', [$budget->id, $repetition->id]));
+ $breadcrumbs->push(
+ Navigation::periodShow($repetition->startdate, $repetition->budgetLimit->repeat_freq), route('budgets.show', [$budget->id, $repetition->id])
+ );
}
);
@@ -317,9 +334,9 @@ Breadcrumbs::register(
);
Breadcrumbs::register(
- 'categories.noCategory', function (BreadCrumbGenerator $breadcrumbs, $subTitle) {
+ 'categories.no-category', function (BreadCrumbGenerator $breadcrumbs, $subTitle) {
$breadcrumbs->parent('categories.index');
- $breadcrumbs->push($subTitle, route('categories.noCategory'));
+ $breadcrumbs->push($subTitle, route('categories.no-category'));
}
);
@@ -400,6 +417,35 @@ Breadcrumbs::register(
}
);
+/**
+ * IMPORT
+ */
+Breadcrumbs::register(
+ 'import.index', function (BreadCrumbGenerator $breadcrumbs) {
+ $breadcrumbs->parent('home');
+ $breadcrumbs->push(trans('firefly.import'), route('import.index'));
+}
+);
+Breadcrumbs::register(
+ 'import.complete', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
+ $breadcrumbs->parent('import.index');
+ $breadcrumbs->push(trans('firefly.bread_crumb_import_complete', ['key' => $job->key]), route('import.complete', [$job->key]));
+}
+);
+Breadcrumbs::register(
+ 'import.configure', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
+ $breadcrumbs->parent('import.index');
+ $breadcrumbs->push(trans('firefly.bread_crumb_configure_import', ['key' => $job->key]), route('import.configure', [$job->key]));
+}
+);
+Breadcrumbs::register(
+ 'import.finished', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
+ $breadcrumbs->parent('import.index');
+ $breadcrumbs->push(trans('firefly.bread_crumb_import_finished', ['key' => $job->key]), route('import.finished', [$job->key]));
+}
+);
+
+
/**
* PREFERENCES
*/
@@ -407,7 +453,6 @@ Breadcrumbs::register(
'preferences.index', function (BreadCrumbGenerator $breadcrumbs) {
$breadcrumbs->parent('home');
$breadcrumbs->push(trans('breadcrumbs.preferences'), route('preferences.index'));
-
}
);
@@ -646,20 +691,36 @@ Breadcrumbs::register(
}
);
+/**
+ * MASS TRANSACTION EDIT / DELETE
+ */
+Breadcrumbs::register(
+ 'transactions.mass.edit', function (BreadCrumbGenerator $breadcrumbs, Collection $journals) {
+
+ $journalIds = $journals->pluck('id')->toArray();
+ $what = strtolower($journals->first()->transactionType->type);
+ $breadcrumbs->parent('transactions.index', $what);
+ $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds));
+}
+);
+
+Breadcrumbs::register(
+ 'transactions.mass.delete', function (BreadCrumbGenerator $breadcrumbs, Collection $journals) {
+
+ $journalIds = $journals->pluck('id')->toArray();
+ $what = strtolower($journals->first()->transactionType->type);
+ $breadcrumbs->parent('transactions.index', $what);
+ $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds));
+}
+);
+
/**
* SPLIT
*/
Breadcrumbs::register(
- 'transactions.edit-split', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
+ 'transactions.split.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
$breadcrumbs->parent('transactions.show', $journal);
- $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit-split', [$journal->id]));
-}
-);
-
-Breadcrumbs::register(
- 'split.journal.create', function (BreadCrumbGenerator $breadcrumbs, string $what) {
- $breadcrumbs->parent('transactions.index', $what);
- $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('split.journal.create', [$what]));
+ $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.split.edit', [$journal->id]));
}
);
diff --git a/app/Import/Converter/BasicConverter.php b/app/Import/Converter/BasicConverter.php
index bce8b5e46b..91183ba936 100644
--- a/app/Import/Converter/BasicConverter.php
+++ b/app/Import/Converter/BasicConverter.php
@@ -37,7 +37,7 @@ class BasicConverter
/**
* @return int
*/
- public function getCertainty():int
+ public function getCertainty(): int
{
return $this->certainty;
}
diff --git a/app/Import/ImportProcedure.php b/app/Import/ImportProcedure.php
index ae6b2cf910..dd3878a01b 100644
--- a/app/Import/ImportProcedure.php
+++ b/app/Import/ImportProcedure.php
@@ -31,7 +31,7 @@ class ImportProcedure
*
* @return Collection
*/
- public static function runImport(ImportJob $job): Collection
+ public function runImport(ImportJob $job): Collection
{
// update job to say we started.
$job->status = 'import_running';
diff --git a/app/Import/ImportStorage.php b/app/Import/ImportStorage.php
index 8b4986a9fe..ce13b7b381 100644
--- a/app/Import/ImportStorage.php
+++ b/app/Import/ImportStorage.php
@@ -113,7 +113,11 @@ class ImportStorage
private function alreadyImported(string $hash): TransactionJournal
{
- $meta = TransactionJournalMeta::where('name', 'originalImportHash')->where('data', json_encode($hash))->first(['journal_meta.*']);
+ $meta = TransactionJournalMeta
+ ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
+ ->where('journal_meta.name', 'originalImportHash')
+ ->where('transaction_journals.user_id', $this->user->id)
+ ->where('journal_meta.data', json_encode($hash))->first(['journal_meta.*']);
if (!is_null($meta)) {
/** @var TransactionJournal $journal */
$journal = $meta->transactionjournal;
diff --git a/app/Import/Setup/CsvSetup.php b/app/Import/Setup/CsvSetup.php
index 782dba86dd..20c938bda3 100644
--- a/app/Import/Setup/CsvSetup.php
+++ b/app/Import/Setup/CsvSetup.php
@@ -433,7 +433,7 @@ class CsvSetup implements SetupInterface
*
* @return array
*/
- private function getDataForColumnRoles():array
+ private function getDataForColumnRoles(): array
{
Log::debug('Now in getDataForColumnRoles()');
$config = $this->job->configuration;
diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php
index b5c465e455..92fa2f3246 100644
--- a/app/Jobs/Job.php
+++ b/app/Jobs/Job.php
@@ -22,16 +22,6 @@ use Illuminate\Bus\Queueable;
*/
abstract class Job
{
- /*
- |--------------------------------------------------------------------------
- | Queueable Jobs
- |--------------------------------------------------------------------------
- |
- | This job base class provides a central location to place any logic that
- | is shared across all of your jobs. The trait included with the class
- | provides access to the "onQueue" and "delay" queue helper methods.
- |
- */
use Queueable;
}
diff --git a/app/Models/Account.php b/app/Models/Account.php
index 580ace8509..bc06e9b665 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -61,7 +61,7 @@ class Account extends Model
public static function firstOrCreateEncrypted(array $fields)
{
// everything but the name:
- $query = Account::orderBy('id');
+ $query = self::orderBy('id');
$search = $fields;
unset($search['name'], $search['iban']);
@@ -81,7 +81,7 @@ class Account extends Model
}
// create it!
- $account = Account::create($fields);
+ $account = self::create($fields);
return $account;
@@ -200,10 +200,10 @@ class Account extends Model
public function getOpeningBalanceAmount(): string
{
$journal = TransactionJournal::sortCorrectly()
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.account_id', $this->id)
- ->transactionTypes([TransactionType::OPENING_BALANCE])
- ->first(['transaction_journals.*']);
+ ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transactions.account_id', $this->id)
+ ->transactionTypes([TransactionType::OPENING_BALANCE])
+ ->first(['transaction_journals.*']);
if (is_null($journal)) {
return '0';
}
@@ -230,10 +230,10 @@ class Account extends Model
{
$date = new Carbon('1900-01-01');
$journal = TransactionJournal::sortCorrectly()
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.account_id', $this->id)
- ->transactionTypes([TransactionType::OPENING_BALANCE])
- ->first(['transaction_journals.*']);
+ ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transactions.account_id', $this->id)
+ ->transactionTypes([TransactionType::OPENING_BALANCE])
+ ->first(['transaction_journals.*']);
if (is_null($journal)) {
return $date;
}
diff --git a/app/Models/Budget.php b/app/Models/Budget.php
index c20df153b0..8d073fa561 100644
--- a/app/Models/Budget.php
+++ b/app/Models/Budget.php
@@ -43,7 +43,7 @@ class Budget extends Model
public static function firstOrCreateEncrypted(array $fields)
{
// everything but the name:
- $query = Budget::orderBy('id');
+ $query = self::orderBy('id');
$search = $fields;
unset($search['name']);
foreach ($search as $name => $value) {
@@ -57,7 +57,7 @@ class Budget extends Model
}
}
// create it!
- $budget = Budget::create($fields);
+ $budget = self::create($fields);
return $budget;
diff --git a/app/Models/Category.php b/app/Models/Category.php
index e56adb3f2a..b80e03d1b7 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -42,7 +42,7 @@ class Category extends Model
public static function firstOrCreateEncrypted(array $fields)
{
// everything but the name:
- $query = Category::orderBy('id');
+ $query = self::orderBy('id');
$search = $fields;
unset($search['name']);
foreach ($search as $name => $value) {
@@ -56,7 +56,7 @@ class Category extends Model
}
}
// create it!
- $category = Category::create($fields);
+ $category = self::create($fields);
return $category;
diff --git a/app/Models/LimitRepetition.php b/app/Models/LimitRepetition.php
index 1ce71bb237..be87b5accf 100644
--- a/app/Models/LimitRepetition.php
+++ b/app/Models/LimitRepetition.php
@@ -37,7 +37,7 @@ class LimitRepetition extends Model
public static function routeBinder($value)
{
if (auth()->check()) {
- $object = LimitRepetition::where('limit_repetitions.id', $value)
+ $object = self::where('limit_repetitions.id', $value)
->leftJoin('budget_limits', 'budget_limits.id', '=', 'limit_repetitions.budget_limit_id')
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('budgets.user_id', auth()->user()->id)
diff --git a/app/Models/Preference.php b/app/Models/Preference.php
index 768c662f2d..10f38ddd2d 100644
--- a/app/Models/Preference.php
+++ b/app/Models/Preference.php
@@ -48,7 +48,7 @@ class Preference extends Model
}
- return json_decode($data);
+ return json_decode($data, true);
}
/**
diff --git a/app/Models/Tag.php b/app/Models/Tag.php
index 8106423a33..6912a47582 100644
--- a/app/Models/Tag.php
+++ b/app/Models/Tag.php
@@ -43,7 +43,7 @@ class Tag extends TagSupport
$search = $fields;
unset($search['tag']);
- $query = Tag::orderBy('id');
+ $query = self::orderBy('id');
foreach ($search as $name => $value) {
$query->where($name, $value);
}
@@ -57,7 +57,7 @@ class Tag extends TagSupport
// create it!
$fields['tagMode'] = 'nothing';
$fields['description'] = $fields['description'] ?? '';
- $tag = Tag::create($fields);
+ $tag = self::create($fields);
return $tag;
diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php
index f54d613a29..6a93196cfb 100644
--- a/app/Models/Transaction.php
+++ b/app/Models/Transaction.php
@@ -46,7 +46,7 @@ class Transaction extends Model
*
* @return bool
*/
- public static function isJoined(Builder $query, string $table):bool
+ public static function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (is_null($joins)) {
diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php
index f9a8c206e9..56773f7b5c 100644
--- a/app/Models/TransactionJournal.php
+++ b/app/Models/TransactionJournal.php
@@ -64,10 +64,10 @@ class TransactionJournal extends TransactionJournalSupport
public static function routeBinder($value)
{
if (auth()->check()) {
- $object = TransactionJournal::where('transaction_journals.id', $value)
- ->with('transactionType')
- ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->where('user_id', auth()->user()->id)->first(['transaction_journals.*']);
+ $object = self::where('transaction_journals.id', $value)
+ ->with('transactionType')
+ ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->where('user_id', auth()->user()->id)->first(['transaction_journals.*']);
if (!is_null($object)) {
return $object;
}
@@ -113,7 +113,7 @@ class TransactionJournal extends TransactionJournalSupport
*
* @return bool
*/
- public function deleteMeta(string $name):bool
+ public function deleteMeta(string $name): bool
{
$this->transactionJournalMeta()->where('name', $name)->delete();
diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php
index ff685856e7..9d0e6af288 100644
--- a/app/Models/TransactionType.php
+++ b/app/Models/TransactionType.php
@@ -43,7 +43,7 @@ class TransactionType extends Model
if (!auth()->check()) {
throw new NotFoundHttpException;
}
- $transactionType = self::where('type', $type)->first();
+ $transactionType = self::where('type', ucfirst($type))->first();
if (!is_null($transactionType)) {
return $transactionType;
}
@@ -57,7 +57,7 @@ class TransactionType extends Model
*/
public function isDeposit()
{
- return $this->type === TransactionType::DEPOSIT;
+ return $this->type === self::DEPOSIT;
}
/**
@@ -65,7 +65,7 @@ class TransactionType extends Model
*/
public function isOpeningBalance()
{
- return $this->type === TransactionType::OPENING_BALANCE;
+ return $this->type === self::OPENING_BALANCE;
}
/**
@@ -73,7 +73,7 @@ class TransactionType extends Model
*/
public function isTransfer()
{
- return $this->type === TransactionType::TRANSFER;
+ return $this->type === self::TRANSFER;
}
/**
@@ -81,7 +81,7 @@ class TransactionType extends Model
*/
public function isWithdrawal()
{
- return $this->type === TransactionType::WITHDRAWAL;
+ return $this->type === self::WITHDRAWAL;
}
/**
diff --git a/app/Providers/CurrencyServiceProvider.php b/app/Providers/CurrencyServiceProvider.php
new file mode 100644
index 0000000000..d95532e072
--- /dev/null
+++ b/app/Providers/CurrencyServiceProvider.php
@@ -0,0 +1,60 @@
+app->bind(
+ 'FireflyIII\Repositories\Currency\CurrencyRepositoryInterface',
+ function (Application $app, array $arguments) {
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Currency\CurrencyRepository', [auth()->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
+ }
+
+ return app('FireflyIII\Repositories\Currency\CurrencyRepository', $arguments);
+ }
+ );
+ }
+
+}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index fdcf61b5dd..e9c971f2b2 100755
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -37,10 +37,38 @@ class EventServiceProvider extends ServiceProvider
protected $listen
= [
// new event handlers:
- 'FireflyIII\Events\ConfirmedUser' => // is a User related event.
+ 'FireflyIII\Events\ConfirmedUser' => // is a User related event.
[
'FireflyIII\Handlers\Events\UserEventHandler@storeConfirmationIpAddress',
],
+
+ 'FireflyIII\Events\DeletedUser' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@saveEmailAddress',
+ ],
+ 'FireflyIII\Events\LockedOutUser' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@reportLockout',
+ ],
+ 'FireflyIII\Events\BlockedUserLogin' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@reportBlockedUser',
+ ],
+
+ 'FireflyIII\Events\BlockedUseOfEmail' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@reportUseOfBlockedEmail',
+ ],
+
+ 'FireflyIII\Events\BlockedUseOfDomain' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@reportUseBlockedDomain',
+ ],
+
+ 'FireflyIII\Events\BlockedBadLogin' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@reportBadLogin',
+ ],
'FireflyIII\Events\RegisteredUser' => // is a User related event.
[
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php
index 91122f0d5f..048c45c713 100644
--- a/app/Providers/FireflyServiceProvider.php
+++ b/app/Providers/FireflyServiceProvider.php
@@ -93,12 +93,13 @@ class FireflyServiceProvider extends ServiceProvider
}
);
- $this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository');
+ // chart generator:
+ $this->app->bind('FireflyIII\Generator\Chart\Basic\GeneratorInterface', 'FireflyIII\Generator\Chart\Basic\ChartJsGenerator');
+
+ // other generators
+ $this->app->bind('FireflyIII\Export\ProcessorInterface', 'FireflyIII\Export\Processor');
$this->app->bind('FireflyIII\Repositories\User\UserRepositoryInterface', 'FireflyIII\Repositories\User\UserRepository');
$this->app->bind('FireflyIII\Helpers\Attachments\AttachmentHelperInterface', 'FireflyIII\Helpers\Attachments\AttachmentHelper');
- $this->app->bind(
- 'FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface', 'FireflyIII\Generator\Chart\Account\ChartJsAccountChartGenerator'
- );
$this->app->bind('FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface', 'FireflyIII\Generator\Chart\Bill\ChartJsBillChartGenerator');
$this->app->bind('FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface', 'FireflyIII\Generator\Chart\Budget\ChartJsBudgetChartGenerator');
$this->app->bind(
diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php
index 0a892dd5b2..5a85a93201 100644
--- a/app/Repositories/Account/AccountTasker.php
+++ b/app/Repositories/Account/AccountTasker.php
@@ -14,17 +14,13 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Account;
use Carbon\Carbon;
-use Crypt;
-use DB;
use FireflyIII\Helpers\Collection\Account as AccountCollection;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
-use FireflyIII\Models\TransactionType;
use FireflyIII\User;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
use Log;
-use stdClass;
use Steam;
/**
diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php
index 179e0aa12b..d3bfa01572 100644
--- a/app/Repositories/Bill/BillRepository.php
+++ b/app/Repositories/Bill/BillRepository.php
@@ -211,7 +211,6 @@ class BillRepository implements BillRepositoryInterface
$sum = bcadd($sum, $amount);
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
}
- Log::debug('---');
}
return $sum;
@@ -245,7 +244,6 @@ class BillRepository implements BillRepositoryInterface
$sum = bcadd($sum, $multi);
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
}
- Log::debug('---');
}
return $sum;
diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php
index ff5e4467b5..f4633d5ec3 100644
--- a/app/Repositories/Budget/BudgetRepository.php
+++ b/app/Repositories/Budget/BudgetRepository.php
@@ -199,8 +199,7 @@ class BudgetRepository implements BudgetRepositoryInterface
*/
public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection
{
- $query = LimitRepetition::
- leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id')
+ $query = LimitRepetition::leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id')
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'))
->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00'))
diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php
index 6161545760..30633beff9 100644
--- a/app/Repositories/Budget/BudgetRepositoryInterface.php
+++ b/app/Repositories/Budget/BudgetRepositoryInterface.php
@@ -112,8 +112,9 @@ interface BudgetRepositoryInterface
public function getInactiveBudgets(): Collection;
/**
- * @param Carbon $start
- * @param Carbon $end
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
*
* @return array
*/
diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php
index 1bf6e8728f..40aeea18d7 100644
--- a/app/Repositories/Currency/CurrencyRepository.php
+++ b/app/Repositories/Currency/CurrencyRepository.php
@@ -16,7 +16,9 @@ namespace FireflyIII\Repositories\Currency;
use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency;
+use FireflyIII\User;
use Illuminate\Support\Collection;
+use Preferences;
/**
* Class CurrencyRepository
@@ -25,6 +27,50 @@ use Illuminate\Support\Collection;
*/
class CurrencyRepository implements CurrencyRepositoryInterface
{
+ /** @var User */
+ private $user;
+
+ /**
+ * CategoryRepository constructor.
+ *
+ * @param User $user
+ */
+ public function __construct(User $user)
+ {
+ $this->user = $user;
+ }
+
+ /**
+ * @param TransactionCurrency $currency
+ *
+ * @return bool
+ */
+ public function canDeleteCurrency(TransactionCurrency $currency): bool
+ {
+ if ($this->countJournals($currency) > 0) {
+ return false;
+ }
+
+ // is the only currency left
+ if ($this->get()->count() === 1) {
+ return false;
+ }
+
+ // is the default currency for the user or the system
+ $defaultCode = Preferences::getForUser($this->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
+ if ($currency->code === $defaultCode) {
+ return false;
+ }
+
+ // is the default currency for the system
+ $defaultSystemCode = config('firefly.default_currency', 'EUR');
+ if ($currency->code === $defaultSystemCode) {
+ return false;
+ }
+
+ // can be deleted
+ return true;
+ }
/**
* @param TransactionCurrency $currency
@@ -36,6 +82,20 @@ class CurrencyRepository implements CurrencyRepositoryInterface
return $currency->transactionJournals()->count();
}
+ /**
+ * @param TransactionCurrency $currency
+ *
+ * @return bool
+ */
+ public function destroy(TransactionCurrency $currency): bool
+ {
+ if ($this->user->hasRole('owner')) {
+ $currency->forceDelete();
+ }
+
+ return true;
+ }
+
/**
* Find by ID
*
diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php
index 94ae094cbe..4ccebe40b0 100644
--- a/app/Repositories/Currency/CurrencyRepositoryInterface.php
+++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php
@@ -25,6 +25,13 @@ use Illuminate\Support\Collection;
*/
interface CurrencyRepositoryInterface
{
+ /**
+ * @param TransactionCurrency $currency
+ *
+ * @return bool
+ */
+ public function canDeleteCurrency(TransactionCurrency $currency): bool;
+
/**
* @param TransactionCurrency $currency
*
@@ -32,6 +39,13 @@ interface CurrencyRepositoryInterface
*/
public function countJournals(TransactionCurrency $currency): int;
+ /**
+ * @param TransactionCurrency $currency
+ *
+ * @return bool
+ */
+ public function destroy(TransactionCurrency $currency): bool;
+
/**
* Find by ID
*
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index c655b20f41..2093793c8d 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -82,10 +82,15 @@ class JournalRepository implements JournalRepositoryInterface
$destinationTransaction->save();
$journal->transaction_type_id = $type->id;
$journal->save();
- Preferences::mark();
- $messages = new MessageBag;
- return $messages;
+ // if journal is a transfer now, remove budget:
+ if ($type->type === TransactionType::TRANSFER) {
+ $journal->budgets()->detach();
+ }
+
+ Preferences::mark();
+
+ return new MessageBag;
}
/**
diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php
index 16cc5a5f3a..eb77c77e6f 100644
--- a/app/Repositories/Journal/JournalTasker.php
+++ b/app/Repositories/Journal/JournalTasker.php
@@ -113,8 +113,8 @@ class JournalTasker implements JournalTaskerInterface
/** @var Transaction $entry */
foreach ($set as $entry) {
- $sourceBalance = $this->getBalance($entry->id);
- $destinationBalance = $this->getBalance($entry->destination_id);
+ $sourceBalance = $this->getBalance(intval($entry->id));
+ $destinationBalance = $this->getBalance(intval($entry->destination_id));
$budget = $entry->budgets->first();
$category = $entry->categories->first();
$transaction = [
diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php
index ede86894c7..5a9f07704c 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepository.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepository.php
@@ -136,8 +136,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function reset(): bool
{
// split query to make it work in sqlite:
- $set = PiggyBank::
- leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.id')
+ $set = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.id')
->where('accounts.user_id', $this->user->id)->get(['piggy_banks.*']);
foreach ($set as $e) {
$e->order = 0;
diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php
index 3740584f41..dd6e341328 100644
--- a/app/Repositories/Tag/TagRepository.php
+++ b/app/Repositories/Tag/TagRepository.php
@@ -289,32 +289,54 @@ class TagRepository implements TagRepositoryInterface
*/
private function matchAll(TransactionJournal $journal, Tag $tag): bool
{
- $journalSources = join(',', TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray());
- $journalDestinations = join(',', TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray());
+ $journalSources = join(',', array_unique(TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray()));
+ $journalDestinations = join(',', array_unique(TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray()));
$match = true;
$journals = $tag->transactionJournals()->get(['transaction_journals.*']);
Log::debug(sprintf('Tag #%d has %d journals to verify:', $tag->id, $journals->count()));
- /** @var TransactionJournal $original */
- foreach ($journals as $original) {
+ /** @var TransactionJournal $existing */
+ foreach ($journals as $existing) {
+ Log::debug(sprintf('Now existingcomparing new journal #%d to existing journal #%d', $journal->id, $existing->id));
// $checkAccount is the source_account for a withdrawal
// $checkAccount is the destination_account for a deposit
- $originalSources = join(',', TransactionJournal::sourceAccountList($original)->pluck('id')->toArray());
- $originalDestinations = join(',', TransactionJournal::destinationAccountList($original)->pluck('id')->toArray());
+ $existingSources = join(',', array_unique(TransactionJournal::sourceAccountList($existing)->pluck('id')->toArray()));
+ $existingDestinations = join(',', array_unique(TransactionJournal::destinationAccountList($existing)->pluck('id')->toArray()));
- if ($original->isWithdrawal() && $originalSources !== $journalDestinations) {
- Log::debug(sprintf('Original journal #%d is a withdrawal.', $original->id));
- Log::debug(sprintf('Journal #%d must have these destination accounts: %s', $journal->id, $originalSources));
+ if ($existing->isWithdrawal() && $existingSources !== $journalDestinations) {
+ /*
+ * There can only be one withdrawal. And the source account(s) of the withdrawal
+ * must be the same as the destination of the deposit. Because any transaction that arrives
+ * here ($journal) must be a deposit.
+ */
+ Log::debug(sprintf('Existing journal #%d is a withdrawal.', $existing->id));
+ Log::debug(sprintf('New journal #%d must have these destination accounts: %s', $journal->id, $existingSources));
+ Log::debug(sprintf('New journal #%d actually these destination accounts: %s', $journal->id, $journalDestinations));
+ Log::debug('So match is FALSE');
+
+ $match = false;
+ }
+ if ($existing->isDeposit() && $journal->isDeposit() && $existingDestinations !== $journalDestinations) {
+ /*
+ * There can be multiple deposits.
+ * They must have the destination the same as the other deposits.
+ */
+ Log::debug(sprintf('Existing journal #%d is a deposit.', $existing->id));
+ Log::debug(sprintf('Journal #%d must have these destination accounts: %s', $journal->id, $existingDestinations));
Log::debug(sprintf('Journal #%d actually these destination accounts: %s', $journal->id, $journalDestinations));
Log::debug('So match is FALSE');
$match = false;
}
- if ($original->isDeposit() && $originalDestinations !== $journalSources) {
- Log::debug(sprintf('Original journal #%d is a deposit.', $original->id));
- Log::debug(sprintf('Journal #%d must have these destination accounts: %s', $journal->id, $originalSources));
- Log::debug(sprintf('Journal #%d actually these destination accounts: %s', $journal->id, $journalDestinations));
+
+ if ($existing->isDeposit() && $journal->isWithdrawal() && $existingDestinations !== $journalSources) {
+ /*
+ * There can be one new withdrawal only. It must have the same source as the existing has destination.
+ */
+ Log::debug(sprintf('Existing journal #%d is a deposit.', $existing->id));
+ Log::debug(sprintf('Journal #%d must have these source accounts: %s', $journal->id, $existingDestinations));
+ Log::debug(sprintf('Journal #%d actually these source accounts: %s', $journal->id, $journalSources));
Log::debug('So match is FALSE');
$match = false;
diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php
index 87ac639290..f33171c1e4 100644
--- a/app/Repositories/User/UserRepository.php
+++ b/app/Repositories/User/UserRepository.php
@@ -15,10 +15,12 @@ namespace FireflyIII\Repositories\User;
use FireflyConfig;
+use FireflyIII\Events\DeletedUser;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Role;
use FireflyIII\User;
use Illuminate\Support\Collection;
+use Log;
use Preferences;
/**
@@ -60,6 +62,24 @@ class UserRepository implements UserRepositoryInterface
return $this->all()->count();
}
+ /**
+ * @param User $user
+ *
+ * @return bool
+ */
+ public function destroy(User $user): bool
+ {
+ $email = $user->email;
+ Log::debug(sprintf('Calling delete() on user %d', $user->id));
+ $user->delete();
+
+
+ // trigger event:
+ event(new DeletedUser($email));
+
+ return true;
+ }
+
/**
* @param int $userId
*
diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php
index 1855fc1da1..66283a9ba0 100644
--- a/app/Repositories/User/UserRepositoryInterface.php
+++ b/app/Repositories/User/UserRepositoryInterface.php
@@ -48,6 +48,13 @@ interface UserRepositoryInterface
*/
public function count(): int;
+ /**
+ * @param User $user
+ *
+ * @return bool
+ */
+ public function destroy(User $user): bool;
+
/**
* @param int $userId
*
diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php
index 1512f0bbfa..da684a089f 100644
--- a/app/Support/Binder/AccountList.php
+++ b/app/Support/Binder/AccountList.php
@@ -47,6 +47,12 @@ class AccountList implements BinderInterface
->where('user_id', auth()->user()->id)
->get(['accounts.*']);
if ($object->count() > 0) {
+ $object = $object->sortBy(
+ function (Account $account) {
+ return $account->name;
+ }
+ );
+
return $object;
}
}
diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php
index 7ed1c1fdfb..105115f959 100644
--- a/app/Support/CacheProperties.php
+++ b/app/Support/CacheProperties.php
@@ -18,6 +18,7 @@ use Cache;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
+use Log;
use Preferences as Prefs;
/**
@@ -75,6 +76,8 @@ class CacheProperties
public function has(): bool
{
if (getenv('APP_ENV') == 'testing') {
+ Log::debug('APP_ENV is testing, cache disabled.');
+
return false;
}
$this->md5();
@@ -95,6 +98,7 @@ class CacheProperties
*/
private function md5()
{
+ $this->md5 = '';
foreach ($this->properties as $property) {
if ($property instanceof Collection || $property instanceof EloquentCollection) {
@@ -108,10 +112,17 @@ class CacheProperties
if (is_object($property)) {
$this->md5 .= $property->__toString();
}
+ if (is_bool($property) && $property === false) {
+ $this->md5 .= 'false';
+ }
+ if (is_bool($property) && $property === true) {
+ $this->md5 .= 'true';
+ }
$this->md5 .= json_encode($property);
}
-
+ Log::debug(sprintf('Cache string is %s', $this->md5));
$this->md5 = md5($this->md5);
+ Log::debug(sprintf('Cache MD5 is %s', $this->md5));
}
}
diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php
index c0aa1dbc67..0943ac9a00 100644
--- a/app/Support/ExpandedForm.php
+++ b/app/Support/ExpandedForm.php
@@ -341,6 +341,24 @@ class ExpandedForm
return $html;
}
+ /**
+ * @param $name
+ * @param null $value
+ * @param array $options
+ *
+ * @return string
+ */
+ public function password(string $name, array $options = []): string
+ {
+ $label = $this->label($name, $options);
+ $options = $this->expandOptionArray($name, $label, $options);
+ $classes = $this->getHolderClasses($name);
+ $html = view('form.password', compact('classes', 'name', 'label', 'value', 'options'))->render();
+
+ return $html;
+
+ }
+
/**
* @param $name
* @param null $value
diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php
index 32ff7958c3..9e42c6de17 100644
--- a/app/Support/FireflyConfig.php
+++ b/app/Support/FireflyConfig.php
@@ -45,7 +45,7 @@ class FireflyConfig
* @param $name
* @param null $default
*
- * @return Configuration|null
+ * @return \FireflyIII\Models\Configuration|null
*/
public function get($name, $default = null)
{
@@ -91,12 +91,12 @@ class FireflyConfig
}
/**
- * @param $name
- * @param string $value
+ * @param string $name
+ * @param $value
*
* @return Configuration
*/
- public function set($name, $value): Configuration
+ public function set(string $name, $value): Configuration
{
Log::debug('Set new value for ', ['name' => $name]);
$config = Configuration::whereName($name)->first();
diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php
index 97c46c98d0..dab024765c 100644
--- a/app/Support/Migration/TestData.php
+++ b/app/Support/Migration/TestData.php
@@ -115,7 +115,7 @@ class TestData
'title' => Crypt::encrypt($attachment['title']),
'description' => Crypt::encrypt($attachment['description']),
'notes' => Crypt::encrypt($attachment['notes']),
- 'mime' => $attachment['mime'],
+ 'mime' => Crypt::encrypt($attachment['mime']),
'size' => strlen($attachment['content']),
'uploaded' => 1,
]
@@ -267,11 +267,37 @@ class TestData
/**
*
*/
- private function createImportJobs()
+ private function createExportJobs()
{
$insert = [];
- foreach ($this->data['import-jobs'] as $job) {
+ $disk = Storage::disk('export');
+ foreach ($this->data['export-jobs'] as $job) {
$insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $job['user_id'],
+ 'key' => $job['key'],
+ 'status' => $job['status'],
+ ];
+ $disk->put($job['key'] . '.zip', 'Nonsense data for "ziP" file.');
+ }
+ DB::table('export_jobs')->insert($insert);
+
+ // store fake export file:
+
+ }
+
+ /**
+ *
+ */
+ private function createImportJobs()
+ {
+
+ $disk = Storage::disk('upload');
+ $insert = [];
+ foreach ($this->data['import-jobs'] as $job) {
+ $insert[]
+ = [
'created_at' => $this->time,
'updated_at' => $this->time,
'user_id' => $job['user_id'],
@@ -281,6 +307,8 @@ class TestData
'extended_status' => json_encode($job['extended_status']),
'configuration' => json_encode($job['configuration']),
];
+
+ $disk->put($job['key'] . '.upload', Crypt::encrypt(''));
}
DB::table('import_jobs')->insert($insert);
}
@@ -863,6 +891,7 @@ class TestData
$this->createMultiTransfers();
$this->createImportJobs();
$this->createCurrencies();
+ $this->createExportJobs();
}
}
diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php
index 49901f7119..513fec2623 100644
--- a/app/Support/Models/TransactionJournalSupport.php
+++ b/app/Support/Models/TransactionJournalSupport.php
@@ -200,7 +200,7 @@ class TransactionJournalSupport extends Model
*
* @return bool
*/
- public static function isJoined(Builder $query, string $table):bool
+ public static function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (is_null($joins)) {
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index 48ff2724fb..6a6e360e78 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -43,6 +43,7 @@ class Navigation
'1M' => 'addMonths', 'month' => 'addMonths', 'monthly' => 'addMonths', '3M' => 'addMonths',
'quarter' => 'addMonths', 'quarterly' => 'addMonths', '6M' => 'addMonths', 'half-year' => 'addMonths',
'year' => 'addYears', 'yearly' => 'addYears', '1Y' => 'addYears',
+ 'custom' => 'addMonths', // custom? just add one month.
];
$modifierMap = [
'quarter' => 3,
@@ -242,6 +243,28 @@ class Navigation
throw new FireflyException(sprintf('No date formats for frequency "%s"!', $repeatFrequency));
}
+ /**
+ * If the date difference between start and end is less than a month, method returns "endOfDay". If the difference is less than a year,
+ * method returns "endOfMonth". If the date difference is larger, method returns "endOfYear".
+ *
+ * @param \Carbon\Carbon $start
+ * @param \Carbon\Carbon $end
+ *
+ * @return string
+ */
+ public function preferredEndOfPeriod(Carbon $start, Carbon $end): string
+ {
+ $format = 'endOfDay';
+ if ($start->diffInMonths($end) > 1) {
+ $format = 'endOfMonth';
+ }
+
+ if ($start->diffInMonths($end) > 12) {
+ $format = 'endOfYear';
+ }
+ return $format;
+ }
+
/**
* If the date difference between start and end is less than a month, method returns "Y-m-d". If the difference is less than a year,
* method returns "Y-m". If the date difference is larger, method returns "Y".
@@ -261,6 +284,28 @@ class Navigation
if ($start->diffInMonths($end) > 12) {
$format = 'Y';
}
+ return $format;
+ }
+
+ /**
+ * If the date difference between start and end is less than a month, method returns trans(config.month_and_day). If the difference is less than a year,
+ * method returns "config.month". If the date difference is larger, method returns "config.year".
+ *
+ * @param \Carbon\Carbon $start
+ * @param \Carbon\Carbon $end
+ *
+ * @return string
+ */
+ public function preferredCarbonLocalizedFormat(Carbon $start, Carbon $end): string
+ {
+ $format = strval(trans('config.month_and_day'));
+ if ($start->diffInMonths($end) > 1) {
+ $format = strval(trans('config.month'));
+ }
+
+ if ($start->diffInMonths($end) > 12) {
+ $format = strval(trans('config.year'));
+ }
return $format;
diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php
index 30f258a569..8507f3d806 100644
--- a/app/Support/Preferences.php
+++ b/app/Support/Preferences.php
@@ -59,8 +59,8 @@ class Preferences
}
/**
- * @param \FireflyIII\User $user
- * @param array $list
+ * @param \FireflyIII\User $user
+ * @param array $list
*
* @return array
*/
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index d94b7af230..65e4b148a6 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -159,8 +159,7 @@ class Steam
return $cache->get();
}
- $balances = Transaction::
- leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ $balances = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
->groupBy('transactions.account_id')
->whereIn('transactions.account_id', $ids)
diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php
index a13a62faab..04336c3163 100644
--- a/app/Support/Twig/General.php
+++ b/app/Support/Twig/General.php
@@ -84,7 +84,7 @@ class General extends Twig_Extension
protected function activeRoutePartial(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
- 'activeRoutePartial', function () : string {
+ 'activeRoutePartial', function (): string {
$args = func_get_args();
$route = $args[0]; // name of the route.
$name = Route::getCurrentRoute()->getName() ?? '';
@@ -106,7 +106,7 @@ class General extends Twig_Extension
protected function activeRoutePartialWhat(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
- 'activeRoutePartialWhat', function ($context) : string {
+ 'activeRoutePartialWhat', function ($context): string {
$args = func_get_args();
$route = $args[1]; // name of the route.
$what = $args[2]; // name of the route.
@@ -130,7 +130,7 @@ class General extends Twig_Extension
protected function activeRouteStrict(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
- 'activeRouteStrict', function () : string {
+ 'activeRouteStrict', function (): string {
$args = func_get_args();
$route = $args[0]; // name of the route.
@@ -149,7 +149,7 @@ class General extends Twig_Extension
protected function balance(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
- 'balance', function (Account $account = null) : string {
+ 'balance', function (Account $account = null): string {
if (is_null($account)) {
return 'NULL';
}
@@ -166,7 +166,7 @@ class General extends Twig_Extension
protected function env(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
- 'env', function (string $name, string $default) : string {
+ 'env', function (string $name, string $default): string {
return env($name, $default);
}
);
@@ -179,7 +179,7 @@ class General extends Twig_Extension
protected function formatAmount(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
- 'formatAmount', function (string $string) : string {
+ 'formatAmount', function (string $string): string {
return app('amount')->format($string);
}, ['is_safe' => ['html']]
@@ -192,7 +192,7 @@ class General extends Twig_Extension
protected function formatAmountPlain(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
- 'formatAmountPlain', function (string $string) : string {
+ 'formatAmountPlain', function (string $string): string {
return app('amount')->format($string, false);
}, ['is_safe' => ['html']]
@@ -205,7 +205,7 @@ class General extends Twig_Extension
protected function formatFilesize(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
- 'filesize', function (int $size) : string {
+ 'filesize', function (int $size): string {
// less than one GB, more than one MB
if ($size < (1024 * 1024 * 2014) && $size >= (1024 * 1024)) {
@@ -228,7 +228,7 @@ class General extends Twig_Extension
protected function formatJournal(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
- 'formatJournal', function (TransactionJournal $journal) : string {
+ 'formatJournal', function (TransactionJournal $journal): string {
return app('amount')->formatJournal($journal);
}, ['is_safe' => ['html']]
);
@@ -240,7 +240,7 @@ class General extends Twig_Extension
protected function getAccountRole(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
- 'getAccountRole', function (string $name) : string {
+ 'getAccountRole', function (string $name): string {
return Config::get('firefly.accountRoles.' . $name);
}
);
@@ -252,7 +252,7 @@ class General extends Twig_Extension
protected function getCurrencyCode(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
- 'getCurrencyCode', function () : string {
+ 'getCurrencyCode', function (): string {
return app('amount')->getCurrencyCode();
}
);
@@ -264,7 +264,7 @@ class General extends Twig_Extension
protected function getCurrencySymbol(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
- 'getCurrencySymbol', function () : string {
+ 'getCurrencySymbol', function (): string {
return app('amount')->getCurrencySymbol();
}
);
@@ -276,7 +276,7 @@ class General extends Twig_Extension
protected function mimeIcon(): Twig_SimpleFilter
{
return new Twig_SimpleFilter(
- 'mimeIcon', function (string $string) : string {
+ 'mimeIcon', function (string $string): string {
switch ($string) {
default:
return 'fa-file-o';
@@ -296,7 +296,7 @@ class General extends Twig_Extension
protected function phpdate()
{
return new Twig_SimpleFunction(
- 'phpdate', function (string $str) : string {
+ 'phpdate', function (string $str): string {
return date($str);
}
);
@@ -308,7 +308,7 @@ class General extends Twig_Extension
private function getAmountFromJournal()
{
return new Twig_SimpleFunction(
- 'getAmount', function (TransactionJournal $journal) : string {
+ 'getAmount', function (TransactionJournal $journal): string {
return TransactionJournal::amount($journal);
}
);
diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php
index 8e1d1284f8..0e9d851c40 100644
--- a/app/Support/Twig/Transaction.php
+++ b/app/Support/Twig/Transaction.php
@@ -110,9 +110,9 @@ class Transaction extends Twig_Extension
$amount = strval(
TransactionModel::where('transaction_journal_id', $journalId)
- ->whereNull('deleted_at')
- ->where('amount', '<', 0)
- ->sum('amount')
+ ->whereNull('deleted_at')
+ ->where('amount', '<', 0)
+ ->sum('amount')
);
if ($type === TransactionType::DEPOSIT || $type === TransactionType::TRANSFER) {
@@ -203,10 +203,10 @@ class Transaction extends Twig_Extension
$journalId = $transaction->journal_id;
/** @var TransactionModel $other */
$other = TransactionModel::where('transaction_journal_id', $journalId)->where('transactions.id', '!=', $transaction->id)
- ->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where('identifier', $transaction->identifier)
- ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
- ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
- ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']);
+ ->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where('identifier', $transaction->identifier)
+ ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
+ ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
+ ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']);
$name = intval($other->encrypted) === 1 ? Crypt::decrypt($other->name) : $other->name;
$id = $other->account_id;
$type = $other->type;
@@ -266,8 +266,7 @@ class Transaction extends Twig_Extension
// name is present in object, use that one:
if (bccomp($transaction->transaction_amount, '0') === 1 && !is_null($transaction->opposing_account_id)) {
- $name = intval($transaction->opposing_account_encrypted) === 1 ? Crypt::decrypt($transaction->opposing_account_name)
- : $transaction->opposing_account_name;
+ $name = $transaction->opposing_account_name;
$id = intval($transaction->opposing_account_id);
$type = intval($transaction->opposing_account_type);
}
@@ -276,10 +275,10 @@ class Transaction extends Twig_Extension
$journalId = $transaction->journal_id;
/** @var TransactionModel $other */
$other = TransactionModel::where('transaction_journal_id', $journalId)->where('transactions.id', '!=', $transaction->id)
- ->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where('identifier', $transaction->identifier)
- ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
- ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
- ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']);
+ ->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where('identifier', $transaction->identifier)
+ ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
+ ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
+ ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']);
$name = intval($other->encrypted) === 1 ? Crypt::decrypt($other->name) : $other->name;
$id = $other->account_id;
$type = $other->type;
diff --git a/app/User.php b/app/User.php
index 35d9914236..ba57a8ce47 100755
--- a/app/User.php
+++ b/app/User.php
@@ -188,6 +188,20 @@ class User extends Authenticatable
return $this->hasMany('FireflyIII\Models\Rule');
}
+ /**
+ * Send the password reset notification.
+ *
+ * @param string $token
+ *
+ * @return void
+ */
+ public function sendPasswordResetNotification($token)
+ {
+ $ip = Request::ip();
+
+ event(new RequestedNewPassword($this, $token, $ip));
+ }
+
/**
* @return HasMany
*/
@@ -212,18 +226,5 @@ class User extends Authenticatable
return $this->hasManyThrough('FireflyIII\Models\Transaction', 'FireflyIII\Models\TransactionJournal');
}
- /**
- * Send the password reset notification.
- *
- * @param string $token
- * @return void
- */
- public function sendPasswordResetNotification($token)
- {
- $ip = Request::ip();
-
- event(new RequestedNewPassword($this, $token, $ip));
- }
-
}
diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php
index 35b973c265..9555f3e00c 100644
--- a/app/Validation/FireflyValidator.php
+++ b/app/Validation/FireflyValidator.php
@@ -296,8 +296,7 @@ class FireflyValidator extends Validator
{
$accountId = $this->data['id'] ?? 0;
- $query = AccountMeta::
- leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
+ $query = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
->where('accounts.user_id', auth()->user()->id)
->where('account_meta.name', 'accountNumber');
diff --git a/bootstrap/app.php b/bootstrap/app.php
index dd875c174b..06a88210b2 100755
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -23,7 +23,7 @@ declare(strict_types = 1);
|
*/
-bcscale(4);
+bcscale(6);
$app = new Illuminate\Foundation\Application(
diff --git a/composer.lock b/composer.lock
index aeb5bf11c3..b165ea68f1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1942,16 +1942,16 @@
},
{
"name": "symfony/console",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "5be36e1f3ac7ecbe7e34fb641480ad8497b83aa6"
+ "reference": "221a60fb2f369a065eea1ed96b61183219fdfa6e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/5be36e1f3ac7ecbe7e34fb641480ad8497b83aa6",
- "reference": "5be36e1f3ac7ecbe7e34fb641480ad8497b83aa6",
+ "url": "https://api.github.com/repos/symfony/console/zipball/221a60fb2f369a065eea1ed96b61183219fdfa6e",
+ "reference": "221a60fb2f369a065eea1ed96b61183219fdfa6e",
"shasum": ""
},
"require": {
@@ -1999,11 +1999,11 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2016-11-16 22:17:09"
+ "time": "2016-12-08 14:58:14"
},
{
"name": "symfony/debug",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
@@ -2060,7 +2060,7 @@
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.2.0",
+ "version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
@@ -2120,16 +2120,16 @@
},
{
"name": "symfony/finder",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "9925935bf7144f9e4d2b976905881b4face036fb"
+ "reference": "74dcd370c8d057882575e535616fde935e411b19"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/9925935bf7144f9e4d2b976905881b4face036fb",
- "reference": "9925935bf7144f9e4d2b976905881b4face036fb",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/74dcd370c8d057882575e535616fde935e411b19",
+ "reference": "74dcd370c8d057882575e535616fde935e411b19",
"shasum": ""
},
"require": {
@@ -2165,20 +2165,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2016-11-03 08:04:31"
+ "time": "2016-12-13 09:38:21"
},
{
"name": "symfony/http-foundation",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "5a4c8099a1547fe451256e056180ad4624177017"
+ "reference": "88a1d3cee2dbd06f7103ff63938743b903b65a92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5a4c8099a1547fe451256e056180ad4624177017",
- "reference": "5a4c8099a1547fe451256e056180ad4624177017",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/88a1d3cee2dbd06f7103ff63938743b903b65a92",
+ "reference": "88a1d3cee2dbd06f7103ff63938743b903b65a92",
"shasum": ""
},
"require": {
@@ -2218,20 +2218,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2016-11-16 22:17:09"
+ "time": "2016-11-27 04:21:07"
},
{
"name": "symfony/http-kernel",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "674ac403c7b3742c2a988a86e3baf9aca2c696a0"
+ "reference": "d7a4671a6f8e4174127770263dcd95bee5713f76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/674ac403c7b3742c2a988a86e3baf9aca2c696a0",
- "reference": "674ac403c7b3742c2a988a86e3baf9aca2c696a0",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d7a4671a6f8e4174127770263dcd95bee5713f76",
+ "reference": "d7a4671a6f8e4174127770263dcd95bee5713f76",
"shasum": ""
},
"require": {
@@ -2300,7 +2300,7 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2016-11-21 02:44:20"
+ "time": "2016-12-13 12:52:10"
},
{
"name": "symfony/polyfill-mbstring",
@@ -2471,16 +2471,16 @@
},
{
"name": "symfony/process",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "66de154ae86b1a07001da9fbffd620206e4faf94"
+ "reference": "d23427a7f97a373129f61bc3b876fe4c66e2b3c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/66de154ae86b1a07001da9fbffd620206e4faf94",
- "reference": "66de154ae86b1a07001da9fbffd620206e4faf94",
+ "url": "https://api.github.com/repos/symfony/process/zipball/d23427a7f97a373129f61bc3b876fe4c66e2b3c7",
+ "reference": "d23427a7f97a373129f61bc3b876fe4c66e2b3c7",
"shasum": ""
},
"require": {
@@ -2516,20 +2516,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2016-09-29 14:13:09"
+ "time": "2016-11-24 01:08:05"
},
{
"name": "symfony/routing",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "8edf62498a1a4c57ba317664a4b698339c10cdf6"
+ "reference": "4beb3dceb14cf2dd63dd222d1825ca981a2952cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/8edf62498a1a4c57ba317664a4b698339c10cdf6",
- "reference": "8edf62498a1a4c57ba317664a4b698339c10cdf6",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/4beb3dceb14cf2dd63dd222d1825ca981a2952cb",
+ "reference": "4beb3dceb14cf2dd63dd222d1825ca981a2952cb",
"shasum": ""
},
"require": {
@@ -2591,11 +2591,11 @@
"uri",
"url"
],
- "time": "2016-08-16 14:58:24"
+ "time": "2016-11-25 12:27:14"
},
{
"name": "symfony/translation",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
@@ -2659,16 +2659,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "0c2d613e890e33f4da810159ac97931068f5bd17"
+ "reference": "5ccbd23a97035886e595ce497dbe94bc88ac0b57"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0c2d613e890e33f4da810159ac97931068f5bd17",
- "reference": "0c2d613e890e33f4da810159ac97931068f5bd17",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5ccbd23a97035886e595ce497dbe94bc88ac0b57",
+ "reference": "5ccbd23a97035886e595ce497dbe94bc88ac0b57",
"shasum": ""
},
"require": {
@@ -2718,20 +2718,20 @@
"debug",
"dump"
],
- "time": "2016-11-03 08:04:31"
+ "time": "2016-12-08 14:58:14"
},
{
"name": "twig/twig",
- "version": "v1.28.2",
+ "version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "b22ce0eb070e41f7cba65d78fe216de29726459c"
+ "reference": "74f723e542368ca2080b252740be5f1113ebb898"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/b22ce0eb070e41f7cba65d78fe216de29726459c",
- "reference": "b22ce0eb070e41f7cba65d78fe216de29726459c",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/74f723e542368ca2080b252740be5f1113ebb898",
+ "reference": "74f723e542368ca2080b252740be5f1113ebb898",
"shasum": ""
},
"require": {
@@ -2744,7 +2744,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.28-dev"
+ "dev-master": "1.29-dev"
}
},
"autoload": {
@@ -2779,7 +2779,7 @@
"keywords": [
"templating"
],
- "time": "2016-11-23 18:41:40"
+ "time": "2016-12-13 17:28:18"
},
{
"name": "vlucas/phpdotenv",
@@ -2939,16 +2939,16 @@
},
{
"name": "barryvdh/laravel-ide-helper",
- "version": "v2.2.1",
+ "version": "v2.2.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
- "reference": "28af7cd19ca41cc0c63dd1de2b46c2b84d31c463"
+ "reference": "105f14a50d0959a0e80004a15b3350fdf78f9623"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/28af7cd19ca41cc0c63dd1de2b46c2b84d31c463",
- "reference": "28af7cd19ca41cc0c63dd1de2b46c2b84d31c463",
+ "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/105f14a50d0959a0e80004a15b3350fdf78f9623",
+ "reference": "105f14a50d0959a0e80004a15b3350fdf78f9623",
"shasum": ""
},
"require": {
@@ -3001,7 +3001,7 @@
"phpstorm",
"sublime"
],
- "time": "2016-07-04 11:52:48"
+ "time": "2016-11-15 08:21:23"
},
{
"name": "barryvdh/reflection-docblock",
@@ -3822,16 +3822,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "5.7.2",
+ "version": "5.7.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "336aff0ac52e306c98e7455bc3e8d7b0bf777a5e"
+ "reference": "af91da3f2671006ff5d0628023de3b7ac4d1ef09"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/336aff0ac52e306c98e7455bc3e8d7b0bf777a5e",
- "reference": "336aff0ac52e306c98e7455bc3e8d7b0bf777a5e",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/af91da3f2671006ff5d0628023de3b7ac4d1ef09",
+ "reference": "af91da3f2671006ff5d0628023de3b7ac4d1ef09",
"shasum": ""
},
"require": {
@@ -3842,7 +3842,7 @@
"ext-xml": "*",
"myclabs/deep-copy": "~1.3",
"php": "^5.6 || ^7.0",
- "phpspec/prophecy": "^1.3.1",
+ "phpspec/prophecy": "^1.6.2",
"phpunit/php-code-coverage": "^4.0.3",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
@@ -3852,7 +3852,7 @@
"sebastian/diff": "~1.2",
"sebastian/environment": "^1.3.4 || ^2.0",
"sebastian/exporter": "~2.0",
- "sebastian/global-state": "~1.0",
+ "sebastian/global-state": "^1.0 || ^2.0",
"sebastian/object-enumerator": "~2.0",
"sebastian/resource-operations": "~1.0",
"sebastian/version": "~1.0|~2.0",
@@ -3900,20 +3900,20 @@
"testing",
"xunit"
],
- "time": "2016-12-03 08:33:00"
+ "time": "2016-12-13 16:19:44"
},
{
"name": "phpunit/phpunit-mock-objects",
- "version": "3.4.2",
+ "version": "3.4.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "90a08f5deed5f7ac35463c161f2e8fa0e5652faf"
+ "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/90a08f5deed5f7ac35463c161f2e8fa0e5652faf",
- "reference": "90a08f5deed5f7ac35463c161f2e8fa0e5652faf",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
+ "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
"shasum": ""
},
"require": {
@@ -3959,7 +3959,7 @@
"mock",
"xunit"
],
- "time": "2016-11-27 07:52:03"
+ "time": "2016-12-08 20:27:08"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -4476,7 +4476,7 @@
},
{
"name": "symfony/class-loader",
- "version": "v3.2.0",
+ "version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
@@ -4532,7 +4532,7 @@
},
{
"name": "symfony/css-selector",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -4585,16 +4585,16 @@
},
{
"name": "symfony/dom-crawler",
- "version": "v3.1.7",
+ "version": "v3.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "1eb3b4d216e8db117218dd2bb7d23dfe67bdf518"
+ "reference": "51e979357eba65b1e6aac7cba75cf5aa6379b8f3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/1eb3b4d216e8db117218dd2bb7d23dfe67bdf518",
- "reference": "1eb3b4d216e8db117218dd2bb7d23dfe67bdf518",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/51e979357eba65b1e6aac7cba75cf5aa6379b8f3",
+ "reference": "51e979357eba65b1e6aac7cba75cf5aa6379b8f3",
"shasum": ""
},
"require": {
@@ -4637,20 +4637,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2016-11-14 16:20:02"
+ "time": "2016-12-10 14:24:45"
},
{
"name": "symfony/yaml",
- "version": "v3.2.0",
+ "version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "f2300ba8fbb002c028710b92e1906e7457410693"
+ "reference": "a7095af4b97a0955f85c8989106c249fa649011f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/f2300ba8fbb002c028710b92e1906e7457410693",
- "reference": "f2300ba8fbb002c028710b92e1906e7457410693",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/a7095af4b97a0955f85c8989106c249fa649011f",
+ "reference": "a7095af4b97a0955f85c8989106c249fa649011f",
"shasum": ""
},
"require": {
@@ -4692,7 +4692,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2016-11-18 21:17:59"
+ "time": "2016-12-10 10:07:06"
},
{
"name": "webmozart/assert",
diff --git a/config/app.php b/config/app.php
index 68c0f500a9..4933ad1c8c 100755
--- a/config/app.php
+++ b/config/app.php
@@ -80,6 +80,7 @@ return [
FireflyIII\Providers\BillServiceProvider::class,
FireflyIII\Providers\BudgetServiceProvider::class,
FireflyIII\Providers\CategoryServiceProvider::class,
+ FireflyIII\Providers\CurrencyServiceProvider::class,
FireflyIII\Providers\ExportJobServiceProvider::class,
FireflyIII\Providers\JournalServiceProvider::class,
FireflyIII\Providers\PiggyBankServiceProvider::class,
diff --git a/config/filesystems.php b/config/filesystems.php
index bd0b9738ac..cac819789e 100755
--- a/config/filesystems.php
+++ b/config/filesystems.php
@@ -52,7 +52,7 @@ return [
'disks' => [
- 'local' => [
+ 'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
@@ -69,8 +69,10 @@ return [
'driver' => 'local',
'root' => storage_path('database'),
],
-
-
+ 'seeds' => [
+ 'driver' => 'local',
+ 'root' => base_path('resources/seeds'),
+ ],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
diff --git a/config/firefly.php b/config/firefly.php
index f9e2710a74..d48a4299cf 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -19,12 +19,17 @@ declare(strict_types = 1);
return [
'configuration' => [
- 'single_user_mode' => true,
- 'is_demo_site' => false,
- 'must_confirm_account' => false,
+ 'single_user_mode' => true,
+ 'is_demo_site' => false,
+ 'must_confirm_account' => false,
+ 'mail_for_lockout' => false,
+ 'mail_for_blocked_domain' => false,
+ 'mail_for_blocked_email' => false,
+ 'mail_for_bad_login' => false,
+ 'mail_for_blocked_login' => false,
],
'chart' => 'chartjs',
- 'version' => '4.2.1',
+ 'version' => '4.2.2',
'csv_import_enabled' => true,
'maxUploadSize' => 5242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
diff --git a/config/twigbridge.php b/config/twigbridge.php
index 886f44d6e1..dc51e30651 100644
--- a/config/twigbridge.php
+++ b/config/twigbridge.php
@@ -159,7 +159,7 @@ return [
'ExpandedForm' => [
'is_safe' => [
'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location',
- 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall',
+ 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password',
],
],
'Form' => [
diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php
index 334b4725dd..a5e814da50 100644
--- a/database/seeds/TestDataSeeder.php
+++ b/database/seeds/TestDataSeeder.php
@@ -33,7 +33,7 @@ class TestDataSeeder extends Seeder
*/
public function run()
{
- $disk = Storage::disk('database');
+ $disk = Storage::disk('seeds');
$env = App::environment();
Log::debug('Environment is ' . $env);
$fileName = 'seed.' . $env . '.json';
diff --git a/phpunit.xml b/phpunit.xml
index 712e0af587..478d78f2b6 100755
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -7,7 +7,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
- stopOnFailure="false">
+ stopOnFailure="true">
./tests
diff --git a/public/js/ff/accounts/show-by-date.js b/public/js/ff/accounts/show-by-date.js
deleted file mode 100644
index 4e1768daf4..0000000000
--- a/public/js/ff/accounts/show-by-date.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * show_with_date.js
- * Copyright (C) 2016 thegrumpydictator@gmail.com
- *
- * This software may be modified and distributed under the terms
- * of the MIT license. See the LICENSE file for details.
- */
-
-// Return a helper with preserved width of cells
-var fixHelper = function (e, tr) {
- "use strict";
- var $originals = tr.children();
- var $helper = tr.clone();
- $helper.children().each(function (index) {
- // Set helper cell sizes to match the original sizes
- $(this).width($originals.eq(index).width());
- });
- return $helper;
-};
-
-$(function () {
- "use strict";
-
- lineChart(periodUri, 'period-specific-account');
-
- pieChart(incomeCategoryUri, 'account-cat-in');
- pieChart(expenseCategoryUri, 'account-cat-out');
- pieChart(expenseBudgetUri, 'account-budget-out');
-
-
- // sortable!
- if (typeof $(".sortable-table tbody").sortable !== "undefined") {
- $(".sortable-table tbody").sortable(
- {
- helper: fixHelper,
- items: 'tr:not(.ignore)',
- stop: sortStop,
- handle: '.handle',
- start: function (event, ui) {
- // Build a placeholder cell that spans all the cells in the row
- var cellCount = 0;
- $('td, th', ui.helper).each(function () {
- // For each TD or TH try and get it's colspan attribute, and add that or 1 to the total
- var colspan = 1;
- var colspanAttr = $(this).attr('colspan');
- if (colspanAttr > 1) {
- colspan = colspanAttr;
- }
- cellCount += colspan;
- });
-
- // Add the placeholder UI - note that this is the item's content, so TD rather than TR
- ui.placeholder.html('| | ');
- }
- }
- ).disableSelection();
- } else {
- console.log('its null');
- }
-
-});
-
-function sortStop(event, ui) {
- "use strict";
- var current = $(ui.item);
- console.log('sort stop');
- var thisDate = current.data('date');
- var originalBG = current.css('backgroundColor');
-
-
- if (current.prev().data('date') !== thisDate && current.next().data('date') !== thisDate) {
- // animate something with color:
- current.animate({backgroundColor: "#d9534f"}, 200, function () {
- $(this).animate({backgroundColor: originalBG}, 200);
- });
-
- return false;
- }
-
- // do update
- var list = $('tr[data-date="' + thisDate + '"]');
- var submit = [];
- $.each(list, function (i, v) {
- var row = $(v);
- var id = row.data('id');
- submit.push(id);
- });
-
- // do extra animation when done?
- $.post('transaction/reorder', {items: submit, date: thisDate, _token: token});
-
- current.animate({backgroundColor: "#5cb85c"}, 200, function () {
- $(this).animate({backgroundColor: originalBG}, 200);
- });
-}
diff --git a/public/js/ff/accounts/show.js b/public/js/ff/accounts/show.js
index 5875c0da90..e981faeb55 100644
--- a/public/js/ff/accounts/show.js
+++ b/public/js/ff/accounts/show.js
@@ -1,7 +1,13 @@
-/* global $, lineChart, accountID, token, incomeByCategoryUri, expenseByCategoryUri, expenseByBudgetUri */
+/*
+ * show.js
+ * Copyright (C) 2016 thegrumpydictator@gmail.com
+ *
+ * This software may be modified and distributed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International License.
+ *
+ * See the LICENSE file for details.
+ */
-
-// Return a helper with preserved width of cells
var fixHelper = function (e, tr) {
"use strict";
var $originals = tr.children();
@@ -15,7 +21,7 @@ var fixHelper = function (e, tr) {
$(function () {
"use strict";
- lineChart(singleUri, 'overview-chart');
+ lineChart(chartUri, 'overview-chart');
pieChart(incomeCategoryUri, 'account-cat-in');
pieChart(expenseCategoryUri, 'account-cat-out');
pieChart(expenseBudgetUri, 'account-budget-out');
diff --git a/public/js/ff/reports/budget/all.js b/public/js/ff/reports/budget/all.js
new file mode 100644
index 0000000000..25a412d1c5
--- /dev/null
+++ b/public/js/ff/reports/budget/all.js
@@ -0,0 +1,10 @@
+/*
+ * all.js
+ * Copyright (C) 2016 thegrumpydictator@gmail.com
+ *
+ * This software may be modified and distributed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International License.
+ *
+ * See the LICENSE file for details.
+ */
+
diff --git a/public/js/ff/reports/budget/month.js b/public/js/ff/reports/budget/month.js
new file mode 100644
index 0000000000..0eeb33fa93
--- /dev/null
+++ b/public/js/ff/reports/budget/month.js
@@ -0,0 +1,54 @@
+/*
+ * month.js
+ * Copyright (C) 2016 thegrumpydictator@gmail.com
+ *
+ * This software may be modified and distributed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International License.
+ *
+ * See the LICENSE file for details.
+ */
+
+
+$(function () {
+ "use strict";
+ drawChart();
+
+ $('#budgets-out-pie-chart-checked').on('change', function () {
+ redrawPieChart('budgets-out-pie-chart', budgetExpenseUri);
+ });
+
+ $('#accounts-out-pie-chart-checked').on('change', function () {
+ redrawPieChart('accounts-out-pie-chart', accountExpenseUri);
+ });
+
+});
+
+
+function drawChart() {
+ "use strict";
+
+ // month view:
+ stackedColumnChart(mainUri, 'in-out-chart');
+
+ // draw pie chart of income, depending on "show other transactions too":
+ redrawPieChart('budgets-out-pie-chart', budgetExpenseUri);
+ redrawPieChart('accounts-out-pie-chart', accountExpenseUri);
+
+
+}
+
+function redrawPieChart(container, uri) {
+ "use strict";
+ var checkbox = $('#' + container + '-checked');
+
+ var others = '0';
+ // check if box is checked:
+ if (checkbox.prop('checked')) {
+ others = '1';
+ }
+ uri = uri.replace('OTHERS', others);
+ console.log('URI for ' + container + ' is ' + uri);
+
+ pieChart(uri, container);
+
+}
diff --git a/public/js/ff/reports/index.js b/public/js/ff/reports/index.js
index bd949eeac6..9de3f79b50 100644
--- a/public/js/ff/reports/index.js
+++ b/public/js/ff/reports/index.js
@@ -65,12 +65,22 @@ function getReportOptions() {
}
function setOptionalFromCookies() {
+ var arr;
+ // categories
if ((readCookie('report-categories') !== null)) {
- var arr = readCookie('report-categories').split(',');
+ arr = readCookie('report-categories').split(',');
arr.forEach(function (val) {
$('input[class="category-checkbox"][type="checkbox"][value="' + val + '"]').prop('checked', true);
});
}
+
+ // and budgets!
+ if ((readCookie('report-budgets') !== null)) {
+ arr = readCookie('report-budgets').split(',');
+ arr.forEach(function (val) {
+ $('input[class="budget-checkbox"][type="checkbox"][value="' + val + '"]').prop('checked', true);
+ });
+ }
}
function catchSubmit() {
@@ -90,7 +100,6 @@ function catchSubmit() {
});
// all category ids:
- //category-checkbox
var categories = [];
$.each($('.category-checkbox'), function (i, v) {
var c = $(v);
@@ -99,6 +108,15 @@ function catchSubmit() {
}
});
+ // all budget ids:
+ var budgets = [];
+ $.each($('.budget-checkbox'), function (i, v) {
+ var c = $(v);
+ if (c.prop('checked')) {
+ budgets.push(c.val());
+ }
+ });
+
// remember all
if (count > 0) {
@@ -106,6 +124,7 @@ function catchSubmit() {
createCookie('report-type', $('select[name="report_type"]').val(), 365);
createCookie('report-accounts', accounts, 365);
createCookie('report-categories', categories, 365);
+ createCookie('report-budgets', budgets, 365);
createCookie('report-start', moment(picker.startDate).format("YYYYMMDD"), 365);
createCookie('report-end', moment(picker.endDate).format("YYYYMMDD"), 365);
}
diff --git a/public/js/ff/transactions/list.js b/public/js/ff/transactions/list.js
index 22b4089ddf..42786654a3 100644
--- a/public/js/ff/transactions/list.js
+++ b/public/js/ff/transactions/list.js
@@ -31,7 +31,7 @@ function goToMassEdit() {
var checkedArray = getCheckboxes();
// go to specially crafted URL:
- window.location.href = 'transactions/mass-edit/' + checkedArray;
+ window.location.href = 'transactions/mass/edit/' + checkedArray;
return false;
}
@@ -40,7 +40,7 @@ function goToMassDelete() {
var checkedArray = getCheckboxes();
// go to specially crafted URL:
- window.location.href = 'transactions/mass-delete/' + checkedArray;
+ window.location.href = 'transactions/mass/delete/' + checkedArray;
return false;
}
diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php
index 996258c4fe..8dd703474f 100644
--- a/resources/lang/de_DE/firefly.php
+++ b/resources/lang/de_DE/firefly.php
@@ -63,17 +63,17 @@ return [
'two_factor_lost_header' => 'Haben Sie ihre Zwei-Faktor-Authentifizierung verloren?',
'two_factor_lost_intro' => 'Leider ist dieses etwas, dass sie nicht über die Weboberfläche zurücksetzen können. Sie haben zwei Möglichkeiten.',
'two_factor_lost_fix_self' => 'Wenn Sie Ihre eigene Instanz von Firefly III betreiben, überprüfen Sie die Logdatei unter storage/logs für weitere Anweisungen.',
- 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.',
+ 'two_factor_lost_fix_owner' => 'Ansonsten, mailen Sie dem Inhaber der Website, :site_owner und bitten Sie ihn, Ihre Zwei-Faktor Authentifizierung zurückzusetzen.',
'warning_much_data' => ':days Tage an Daten können eine Weile dauern zu laden.',
'registered' => 'Sie haben sich erfolgreich registriert!',
'search' => 'Suche',
- 'search_found_accounts' => 'Found :count account(s) for your query.',
- 'search_found_categories' => 'Found :count category(ies) for your query.',
- 'search_found_budgets' => 'Found :count budget(s) for your query.',
- 'search_found_tags' => 'Found :count tag(s) for your query.',
- 'search_found_transactions' => 'Found :count transaction(s) for your query.',
+ 'search_found_accounts' => ':count Account(s) für Ihre Suche gefunden.',
+ 'search_found_categories' => ':count Kategorie(n) für Ihre Suche gefunden.',
+ 'search_found_budgets' => ':count Budget(s) für Ihre Suche gefunden.',
+ 'search_found_tags' => ':count Tag(s) für Ihre Suche gefunden.',
+ 'search_found_transactions' => ':count Transaktion(en) für Ihre Suche gefunden.',
'results_limited' => 'Es werden maximal :count Ergebnisse angezeigt.',
- 'tagbalancingAct' => 'Balancing act',
+ 'tagbalancingAct' => 'Ausgleich',
'tagadvancePayment' => 'Vorauszahlung',
'tagnothing' => '',
'Default asset account' => 'Standard-Anlagekonto',
@@ -83,14 +83,14 @@ return [
'source_accounts' => 'Herkunftskonto',
'destination_accounts' => 'Zielkonto',
'user_id_is' => 'Ihre Benutzerkennung ist :user',
- 'field_supports_markdown' => 'This field supports Markdown.',
- 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.',
- 'nothing_to_display' => 'There are no transactions to show you',
- 'show_all_no_filter' => 'Show all transactions without grouping them by date.',
+ 'field_supports_markdown' => 'Diese Feld unterstützt Abschlag .',
+ 'need_more_help' => 'Wenn Sie weitere Hilfe für das Verwenden von Firefly III benötigen, öffnen Sie ein Ticket auf Github.',
+ 'nothing_to_display' => 'Es gibt keine Transaktionen zum Anzeigen',
+ 'show_all_no_filter' => 'Alle Transaktionen anzeigen, ohne diese nach Datum zu gruppieren.',
'expenses_by_category' => 'Ausgaben nach Kategorie',
'expenses_by_budget' => 'Ausgaben nach Budget',
'income_by_category' => 'Einkommen nach Kategorie',
- 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.',
+ 'cannot_redirect_to_account' => 'Entschuldigung. Firefly III kann Sie nicht zur richtigen Seite weiterleiten.',
// repeat frequencies:
'repeat_freq_yearly' => 'Jährlich',
'repeat_freq_monthly' => 'monatlich',
@@ -101,14 +101,14 @@ return [
// account confirmation:
'confirm_account_header' => 'Bitte bestätigen Sie Ihr Konto',
'confirm_account_intro' => 'Eine E-Mail wurde an die Adresse gesendet, welche bei der Registrierung angegeben wurde. Bitte lese die Mail für weitere Anweisungen. Wenn Sie die Mail nicht erhalten haben, kann Firefly Sie erneut senden.',
- 'confirm_account_resend_email' => 'Send me the confirmation message I need to activate my account.',
+ 'confirm_account_resend_email' => 'Senden Sie mir die Bestätigung, ich möchte meinen Account aktivieren.',
'account_is_confirmed' => 'Ihr Benutzerkonto wurde bestätigt!',
'invalid_activation_code' => 'Es scheint der genutzte Code ist ungültig oder ist abgelaufen.',
'confirm_account_is_resent_header' => 'Die Bestätigung wurde erneut gesendet',
'confirm_account_is_resent_text' => 'Die Bestätigungsmail wurde erneut gesendet. Wenn Sie die Bestätigungsmail weiterhin nicht erhalten wenden Sie sich bitte an den Seitenbetreiber unter Seitenbetreiber> oder überprüfen Sie das Fehlerprotokoll.',
'confirm_account_is_resent_go_home' => 'Zur Hauptseite von Firefly wechseln',
'confirm_account_not_resent_header' => 'Etwas ist schief gelaufen :(',
- 'confirm_account_not_resent_intro' => 'The confirmation message has been not resent. If you still did not receive the confirmation message, please contact the site owner at :owner instead. Possibly, you have tried to resend the activation message too often. You can have Firefly III try to resend the confirmation message every hour.',
+ 'confirm_account_not_resent_intro' => 'Die Bestätigung wurde nicht erneut versendet. Wenn Sie immer noch keine Bestätigung erhalten haben, wenden Sie sich stattdessen bitte an den Inhaber der Webseite :owner. Möglicherweise haben Sie zu oft versucht die Bestätigung zu senden. Sie können die Bestätigung nur einmal pro Stunde durch Firefly III erneut senden lassen.',
'confirm_account_not_resent_go_home' => 'Zur Hauptseite von Firefly wechseln',
// export data:
@@ -160,11 +160,11 @@ return [
'deleted_rule_group' => 'Regelgruppe ":title" gelöscht',
'update_rule_group' => 'Aktualisiere neue Regelgruppe',
'no_rules_in_group' => 'Die Gruppe enthält keine Regeln',
- 'move_rule_group_up' => 'Move rule group up',
- 'move_rule_group_down' => 'Move rule group down',
+ 'move_rule_group_up' => 'Regelgruppe nach oben verschieben',
+ 'move_rule_group_down' => 'Regelgruppe nach unten verschieben',
'save_rules_by_moving' => 'Speichern Sie diese Regel(n), indem Sie sie in eine andere Gruppe verschieben:',
'make_new_rule' => 'Erstelle neue Regel in der Regelgruppe ":title"',
- 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.',
+ 'rule_help_stop_processing' => 'Wenn Sie dieses Kontrollkästchen aktivieren, werden spätere Regeln in dieser Gruppe nicht ausgeführt.',
'rule_help_active' => 'Inaktive Regeln werden nie ausgeführt.',
'stored_new_rule' => 'Speichere neue Regel mit Titel ":title"',
'deleted_rule' => 'Lösche Regel mit Titel ":title"',
@@ -174,17 +174,17 @@ return [
'default_rule_group_description' => 'Alle Ihre Regeln ohne eine bestimmte Gruppe.',
'default_rule_name' => 'Ihre erste Standardregel',
'default_rule_description' => 'Diese Regel ist ein Beispiel. Sie können sie problemlos löschen.',
- 'default_rule_trigger_description' => 'The Man Who Sold the World',
+ 'default_rule_trigger_description' => 'Der Mann der die Welt verkaufte',
'default_rule_trigger_from_account' => 'David Bowie',
- 'default_rule_action_prepend' => 'Bought the world from ',
+ 'default_rule_action_prepend' => 'Kaufte die Welt von ',
'default_rule_action_set_category' => 'Große Ausgaben',
'trigger' => 'Trigger',
'trigger_value' => 'Auslösen bei Wert',
- 'stop_processing_other_triggers' => 'Stop processing other triggers',
+ 'stop_processing_other_triggers' => 'Keine weiteren Auslöser verarbeiten',
'add_rule_trigger' => 'Neue Trigger hinzufügen',
'action' => 'Aktion',
'action_value' => 'Aktionswert',
- 'stop_executing_other_actions' => 'Stop executing other actions',
+ 'stop_executing_other_actions' => 'Keine weiteren Aktionen durchführen',
'add_rule_action' => 'Neue Aktion hinzufügen',
'edit_rule' => 'Bearbeite Regel ":title"',
'delete_rule' => 'Lösche Regel ":title"',
@@ -210,8 +210,8 @@ return [
'rule_trigger_to_account_starts' => 'Das Zielkonto startet mit ":trigger_value"',
'rule_trigger_to_account_ends' => 'Das Zielkonto endet mit ":trigger_value"',
'rule_trigger_to_account_is' => 'Das Zielkonto ist ":trigger_value"',
- 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"',
- 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"',
+ 'rule_trigger_to_account_contains' => 'Zielkonto enthält ":trigger_value"',
+ 'rule_trigger_transaction_type' => 'Transaktion ist vom Typ ": Trigger_value"',
'rule_trigger_amount_less' => 'Betrag ist kleiner als :trigger_value',
'rule_trigger_amount_exactly' => 'Betrag ist :trigger_value',
'rule_trigger_amount_more' => 'Betrag ist größer als :trigger_value',
@@ -235,32 +235,32 @@ return [
'rule_trigger_description_ends_choice' => 'Beschreibung endet mit..',
'rule_trigger_description_contains_choice' => 'Beschreibung enthält..',
'rule_trigger_description_is_choice' => 'Beschreibung ist..',
- 'rule_trigger_store_journal' => 'When a journal is created',
- 'rule_trigger_update_journal' => 'When a journal is updated',
- 'rule_action_set_category' => 'Set category to ":action_value"',
+ 'rule_trigger_store_journal' => 'Wenn eine Transaktion erstellt wird',
+ 'rule_trigger_update_journal' => 'Wenn eine Transaktion aktualisiert wird',
+ 'rule_action_set_category' => 'Kategorie zu ":action_value" festlegen',
'rule_action_clear_category' => 'Bereinige Kategorie',
- 'rule_action_set_budget' => 'Set budget to ":action_value"',
+ 'rule_action_set_budget' => 'Budget zu ":action_value" festlegen',
'rule_action_clear_budget' => 'Lösche Budget',
- 'rule_action_add_tag' => 'Add tag ":action_value"',
- 'rule_action_remove_tag' => 'Remove tag ":action_value"',
+ 'rule_action_add_tag' => 'Tag ":action_value" hinzufügen',
+ 'rule_action_remove_tag' => 'Tag ":action_value" entfernen',
'rule_action_remove_all_tags' => 'Alle Tags entfernen',
- 'rule_action_set_description' => 'Set description to ":action_value"',
+ 'rule_action_set_description' => 'Beschreibung setzen für ":action_value"',
'rule_action_append_description' => 'Append description with ":action_value"',
'rule_action_prepend_description' => 'Prepend description with ":action_value"',
- 'rule_action_set_category_choice' => 'Set category to..',
- 'rule_action_clear_category_choice' => 'Clear any category',
- 'rule_action_set_budget_choice' => 'Set budget to..',
- 'rule_action_clear_budget_choice' => 'Clear any budget',
+ 'rule_action_set_category_choice' => 'Kategorie festlegen..',
+ 'rule_action_clear_category_choice' => 'Bereinige jede Kategorie',
+ 'rule_action_set_budget_choice' => 'Budget festlegen..',
+ 'rule_action_clear_budget_choice' => 'Lösche jedes Budget',
'rule_action_add_tag_choice' => 'Füge Tag hinzu..',
'rule_action_remove_tag_choice' => 'Entferne Tag..',
'rule_action_remove_all_tags_choice' => 'Entferne alle Tags',
- 'rule_action_set_description_choice' => 'Set description to..',
- 'rule_action_append_description_choice' => 'Append description with..',
- 'rule_action_prepend_description_choice' => 'Prepend description with..',
- 'rule_action_set_source_account_choice' => 'Set source account to...',
- 'rule_action_set_source_account' => 'Set source account to :action_value',
- 'rule_action_set_destination_account_choice' => 'Set destination account to...',
- 'rule_action_set_destination_account' => 'Set destination account to :action_value',
+ 'rule_action_set_description_choice' => 'Beschreibung festlegen..',
+ 'rule_action_append_description_choice' => 'An Beschreibung anhängen..',
+ 'rule_action_prepend_description_choice' => 'An Beschreibung voranstellen..',
+ 'rule_action_set_source_account_choice' => 'Lege Quellkonto fest...',
+ 'rule_action_set_source_account' => 'Lege Quellkonto als :action_value fest',
+ 'rule_action_set_destination_account_choice' => 'Zielkonto festlegen...',
+ 'rule_action_set_destination_account' => 'Lege Zielkonto als :action_value fest',
// tags
'store_new_tag' => 'Neuen Tag speichern',
@@ -302,19 +302,20 @@ return [
'preferences_frontpage' => 'Startbildschirm',
'preferences_security' => 'Sicherheit',
'preferences_layout' => 'Layout',
- 'pref_home_show_deposits' => 'Show deposits on the home screen',
- 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?',
+ 'pref_home_show_deposits' => 'Einnahmen auf dem Startbildschirm anzeigen',
+ 'pref_home_show_deposits_info' => 'Der Startbildschirm zeigt schon Ihre Ausgabenkonten an.
+Sollen zusätzlich Ihre Girokonten angezeigt werden?',
'pref_home_do_show_deposits' => 'Ja, zeige sie an',
- 'successful_count' => 'of which :count successful',
+ 'successful_count' => 'davon :count erfolgreich',
'transaction_page_size_title' => 'Seitengröße',
- 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions',
+ 'transaction_page_size_help' => 'Jede Liste von Transaktionen zeigt maximal folgende Anzahl an Transaktionen',
'transaction_page_size_label' => 'Seitengröße',
'between_dates' => '(:start und :end)',
'pref_optional_fields_transaction' => 'Optionale Felder für Überweisungen',
'pref_optional_fields_transaction_help' => 'Standardmäßig sind nicht alle Felder aktiviert, wenn eine neue Überweisung erstellt wird (wegen der Übersicht). Unten können Sie diese Felder aktivieren, wenn Sie glauben, dass Sie nützlich für Sie sind. Alle Felder die deaktiviert sind, aber bereits ausgefüllt sind, werden unabhängig von ihren Einstellung sichtbar sein.',
'optional_tj_date_fields' => 'Datumsfeld',
- 'optional_tj_business_fields' => 'Business fields',
- 'optional_tj_attachment_fields' => 'Attachment fields',
+ 'optional_tj_business_fields' => 'Geschäftsfelder',
+ 'optional_tj_attachment_fields' => 'Anlage-Felder',
'pref_optional_tj_interest_date' => 'Zinstermin',
'pref_optional_tj_book_date' => 'Buchungstermin',
'pref_optional_tj_process_date' => 'Bearbeitungsdatum',
@@ -344,7 +345,7 @@ return [
'delete_account_button' => 'LÖSCHEN Sie ihr Benutzerkonto',
'invalid_current_password' => 'Aktuelles Passwort ist ungültig!',
'password_changed' => 'Passwort geändert!',
- 'should_change' => 'The idea is to change your password.',
+ 'should_change' => 'Ziel ist es, ihr Passwort zu ändern.',
'invalid_password' => 'Ungültiges Passwort!',
@@ -373,9 +374,9 @@ return [
'title_transfers' => 'Überweisungen',
// convert stuff:
- 'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal',
- 'convert_is_already_type_Deposit' => 'This transaction is already a deposit',
- 'convert_is_already_type_Transfer' => 'This transaction is already a transfer',
+ 'convert_is_already_type_Withdrawal' => 'Diese Transaktion ist bereits eine Ausgabe',
+ 'convert_is_already_type_Deposit' => 'Diese Transaktion ist bereits eine Einzahlung',
+ 'convert_is_already_type_Transfer' => 'Diese Transaktion ist bereits eine Überweisung',
'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal',
'convert_to_Deposit' => 'Convert ":description" to a deposit',
'convert_to_Transfer' => 'Convert ":description" to a transfer',
@@ -392,7 +393,7 @@ return [
'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer',
'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit',
'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal',
- 'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.',
+ 'convert_please_set_revenue_source' => 'Bitte ein Eingangskonto wählen, woher das Geld kommen wird.',
'convert_please_set_asset_destination' => 'Please pick the asset account where the money will go to.',
'convert_please_set_expense_destination' => 'Please pick the expense account where the money will go to.',
'convert_please_set_asset_source' => 'Please pick the asset account where the money will come from.',
@@ -413,7 +414,7 @@ return [
'create_new_transfer' => 'Eine neue Überweisung erstellen',
'create_new_asset' => 'Erstellen Sie ein neuen Girokonto',
'create_new_expense' => 'Neuen Debitor (Ausgabe) erstellen',
- 'create_new_revenue' => 'Create new revenue account',
+ 'create_new_revenue' => 'Neuen Schuldner erstellen',
'create_new_piggy_bank' => 'Ein neues Sparschwein erstellen',
'create_new_bill' => 'Eine neue Rechnung erstellen',
@@ -466,71 +467,73 @@ return [
'update_budget_amount_range' => 'Update (expected) available amount between :start and :end',
// bills:
- 'matching_on' => 'Matching on',
+ 'matching_on' => 'Reagiert auf',
'between_amounts' => 'zwischen :low und :high.',
'repeats' => 'Wiederholungen',
'connected_journals' => 'Vernüpfte Überweisungen',
- 'auto_match_on' => 'Automatically matched by Firefly',
+ 'auto_match_on' => 'Automatisch von Firefly abgestimmt',
'auto_match_off' => 'Not automatically matched by Firefly',
- 'next_expected_match' => 'Next expected match',
+ 'next_expected_match' => 'Nächste erwartete Übereinstimmung',
'delete_bill' => 'Rechnung ":name" löschen',
'deleted_bill' => 'Rechnung ":name" gelöscht',
'edit_bill' => 'Rechnung ":name" bearbeiten',
'more' => 'Weitere',
- 'rescan_old' => 'Rescan old transactions',
+ 'rescan_old' => 'Ältere Transaktionen Scannen',
'update_bill' => 'Aktualisieren Sie eine Rechnung',
'updated_bill' => 'Rechnung ":name" aktualisiert',
'store_new_bill' => 'Neue Rechnung speichern',
'stored_new_bill' => 'Neue Rechung ":name" gespeichert',
- 'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.',
- 'rescanned_bill' => 'Rescanned everything.',
+ 'cannot_scan_inactive_bill' => 'Inaktive Rechnungen können nicht gesacannt werden.',
+ 'rescanned_bill' => 'Alles gescannt.',
'average_bill_amount_year' => 'Durchschnittliche Rechnungssumme (:year)',
'average_bill_amount_overall' => 'Durchschnittliche Rechnungssumme (gesamt)',
- 'not_or_not_yet' => 'Not (yet)',
- 'not_expected_period' => 'Not expected this period',
+ 'not_or_not_yet' => '(noch) nicht',
+ 'not_expected_period' => 'Diesen Zeitraum nicht erwartet',
// accounts:
'details_for_asset' => 'Details für Girokonto ":name"',
- 'details_for_expense' => 'Details for expense account ":name"',
- 'details_for_revenue' => 'Details for revenue account ":name"',
+ 'details_for_expense' => 'Details für Debitor ":name"',
+ 'details_for_revenue' => 'Details für Schuldner ":name"',
'details_for_cash' => 'Details for cash account ":name"',
'store_new_asset_account' => 'Speichere neues Girokonto',
- 'store_new_expense_account' => 'Store new expense account',
- 'store_new_revenue_account' => 'Store new revenue account',
+ 'store_new_expense_account' => 'Speichere neuen Debitor (Ausgabe)',
+ 'store_new_revenue_account' => 'Speichere neuen Schuldner',
'edit_asset_account' => 'Bearbeite Girokonto ":name"',
- 'edit_expense_account' => 'Edit expense account ":name"',
- 'edit_revenue_account' => 'Edit revenue account ":name"',
+ 'edit_expense_account' => 'Debitor (Ausgabe) ":name" bearbeiten',
+ 'edit_revenue_account' => 'Schuldner ":name" bearbeiten',
'delete_asset_account' => 'Lösche Girokonto ":name"',
- 'delete_expense_account' => 'Delete expense account ":name"',
- 'delete_revenue_account' => 'Delete revenue account ":name"',
+ 'delete_expense_account' => 'Debitor (Ausgabe) ":name" löschen',
+ 'delete_revenue_account' => 'Schuldner ":name" löschen',
'asset_deleted' => 'Girokonto ":name" wurde erfolgreich gelöscht',
- 'expense_deleted' => 'Successfully deleted expense account ":name"',
- 'revenue_deleted' => 'Successfully deleted revenue account ":name"',
+ 'expense_deleted' => 'Debitor (Ausgabe) ":name" erfolgreich gelöscht',
+ 'revenue_deleted' => 'Schuldner ":name" erfolgreich gelöscht',
'update_asset_account' => 'Girokonto aktualisieren',
- 'update_expense_account' => 'Update expense account',
- 'update_revenue_account' => 'Update revenue account',
+ 'update_expense_account' => 'Debitor (Ausgabe) bearbeiten',
+ 'update_revenue_account' => 'Schuldner bearbeiten',
'make_new_asset_account' => 'Erstellen Sie ein neuen Girokonto',
- 'make_new_expense_account' => 'Create a new expense account',
- 'make_new_revenue_account' => 'Create a new revenue account',
+ 'make_new_expense_account' => 'Neuen Debitor (Ausgabe) erstellen',
+ 'make_new_revenue_account' => 'Neuen Schuldner erstellen',
'asset_accounts' => 'Girokonten',
'expense_accounts' => 'Debitoren (Ausgaben)',
'revenue_accounts' => 'Schuldner',
'cash_accounts' => 'Bargeldkonten',
'Cash account' => 'Bargeldkonto',
'account_type' => 'Kontotyp',
- 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:',
- 'stored_new_account' => 'New account ":name" stored!',
+ 'save_transactions_by_moving' => 'Speichern Sie diese Transaktion(en), indem Sie sie auf ein anderes Konto verschieben:',
+ 'stored_new_account' => 'Neues Konto ":name" gespeichert!',
'updated_account' => 'Updated account ":name"',
'credit_card_options' => 'Kreditkartenoptionen',
'no_transactions_account' => 'There are no transactions (in this period) for asset account ":name".',
'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.',
- 'select_more_than_one_account' => 'Please select more than one account',
- 'select_more_than_one_category' => 'Please select more than one category',
+ 'select_more_than_one_account' => 'Bitte mehr als ein Konto wählen',
+ 'select_more_than_one_category' => 'Bitte mehr als eine Kategorie wählen',
+ 'select_more_than_one_budget' => 'Bitte mehr als ein Budget wählen',
+ 'from_to' => 'Von :start bis :end',
// categories:
'new_category' => 'Neue Kategorie',
'create_new_category' => 'Eine neue Kategorie herstellen',
'without_category' => 'Ohne Kategorie',
- 'update_category' => 'Update category',
+ 'update_category' => 'Kategorie aktualisieren',
'updated_category' => 'Kategorie ":name" aktualisiert',
'categories' => 'Kategorien',
'edit_category' => 'Kategorie ":name" bearbeiten',
@@ -598,8 +601,8 @@ return [
'Asset account' => 'Girokonto',
'Default account' => 'Girokonto',
'Expense account' => 'Debitor (Ausgabe)',
- 'Revenue account' => 'Revenue account',
- 'Initial balance account' => 'Initial balance account',
+ 'Revenue account' => 'Kreditor Einnahme',
+ 'Initial balance account' => 'Eröffnungssaldo',
'budgets' => 'Budgets',
'tags' => 'Tags',
'reports' => 'Berichte',
@@ -622,7 +625,7 @@ return [
'no' => 'Nein',
'amount' => 'Betrag',
'overview' => 'Übersicht',
- 'saveOnAccount' => 'Save on account',
+ 'saveOnAccount' => 'Rücklagen für Konto',
'unknown' => 'Unbekannt',
'daily' => 'Täglich',
'monthly' => 'Monatlich',
@@ -633,16 +636,17 @@ return [
'report_default' => 'Standard Finanzbericht für :start bis :end',
'report_audit' => 'Transaction history overview for :start until :end',
'report_category' => 'Category report for :start until :end',
+ 'report_budget' => 'Budget report for :start until :end',
'quick_link_reports' => 'Schnellzugriff',
'quick_link_default_report' => 'Standard Finanzbericht',
- 'quick_link_audit_report' => 'Transaction history overview',
+ 'quick_link_audit_report' => 'Transaktionshistorie',
'report_this_month_quick' => 'Aktueller Monat, alle Konten',
'report_this_year_quick' => 'Aktuelles Jahr, alle Konten',
'report_this_fiscal_year_quick' => 'Aktuelles Geschäftsjahr, alle Konten',
'report_all_time_quick' => 'Gesamte Zeit, alle Konten',
'reports_can_bookmark' => 'Berichte können als Lesezeichen gespeichert werden.',
'incomeVsExpenses' => 'Einkommen vs Ausgaben',
- 'accountBalances' => 'Account balances',
+ 'accountBalances' => 'Kontostände',
'balanceStartOfYear' => 'Bilanz zum Jahresbeginn',
'balanceEndOfYear' => 'Bilanz zum Jahresende',
'balanceStartOfMonth' => 'Bilanz zum Monatsbeginn',
@@ -651,12 +655,12 @@ return [
'balanceEnd' => 'Bilanz zum Ende der Periode',
'reportsOwnAccounts' => 'Berichte für Ihren eigenen Konten',
'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts',
- 'splitByAccount' => 'Split by account',
- 'balancedByTransfersAndTags' => 'Balanced by transfers and tags',
- 'coveredWithTags' => 'Covered with tags',
+ 'splitByAccount' => 'Nach Konto aufteilen',
+ 'balancedByTransfersAndTags' => 'Ausgeglichen durch Überweisungen und Tags',
+ 'coveredWithTags' => 'Abgedeckt durch Tags',
'leftUnbalanced' => 'Left unbalanced',
- 'expectedBalance' => 'Expected balance',
- 'outsideOfBudgets' => 'Outside of budgets',
+ 'expectedBalance' => 'Erwarteter Kontostand',
+ 'outsideOfBudgets' => 'Ausserhalb von Budgets',
'leftInBudget' => 'Left in budget',
'sumOfSums' => 'Summe der Summen',
'noCategory' => '(keine Kategorie)',
@@ -679,6 +683,7 @@ return [
'report_type_default' => 'Default financial report',
'report_type_audit' => 'Transaction history overview (audit)',
'report_type_category' => 'Category report',
+ 'report_type_budget' => 'Budget report',
'report_type_meta-history' => 'Übersicht über Kategorien, Budgets und Rechnungen',
'more_info_help' => 'Weitere Informationen über diese Art von Berichten finden Sie in der Hilfe. Drücken Sie hierfür das (?)-Symbol in der oberen rechten Ecke.',
'report_included_accounts' => 'Eingezogene Konten',
@@ -692,17 +697,20 @@ return [
'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end',
'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end',
'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.',
- 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance',
- 'reports_extra_options' => 'Extra options',
- 'report_has_no_extra_options' => 'This report has no extra options',
+ 'audit_end_balance' => 'Kontostand von :account_name Ende war: :balance',
+ 'reports_extra_options' => 'Zusatzoptionen',
+ 'report_has_no_extra_options' => 'Dieser Bericht hat keine zusätzliche Optionen',
'reports_submit' => 'Zeige Bericht',
- 'end_after_start_date' => 'End date of report must be after start date.',
- 'select_category' => 'Select one or more categories.',
- 'income_per_category' => 'Income per category',
- 'expense_per_category' => 'Expense per category',
- 'income_per_account' => 'Income per account',
- 'expense_per_account' => 'Expense per account',
- 'include_not_in_category' => 'Include transactions not selected for this report',
+ 'end_after_start_date' => 'Enddatum des Berichts muss nach dem Startdatum liegen.',
+ 'select_category' => 'Wählen Sie eine oder mehrere Kategorien aus.',
+ 'select_budget' => 'Wählen Sie ein oder mehrere Budgets aus.',
+ 'income_per_category' => 'Einnahmen pro Kategorie',
+ 'expense_per_category' => 'Ausgaben pro Kategorie',
+ 'expense_per_budget' => 'Ausgaben pro Budget',
+ 'income_per_account' => 'Einnahmen pro Konto',
+ 'expense_per_account' => 'Ausgaben pro Konto',
+ 'include_not_in_category' => 'Kategorien einbeziehen, die nicht für diesen Report ausgewählt wurden',
+ 'include_not_in_budget' => 'Budgets einbeziehen, die nicht für diesen Report ausgewählt wurden',
'everything_else' => 'Der Rest',
'income_and_expenses' => 'Einkommen und Ausgaben',
'spent_average' => 'Ausgaben (Durchschnitt)',
@@ -712,6 +720,8 @@ return [
'average_income_per_account' => 'Durchschnittseinkommen pro Konto',
'total' => 'Gesamt',
'description' => 'Beschreibung',
+ 'sum_of_period' => 'Sum of period',
+ 'average_in_period' => 'Average in period',
// charts:
@@ -720,13 +730,15 @@ return [
'month' => 'Monat',
'budget' => 'Budget',
'spent' => 'Ausgegeben',
+ 'spent_in_budget' => 'Spent in budget',
+ 'left_to_spend' => 'Left to spend',
'earned' => 'Verdient',
'overspent' => 'Zuviel ausgegeben',
'left' => 'Übrig',
'no_budget' => '(kein Budget)',
- 'maxAmount' => 'Höchstbetrag',
- 'minAmount' => 'Mindestbetrag',
- 'billEntry' => 'Current bill entry',
+ 'max-amount' => 'Höchstbetrag',
+ 'min-amount' => 'Mindestbetrag',
+ 'journal-amount' => 'Current bill entry',
'name' => 'Name',
'date' => 'Datum',
'paid' => 'Bezahlt',
@@ -747,17 +759,17 @@ return [
'store_piggy_bank' => 'Speichere neues Sparschwein',
'stored_piggy_bank' => 'Speichere neues Sparschwein ":name"',
'account_status' => 'Kontostatus',
- 'left_for_piggy_banks' => 'Left for piggy banks',
- 'sum_of_piggy_banks' => 'Sum of piggy banks',
+ 'left_for_piggy_banks' => 'Übrig für Sparschweine',
+ 'sum_of_piggy_banks' => 'Summe der Sparschweine',
'saved_so_far' => 'Gespart',
'left_to_save' => 'Zu Sparen',
- 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"',
- 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"',
+ 'add_money_to_piggy_title' => 'Geld dem Sparschwein ":name" hinzufügen',
+ 'remove_money_from_piggy_title' => 'Geld dem Sparschwein ":name" entnehmen',
'add' => 'Hinzufügen',
'remove' => 'Entfernen',
- 'max_amount_add' => 'The maximum amount you can add is',
- 'max_amount_remove' => 'The maximum amount you can remove is',
+ 'max_amount_add' => 'Der maximale Betrag, den Sie hinzufügen können ist',
+ 'max_amount_remove' => 'Der maximale Betrag, den Sie entnehmen können ist',
'update_piggy_button' => 'Sparschwein aktualisieren',
'update_piggy_title' => 'Sparschwein ":name" aktualisieren',
'updated_piggy_bank' => 'Sparschwein ":name" aktualisiert',
@@ -771,7 +783,7 @@ return [
'table' => 'Tabelle',
'piggy_bank_not_exists' => 'Dieses Sparschwein existiert nicht mehr.',
'add_any_amount_to_piggy' => 'Fügen sie Geld ihrem Sparschein hinzu, um ihr Ziel von :amount zu erreichen.',
- 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date',
+ 'add_set_amount_to_piggy' => ':amount einzahlen um Sparschwein bis :date zu füllen',
'delete_piggy_bank' => 'Sparschwein ":name" löschen',
'cannot_add_amount_piggy' => ':amount konnte nicht zu ":name" hinzugefügt werden.',
'deleted_piggy_bank' => 'Sparschwein ":name" gelöscht',
@@ -780,25 +792,25 @@ return [
'cannot_remove_amount_piggy' => 'Konnten :amount nicht von ":name" entfernen.',
// tags
- 'regular_tag' => 'Just a regular tag.',
+ 'regular_tag' => 'Nur ein normaler Tag.',
'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.',
- 'delete_tag' => 'Delete tag ":tag"',
- 'deleted_tag' => 'Deleted tag ":tag"',
- 'new_tag' => 'Make new tag',
- 'edit_tag' => 'Edit tag ":tag"',
- 'updated_tag' => 'Updated tag ":tag"',
- 'created_tag' => 'Tag ":tag" has been created!',
+ 'delete_tag' => 'Tag ":tag" entfernen',
+ 'deleted_tag' => 'Tag ":tag" entfernt',
+ 'new_tag' => 'Neuen Tag erstellen',
+ 'edit_tag' => 'Tag ":tag" bearbeiten',
+ 'updated_tag' => 'Aktualisierter Tag ":tag"',
+ 'created_tag' => 'Tag ":tag" wurde erstellt!',
'no_year' => 'Kein Jahr angegeben',
'no_month' => 'Kein Monat angegeben',
- 'tag_title_nothing' => 'Default tags',
- 'tag_title_balancingAct' => 'Balancing act tags',
- 'tag_title_advancePayment' => 'Advance payment tags',
+ 'tag_title_nothing' => 'Standard-Tags',
+ 'tag_title_balancingAct' => 'Ausgleich Tags',
+ 'tag_title_advancePayment' => 'Vorauszahlung Tags',
'tags_introduction' => 'In der Regel sind Tags einzelne Worte, erdacht um Einträge mit Begriffen wie Ausgaben, Rechnungen oder Partyvorbereitung schnell zusammenzufassen. In Firefly III können Tags weitere Eigenschaften wie ein Datum, eine Beschreibung und einen Ort enthalten. Dieses erlaubt es Ihnen Transaktionen in sinnvoller Weise miteinander zu verknüpfen. Zum Beispiel können Sie einen Tag mit dem Titel Weihnachtsessen mit Freunden erstellen und Informationen über das Restaurant hinzufügen. Solche Tags sind "einzigartig", sie werden nur für einen Anlass genutzt, enthalten aber eventuell mehrere Transaktionen.',
'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.',
'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.',
- 'transaction_journal_information' => 'Transaction information',
+ 'transaction_journal_information' => 'Transaktionsinformationen',
'transaction_journal_meta' => 'Metainformationen',
'total_amount' => 'Gesamtbetrag',
@@ -811,33 +823,45 @@ return [
'blocked_domains' => 'Blockierte Domains',
'no_domains_banned' => 'Keine blockierten Domains',
'all_user_domains' => 'All user email address domains',
- 'all_domains_is_filtered' => 'This list does not include already blocked domains.',
- 'domain_now_blocked' => 'Domain :domain is now blocked',
- 'domain_now_unblocked' => 'Domain :domain is now unblocked',
- 'manual_block_domain' => 'Block a domain by hand',
- 'block_domain' => 'Block domain',
- 'no_domain_filled_in' => 'No domain filled in',
- 'domain_already_blocked' => 'Domain :domain is already blocked',
- 'domain_is_now_blocked' => 'Domain :domain is now blocked',
- 'instance_configuration' => 'Configuration',
- 'firefly_instance_configuration' => 'Configuration options for Firefly III',
- 'setting_single_user_mode' => 'Single user mode',
- 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
- 'store_configuration' => 'Store configuration',
- 'single_user_administration' => 'User administration for :email',
+ 'all_domains_is_filtered' => 'Diese Liste enthält keine bereits gesperrten Domains.',
+ 'domain_now_blocked' => 'Domain :domain ist jetzt gesperrt',
+ 'domain_now_unblocked' => 'Domain :domain ist wieder freigegeben',
+ 'manual_block_domain' => 'Domain von Hand sperren',
+ 'block_domain' => 'Domain sperren',
+ 'no_domain_filled_in' => 'Keine domain angegeben',
+ 'domain_already_blocked' => 'Domain :domain ist bereits gesperrt',
+ 'domain_is_now_blocked' => 'Domain :domain ist jetzt gesperrt',
+ 'instance_configuration' => 'Konfiguration',
+ 'firefly_instance_configuration' => 'Konfigurationsoptionen für Firefly III',
+ 'setting_single_user_mode' => 'Einzelnutzermodus',
+ 'setting_single_user_mode_explain' => 'Standardmäßig aktzeptiert Firefly III nur eine Registration: Sie. Diese Sicherheitsmaßnahme verhindert das Benutzen Ihrer Instanz durch andere, außer Sie erlauben es. Zukünftige Registrationen sind gesperrt. Wenn Sie dieses Kontrollkästchen deaktivieren können andere ihre Instanz ebenfalls benutzen, vorausgesetzt sie ist erreichbar (mit dem Internet verbunden).',
+ 'store_configuration' => 'Konfiguration speichern',
+ 'single_user_administration' => 'Benutzerverwaltung für :email',
+ 'edit_user' => 'Benutzer :email bearbeiten',
'hidden_fields_preferences' => 'Zur Zeit sind nicht alle Felder sichtbar. Sie müssen in den Einstellungen aktiviert werden.',
- 'user_data_information' => 'User data',
- 'user_information' => 'User information',
- 'total_size' => 'total size',
- 'budget_or_budgets' => 'budget(s)',
- 'budgets_with_limits' => 'budget(s) with configured amount',
+ 'user_data_information' => 'Nutzerdaten',
+ 'user_information' => 'Benutzerinformationen',
+ 'total_size' => 'Gesamtgröße',
+ 'budget_or_budgets' => 'Budget(s)',
+ 'budgets_with_limits' => 'Budget(s) mit konfigurierten Betrag',
'rule_or_rules' => 'Regel(n)',
- 'rulegroup_or_groups' => 'rule group(s)',
+ 'rulegroup_or_groups' => 'Regelgruppe(n)',
'setting_must_confirm_account' => 'Kontobestätigung',
'setting_must_confirm_account_explain' => 'Wenn sie diese Option auswählen, müssen Benutzer vor Benutzung ihr Konto aktivieren.',
'configuration_updated' => 'Die Konfiguration wurde aktualisiert',
'setting_is_demo_site' => 'Demonstrationsseite',
'setting_is_demo_site_explain' => 'Wenn sie diese Option auswählen, wird sich diese Installation wie eine Demonstrationsseite verhalten, was ungwollte Auswirkungen haben kann.',
+ 'setting_send_email_notifications' => 'E-Mail-Benachrichtigungen senden',
+ 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.',
+ 'mail_for_lockout_help' => 'Wenn ein Benutzer gesperrt ist',
+ 'mail_for_blocked_domain_help' => 'When a user tries to register using a blocked domain',
+ 'mail_for_blocked_email_help' => 'When a user tries to register using a blocked email address',
+ 'mail_for_bad_login_help' => 'When a user fails to login',
+ 'mail_for_blocked_login_help' => 'When a blocked user tries to login',
+ 'block_code_bounced' => 'Email message(s) bounced',
+ 'block_code_expired' => 'Demo account expired',
+ 'no_block_code' => 'No reason for block or user not blocked',
+
// split a transaction:
'transaction_meta_data' => 'Transaktions Metadaten',
@@ -850,8 +874,8 @@ return [
'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
'store_splitted_withdrawal' => 'Store splitted withdrawal',
'update_splitted_withdrawal' => 'Update splitted withdrawal',
- 'split_title_deposit' => 'Split your new deposit',
- 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.',
+ 'split_title_deposit' => 'Neue Einnahme aufteilen',
+ 'split_intro_one_deposit' => 'Firefly untestützt das "Aufteilen" von Einnahmen.',
'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.',
'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.',
'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
@@ -891,16 +915,16 @@ return [
'import_data' => 'Daten importieren',
'import_data_full' => 'Importieren Sie Daten in Firefly III',
'import' => 'Import',
- 'import_file_help' => 'Select your file',
- 'import_status_settings_complete' => 'The import is ready to start.',
- 'import_status_import_complete' => 'The import has completed.',
+ 'import_file_help' => 'Datei auswählen',
+ 'import_status_settings_complete' => 'Der Import is startbereit.',
+ 'import_status_import_complete' => 'Import vollständig.',
'import_status_import_running' => 'The import is currently running. Please be patient.',
- 'import_status_header' => 'Import status and progress',
- 'import_status_errors' => 'Import errors',
- 'import_status_report' => 'Import report',
+ 'import_status_header' => 'Importstatus und Fortschritt',
+ 'import_status_errors' => 'Importfehler',
+ 'import_status_report' => 'Import-Bericht',
'import_finished' => 'Der Importierungsvorgang ist beendet',
- 'import_error_single' => 'An error has occured during the import.',
- 'import_error_multi' => 'Some errors occured during the import.',
+ 'import_error_single' => 'Beim Importieren ist ein Fehler aufgetreten.',
+ 'import_error_multi' => 'Beim Importieren sind Fehler aufgetreten.',
'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:',
'import_error_timeout' => 'Der Import scheint ein Zeitlimit überschritten zu haben. Wenn dieser Fehler weiterhin auftritt importieren Sie die Daten bitte über die Kommandozeile.',
'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.',
@@ -911,4 +935,10 @@ return [
'import_finished_link' => 'The transactions imported can be found in tag :tag.',
'need_at_least_one_account' => 'Sie benötigen mindestens ein Bestandskonto, um ein Sparschwein zu erstellen',
'see_help_top_right' => 'Mehr Information finden sie in den Hilfeseiten, indem sie auf das Fragezeichen in der rechten oberen Ecke klicken.',
+ 'bread_crumb_import_complete' => 'Import ":key" complete',
+ 'bread_crumb_configure_import' => 'Configure import ":key"',
+ 'bread_crumb_import_finished' => 'Import ":key" finished',
+ 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.',
+ 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".',
+ 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.',
];
\ No newline at end of file
diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php
index 1c7253ad19..a695d4be5f 100644
--- a/resources/lang/de_DE/form.php
+++ b/resources/lang/de_DE/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'Die eine Überweisungen, die mit dieser Kategorie verknüpft ist, wird nicht gelöscht. | Keine der :count Kategorien, die mit dieser Rechnung verknüpft sind, werden gelöscht.',
'tag_keep_transactions' => 'Die einzige Überweisung, die mit diesem Tag verknüpft ist, wird nicht gelöscht. | Keiner der :count Tags, die mit dieser Rechnung verknüpft sind, werden gelöscht.',
+ 'email' => 'Email address',
+ 'password' => 'Password',
+ 'password_confirmation' => 'Password (again)',
+ 'blocked' => 'Is blocked?',
+ 'blocked_code' => 'Reason for block',
+
+
// admin
- 'domain' => 'Domain',
- 'single_user_mode' => 'Einzelnutzermodus',
- 'must_confirm_account' => 'Erstanwender müssen ihr Konto aktivieren',
- 'is_demo_site' => 'Ist eine Demonstrationsseite',
+ 'domain' => 'Domain',
+ 'single_user_mode' => 'Einzelnutzermodus',
+ 'must_confirm_account' => 'Erstanwender müssen ihr Konto aktivieren',
+ 'is_demo_site' => 'Ist eine Demonstrationsseite',
+ 'mail_for_lockout' => 'Ausgesperrt',
+ 'mail_for_blocked_domain' => 'Gesperrte Domain',
+ 'mail_for_blocked_email' => 'Gesperrte Email-Adresse',
+ 'mail_for_bad_login' => 'Anmeldung fehlgeschlagen',
+ 'mail_for_blocked_login' => 'Gesperrter Benutzer',
+
// import
- 'import_file' => 'Datei importieren',
- 'configuration_file' => 'Konfigurationsdatei',
- 'import_file_type' => 'Import-Dateityp',
- 'csv_comma' => 'Ein Komma (,)',
- 'csv_semicolon' => 'Ein Semikolon (;)',
- 'csv_tab' => 'Ein Tab (unsichtbar)',
- 'csv_delimiter' => 'CSV-Trennzeichen',
- 'csv_import_account' => 'Standard Import-Konto',
- 'csv_config' => 'CSV-Import Einstellungen',
+ 'import_file' => 'Datei importieren',
+ 'configuration_file' => 'Konfigurationsdatei',
+ 'import_file_type' => 'Import-Dateityp',
+ 'csv_comma' => 'Ein Komma (,)',
+ 'csv_semicolon' => 'Ein Semikolon (;)',
+ 'csv_tab' => 'Ein Tab (unsichtbar)',
+ 'csv_delimiter' => 'CSV-Trennzeichen',
+ 'csv_import_account' => 'Standard Import-Konto',
+ 'csv_config' => 'CSV-Import Einstellungen',
'due_date' => 'Fälligkeitstermin',
diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php
index db48fdd75d..b7aeba4eb0 100644
--- a/resources/lang/de_DE/list.php
+++ b/resources/lang/de_DE/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => 'Schaltfläche',
'icon' => 'Icon',
+ 'id' => 'ID',
'create_date' => 'Erstellt am',
'update_date' => 'Aktualisiert am',
'balance_before' => 'Bilanz vor',
@@ -76,7 +77,7 @@ return [
'destination_account' => 'Zielkonto',
'accounts_count' => 'Anzahl Konten',
- 'journals_count' => 'Anzahl Transaktionen',
+ 'journals_count' => 'Anzahl der Zahlungsvorgänge',
'attachments_count' => 'Anzahl Anhänge',
'bills_count' => 'Anzahl Rechnungen',
'categories_count' => 'Anzahl Kategorien',
diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php
index 3edaa1a19e..5420eda870 100644
--- a/resources/lang/de_DE/validation.php
+++ b/resources/lang/de_DE/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => 'Dies ist keine gültige IBAN.',
'unique_account_number_for_user' => 'Es sieht so aus, als ob diese Kontonummer bereits verwendet würde.',
+ 'deleted_user' => 'Aufgrund von Sicherheitsbeschränkungen ist eine Registrierung dieser Email-Adresse nicht zugelassen.',
'rule_trigger_value' => 'Dieser Wert ist für den ausgewählten Trigger ungültig.',
'rule_action_value' => 'Dieser Wert ist für die gewählte Aktion ungültig.',
'invalid_domain' => 'Aufgrund von Sicherheitsbeschränkungen ist eine Registrierung von dieser Domain nicht zugelassen.',
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index 11d55a5cac..226834da8a 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -235,8 +235,8 @@ return [
'rule_trigger_description_ends_choice' => 'Description ends with..',
'rule_trigger_description_contains_choice' => 'Description contains..',
'rule_trigger_description_is_choice' => 'Description is..',
- 'rule_trigger_store_journal' => 'When a journal is created',
- 'rule_trigger_update_journal' => 'When a journal is updated',
+ 'rule_trigger_store_journal' => 'When a transaction is created',
+ 'rule_trigger_update_journal' => 'When a transaction is updated',
'rule_action_set_category' => 'Set category to ":action_value"',
'rule_action_clear_category' => 'Clear category',
'rule_action_set_budget' => 'Set budget to ":action_value"',
@@ -525,6 +525,8 @@ return [
'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.',
'select_more_than_one_account' => 'Please select more than one account',
'select_more_than_one_category' => 'Please select more than one category',
+ 'select_more_than_one_budget' => 'Please select more than one budget',
+ 'from_to' => 'From :start to :end',
// categories:
'new_category' => 'New category',
@@ -633,6 +635,7 @@ return [
'report_default' => 'Default financial report for :start until :end',
'report_audit' => 'Transaction history overview for :start until :end',
'report_category' => 'Category report for :start until :end',
+ 'report_budget' => 'Budget report for :start until :end',
'quick_link_reports' => 'Quick links',
'quick_link_default_report' => 'Default financial report',
'quick_link_audit_report' => 'Transaction history overview',
@@ -679,6 +682,7 @@ return [
'report_type_default' => 'Default financial report',
'report_type_audit' => 'Transaction history overview (audit)',
'report_type_category' => 'Category report',
+ 'report_type_budget' => 'Budget report',
'report_type_meta-history' => 'Categories, budgets and bills overview',
'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.',
'report_included_accounts' => 'Included accounts',
@@ -698,11 +702,14 @@ return [
'reports_submit' => 'View report',
'end_after_start_date' => 'End date of report must be after start date.',
'select_category' => 'Select one or more categories.',
+ 'select_budget' => 'Select one or more budgets.',
'income_per_category' => 'Income per category',
'expense_per_category' => 'Expense per category',
+ 'expense_per_budget' => 'Expense per budget',
'income_per_account' => 'Income per account',
'expense_per_account' => 'Expense per account',
- 'include_not_in_category' => 'Include transactions not selected for this report',
+ 'include_not_in_category' => 'Include categories not selected for this report',
+ 'include_not_in_budget' => 'Include budgets not selected for this report',
'everything_else' => 'Everything else',
'income_and_expenses' => 'Income and expenses',
'spent_average' => 'Spent (average)',
@@ -712,6 +719,8 @@ return [
'average_income_per_account' => 'Average income per account',
'total' => 'Total',
'description' => 'Description',
+ 'sum_of_period' => 'Sum of period',
+ 'average_in_period' => 'Average in period',
// charts:
@@ -720,13 +729,15 @@ return [
'month' => 'Month',
'budget' => 'Budget',
'spent' => 'Spent',
+ 'spent_in_budget' => 'Spent in budget',
+ 'left_to_spend' => 'Left to spend',
'earned' => 'Earned',
'overspent' => 'Overspent',
'left' => 'Left',
'no_budget' => '(no budget)',
- 'maxAmount' => 'Maximum amount',
- 'minAmount' => 'Minumum amount',
- 'billEntry' => 'Current bill entry',
+ 'max-amount' => 'Maximum amount',
+ 'min-amount' => 'Minumum amount',
+ 'journal-amount' => 'Current bill entry',
'name' => 'Name',
'date' => 'Date',
'paid' => 'Paid',
@@ -825,6 +836,7 @@ return [
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email',
+ 'edit_user' => 'Edit user :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.',
'user_data_information' => 'User data',
'user_information' => 'User information',
@@ -838,6 +850,17 @@ return [
'configuration_updated' => 'The configuration has been updated',
'setting_is_demo_site' => 'Demo site',
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
+ 'setting_send_email_notifications' => 'Send email notifications',
+ 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.',
+ 'mail_for_lockout_help' => 'When a user is locked out',
+ 'mail_for_blocked_domain_help' => 'When a user tries to register using a blocked domain',
+ 'mail_for_blocked_email_help' => 'When a user tries to register using a blocked email address',
+ 'mail_for_bad_login_help' => 'When a user fails to login',
+ 'mail_for_blocked_login_help' => 'When a blocked user tries to login',
+ 'block_code_bounced' => 'Email message(s) bounced',
+ 'block_code_expired' => 'Demo account expired',
+ 'no_block_code' => 'No reason for block or user not blocked',
+
// split a transaction:
'transaction_meta_data' => 'Transaction meta-data',
@@ -911,4 +934,10 @@ return [
'import_finished_link' => 'The transactions imported can be found in tag :tag.',
'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks',
'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.',
+ 'bread_crumb_import_complete' => 'Import ":key" complete',
+ 'bread_crumb_configure_import' => 'Configure import ":key"',
+ 'bread_crumb_import_finished' => 'Import ":key" finished',
+ 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.',
+ 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".',
+ 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.',
];
diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php
index a417f504a9..58ab10e01a 100644
--- a/resources/lang/en_US/form.php
+++ b/resources/lang/en_US/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+ 'email' => 'Email address',
+ 'password' => 'Password',
+ 'password_confirmation' => 'Password (again)',
+ 'blocked' => 'Is blocked?',
+ 'blocked_code' => 'Reason for block',
+
+
// admin
- 'domain' => 'Domain',
- 'single_user_mode' => 'Single user mode',
- 'must_confirm_account' => 'New users must activate account',
- 'is_demo_site' => 'Is demo site',
+ 'domain' => 'Domain',
+ 'single_user_mode' => 'Single user mode',
+ 'must_confirm_account' => 'New users must activate account',
+ 'is_demo_site' => 'Is demo site',
+ 'mail_for_lockout' => 'Locked out',
+ 'mail_for_blocked_domain' => 'Blocked domain',
+ 'mail_for_blocked_email' => 'Blocked email address',
+ 'mail_for_bad_login' => 'Login failure',
+ 'mail_for_blocked_login' => 'Blocked user',
+
// import
- 'import_file' => 'Import file',
- 'configuration_file' => 'Configuration file',
- 'import_file_type' => 'Import file type',
- 'csv_comma' => 'A comma (,)',
- 'csv_semicolon' => 'A semicolon (;)',
- 'csv_tab' => 'A tab (invisible)',
- 'csv_delimiter' => 'CSV field delimiter',
- 'csv_import_account' => 'Default import account',
- 'csv_config' => 'CSV import configuration',
+ 'import_file' => 'Import file',
+ 'configuration_file' => 'Configuration file',
+ 'import_file_type' => 'Import file type',
+ 'csv_comma' => 'A comma (,)',
+ 'csv_semicolon' => 'A semicolon (;)',
+ 'csv_tab' => 'A tab (invisible)',
+ 'csv_delimiter' => 'CSV field delimiter',
+ 'csv_import_account' => 'Default import account',
+ 'csv_config' => 'CSV import configuration',
'due_date' => 'Due date',
diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php
index a4ec03ab72..91254e401d 100644
--- a/resources/lang/en_US/list.php
+++ b/resources/lang/en_US/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => 'Buttons',
'icon' => 'Icon',
+ 'id' => 'ID',
'create_date' => 'Created at',
'update_date' => 'Updated at',
'balance_before' => 'Balance before',
@@ -76,7 +77,7 @@ return [
'destination_account' => 'Destination account',
'accounts_count' => 'Number of accounts',
- 'journals_count' => 'Number of journals',
+ 'journals_count' => 'Number of transactions',
'attachments_count' => 'Number of attachments',
'bills_count' => 'Number of bills',
'categories_count' => 'Number of categories',
diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php
index 33f05b180e..7905a9f4c8 100644
--- a/resources/lang/en_US/validation.php
+++ b/resources/lang/en_US/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => 'This is not a valid IBAN.',
'unique_account_number_for_user' => 'It looks like this account number is already in use.',
+ 'deleted_user' => 'Due to security constraints, you cannot register using this email address.',
'rule_trigger_value' => 'This value is invalid for the selected trigger.',
'rule_action_value' => 'This value is invalid for the selected action.',
'invalid_domain' => 'Due to security constraints, you cannot register from this domain.',
diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php
index cc80087ff2..9bd76c4595 100644
--- a/resources/lang/fr_FR/firefly.php
+++ b/resources/lang/fr_FR/firefly.php
@@ -235,8 +235,8 @@ return [
'rule_trigger_description_ends_choice' => 'La description se termine par..',
'rule_trigger_description_contains_choice' => 'La description contient..',
'rule_trigger_description_is_choice' => 'La description est..',
- 'rule_trigger_store_journal' => 'Lorsqu’un journal est créé',
- 'rule_trigger_update_journal' => 'Lorsqu’un journal est mis à jour',
+ 'rule_trigger_store_journal' => 'When a transaction is created',
+ 'rule_trigger_update_journal' => 'When a transaction is updated',
'rule_action_set_category' => 'Définir la catégorie à ":action_value"',
'rule_action_clear_category' => 'Supprimer la catégorie',
'rule_action_set_budget' => 'Définir le budget à ":action_value"',
@@ -525,6 +525,8 @@ return [
'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.',
'select_more_than_one_account' => 'Please select more than one account',
'select_more_than_one_category' => 'Please select more than one category',
+ 'select_more_than_one_budget' => 'Please select more than one budget',
+ 'from_to' => 'From :start to :end',
// categories:
'new_category' => 'Nouvelle catégorie',
@@ -633,6 +635,7 @@ return [
'report_default' => 'Rapport financier par défaut du :start au :end',
'report_audit' => 'Historique des transactions du :start au :end',
'report_category' => 'Category report for :start until :end',
+ 'report_budget' => 'Budget report for :start until :end',
'quick_link_reports' => 'Liens rapides',
'quick_link_default_report' => 'Rapport financier par défaut',
'quick_link_audit_report' => 'Historique des transactions',
@@ -679,6 +682,7 @@ return [
'report_type_default' => 'Rapport financier par défaut',
'report_type_audit' => 'Historique des transactions',
'report_type_category' => 'Category report',
+ 'report_type_budget' => 'Budget report',
'report_type_meta-history' => 'Vue d’ensemble des budgets, des catégories et des factures',
'more_info_help' => 'Plus d’informations sur ces types de rapports se trouvent dans les pages d’aide. Appuyez sur l’icône ( ?) dans le coin supérieur droit.',
'report_included_accounts' => 'Comptes inclus',
@@ -698,11 +702,14 @@ return [
'reports_submit' => 'View report',
'end_after_start_date' => 'End date of report must be after start date.',
'select_category' => 'Select one or more categories.',
+ 'select_budget' => 'Select one or more budgets.',
'income_per_category' => 'Income per category',
'expense_per_category' => 'Expense per category',
+ 'expense_per_budget' => 'Expense per budget',
'income_per_account' => 'Income per account',
'expense_per_account' => 'Expense per account',
- 'include_not_in_category' => 'Include transactions not selected for this report',
+ 'include_not_in_category' => 'Include categories not selected for this report',
+ 'include_not_in_budget' => 'Include budgets not selected for this report',
'everything_else' => 'Everything else',
'income_and_expenses' => 'Income and expenses',
'spent_average' => 'Spent (average)',
@@ -712,6 +719,8 @@ return [
'average_income_per_account' => 'Average income per account',
'total' => 'Total',
'description' => 'Description',
+ 'sum_of_period' => 'Sum of period',
+ 'average_in_period' => 'Average in period',
// charts:
@@ -720,13 +729,15 @@ return [
'month' => 'Mois',
'budget' => 'Budget',
'spent' => 'Dépensé',
+ 'spent_in_budget' => 'Spent in budget',
+ 'left_to_spend' => 'Left to spend',
'earned' => 'Gagné',
'overspent' => 'Overspent',
'left' => 'Gauche',
'no_budget' => '(pas de budget)',
- 'maxAmount' => 'Montant maximum',
- 'minAmount' => 'Montant minimum',
- 'billEntry' => 'Current bill entry',
+ 'max-amount' => 'Maximum amount',
+ 'min-amount' => 'Minumum amount',
+ 'journal-amount' => 'Current bill entry',
'name' => 'Nom',
'date' => 'Date',
'paid' => 'Payé',
@@ -825,6 +836,7 @@ return [
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email',
+ 'edit_user' => 'Edit user :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.',
'user_data_information' => 'User data',
'user_information' => 'User information',
@@ -838,6 +850,17 @@ return [
'configuration_updated' => 'The configuration has been updated',
'setting_is_demo_site' => 'Demo site',
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
+ 'setting_send_email_notifications' => 'Send email notifications',
+ 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.',
+ 'mail_for_lockout_help' => 'When a user is locked out',
+ 'mail_for_blocked_domain_help' => 'When a user tries to register using a blocked domain',
+ 'mail_for_blocked_email_help' => 'When a user tries to register using a blocked email address',
+ 'mail_for_bad_login_help' => 'When a user fails to login',
+ 'mail_for_blocked_login_help' => 'When a blocked user tries to login',
+ 'block_code_bounced' => 'Email message(s) bounced',
+ 'block_code_expired' => 'Demo account expired',
+ 'no_block_code' => 'No reason for block or user not blocked',
+
// split a transaction:
'transaction_meta_data' => 'Transaction meta-data',
@@ -911,4 +934,10 @@ return [
'import_finished_link' => 'The transactions imported can be found in tag :tag.',
'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks',
'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.',
+ 'bread_crumb_import_complete' => 'Import ":key" complete',
+ 'bread_crumb_configure_import' => 'Configure import ":key"',
+ 'bread_crumb_import_finished' => 'Import ":key" finished',
+ 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.',
+ 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".',
+ 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.',
];
\ No newline at end of file
diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php
index b0d37f901b..ec5aca8d95 100644
--- a/resources/lang/fr_FR/form.php
+++ b/resources/lang/fr_FR/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'La seule opération liée à cette catégorie ne sera pas supprimée.|Les :count opérations liées à cette catégorie ne seront pas supprimées.',
'tag_keep_transactions' => 'La seule opération liée à ce tag ne sera pas supprimée.|Les :count opérations liées à ce tag ne seront pas supprimées.',
+ 'email' => 'Email address',
+ 'password' => 'Password',
+ 'password_confirmation' => 'Password (again)',
+ 'blocked' => 'Is blocked?',
+ 'blocked_code' => 'Reason for block',
+
+
// admin
- 'domain' => 'Domaine',
- 'single_user_mode' => 'Mode utilisateur unique',
- 'must_confirm_account' => 'New users must activate account',
- 'is_demo_site' => 'Is demo site',
+ 'domain' => 'Domaine',
+ 'single_user_mode' => 'Mode utilisateur unique',
+ 'must_confirm_account' => 'New users must activate account',
+ 'is_demo_site' => 'Is demo site',
+ 'mail_for_lockout' => 'Locked out',
+ 'mail_for_blocked_domain' => 'Blocked domain',
+ 'mail_for_blocked_email' => 'Blocked email address',
+ 'mail_for_bad_login' => 'Login failure',
+ 'mail_for_blocked_login' => 'Blocked user',
+
// import
- 'import_file' => 'Fichier à importer',
- 'configuration_file' => 'Fichier de configuration',
- 'import_file_type' => 'Type de fichier de configuration',
- 'csv_comma' => 'Une virgule (,)',
- 'csv_semicolon' => 'Un point-virgule (;)',
- 'csv_tab' => 'Un onglet (invisible)',
- 'csv_delimiter' => 'Délimiteur de champ CSV',
- 'csv_import_account' => 'Compte d’importation par défaut',
- 'csv_config' => 'Configuration d\'importation CSV',
+ 'import_file' => 'Fichier à importer',
+ 'configuration_file' => 'Fichier de configuration',
+ 'import_file_type' => 'Type de fichier de configuration',
+ 'csv_comma' => 'Une virgule (,)',
+ 'csv_semicolon' => 'Un point-virgule (;)',
+ 'csv_tab' => 'Un onglet (invisible)',
+ 'csv_delimiter' => 'Délimiteur de champ CSV',
+ 'csv_import_account' => 'Compte d’importation par défaut',
+ 'csv_config' => 'Configuration d\'importation CSV',
'due_date' => 'Due date',
diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php
index 18a99031d3..55aba8cc1d 100644
--- a/resources/lang/fr_FR/list.php
+++ b/resources/lang/fr_FR/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => 'Boutons',
'icon' => 'Icône',
+ 'id' => 'ID',
'create_date' => 'Créé le',
'update_date' => 'Mis à jour le',
'balance_before' => 'Solde avant',
@@ -76,7 +77,7 @@ return [
'destination_account' => 'Destination account',
'accounts_count' => 'Number of accounts',
- 'journals_count' => 'Number of journals',
+ 'journals_count' => 'Number of transactions',
'attachments_count' => 'Number of attachments',
'bills_count' => 'Number of bills',
'categories_count' => 'Number of categories',
diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php
index 11f910cddc..ff4971ddd3 100644
--- a/resources/lang/fr_FR/validation.php
+++ b/resources/lang/fr_FR/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => 'Il ne s\'agit pas d\'un IBAN valide.',
'unique_account_number_for_user' => 'Il semble que ce numéro de compte est déjà utilisé.',
+ 'deleted_user' => 'Due to security constraints, you cannot register using this email address.',
'rule_trigger_value' => 'Cette valeur n’est pas valide pour le déclencheur sélectionné.',
'rule_action_value' => 'Cette valeur n’est pas valide pour l’action sélectionnée.',
'invalid_domain' => 'Compte tenu des contraintes de sécurité, vous ne pouvez pas vous enregistrer depuis ce domaine.',
diff --git a/resources/lang/hr_HR/firefly.php b/resources/lang/hr_HR/firefly.php
index 377f4c2ca3..7d587786c5 100644
--- a/resources/lang/hr_HR/firefly.php
+++ b/resources/lang/hr_HR/firefly.php
@@ -235,8 +235,8 @@ return [
'rule_trigger_description_ends_choice' => 'Description ends with..',
'rule_trigger_description_contains_choice' => 'Description contains..',
'rule_trigger_description_is_choice' => 'Description is..',
- 'rule_trigger_store_journal' => 'When a journal is created',
- 'rule_trigger_update_journal' => 'When a journal is updated',
+ 'rule_trigger_store_journal' => 'When a transaction is created',
+ 'rule_trigger_update_journal' => 'When a transaction is updated',
'rule_action_set_category' => 'Set category to ":action_value"',
'rule_action_clear_category' => 'Clear category',
'rule_action_set_budget' => 'Set budget to ":action_value"',
@@ -525,6 +525,8 @@ return [
'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.',
'select_more_than_one_account' => 'Please select more than one account',
'select_more_than_one_category' => 'Please select more than one category',
+ 'select_more_than_one_budget' => 'Please select more than one budget',
+ 'from_to' => 'From :start to :end',
// categories:
'new_category' => 'New category',
@@ -633,6 +635,7 @@ return [
'report_default' => 'Default financial report for :start until :end',
'report_audit' => 'Transaction history overview for :start until :end',
'report_category' => 'Category report for :start until :end',
+ 'report_budget' => 'Budget report for :start until :end',
'quick_link_reports' => 'Quick links',
'quick_link_default_report' => 'Default financial report',
'quick_link_audit_report' => 'Transaction history overview',
@@ -679,6 +682,7 @@ return [
'report_type_default' => 'Default financial report',
'report_type_audit' => 'Transaction history overview (audit)',
'report_type_category' => 'Category report',
+ 'report_type_budget' => 'Budget report',
'report_type_meta-history' => 'Categories, budgets and bills overview',
'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.',
'report_included_accounts' => 'Included accounts',
@@ -698,11 +702,14 @@ return [
'reports_submit' => 'View report',
'end_after_start_date' => 'End date of report must be after start date.',
'select_category' => 'Select one or more categories.',
+ 'select_budget' => 'Select one or more budgets.',
'income_per_category' => 'Income per category',
'expense_per_category' => 'Expense per category',
+ 'expense_per_budget' => 'Expense per budget',
'income_per_account' => 'Income per account',
'expense_per_account' => 'Expense per account',
- 'include_not_in_category' => 'Include transactions not selected for this report',
+ 'include_not_in_category' => 'Include categories not selected for this report',
+ 'include_not_in_budget' => 'Include budgets not selected for this report',
'everything_else' => 'Everything else',
'income_and_expenses' => 'Income and expenses',
'spent_average' => 'Spent (average)',
@@ -712,6 +719,8 @@ return [
'average_income_per_account' => 'Average income per account',
'total' => 'Total',
'description' => 'Description',
+ 'sum_of_period' => 'Sum of period',
+ 'average_in_period' => 'Average in period',
// charts:
@@ -720,13 +729,15 @@ return [
'month' => 'Month',
'budget' => 'Budget',
'spent' => 'Spent',
+ 'spent_in_budget' => 'Spent in budget',
+ 'left_to_spend' => 'Left to spend',
'earned' => 'Earned',
'overspent' => 'Overspent',
'left' => 'Left',
'no_budget' => '(no budget)',
- 'maxAmount' => 'Maximum amount',
- 'minAmount' => 'Minumum amount',
- 'billEntry' => 'Current bill entry',
+ 'max-amount' => 'Maximum amount',
+ 'min-amount' => 'Minumum amount',
+ 'journal-amount' => 'Current bill entry',
'name' => 'Name',
'date' => 'Date',
'paid' => 'Paid',
@@ -825,6 +836,7 @@ return [
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email',
+ 'edit_user' => 'Edit user :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.',
'user_data_information' => 'User data',
'user_information' => 'User information',
@@ -838,6 +850,17 @@ return [
'configuration_updated' => 'The configuration has been updated',
'setting_is_demo_site' => 'Demo site',
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
+ 'setting_send_email_notifications' => 'Send email notifications',
+ 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.',
+ 'mail_for_lockout_help' => 'When a user is locked out',
+ 'mail_for_blocked_domain_help' => 'When a user tries to register using a blocked domain',
+ 'mail_for_blocked_email_help' => 'When a user tries to register using a blocked email address',
+ 'mail_for_bad_login_help' => 'When a user fails to login',
+ 'mail_for_blocked_login_help' => 'When a blocked user tries to login',
+ 'block_code_bounced' => 'Email message(s) bounced',
+ 'block_code_expired' => 'Demo account expired',
+ 'no_block_code' => 'No reason for block or user not blocked',
+
// split a transaction:
'transaction_meta_data' => 'Transaction meta-data',
@@ -911,4 +934,10 @@ return [
'import_finished_link' => 'The transactions imported can be found in tag :tag.',
'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks',
'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.',
+ 'bread_crumb_import_complete' => 'Import ":key" complete',
+ 'bread_crumb_configure_import' => 'Configure import ":key"',
+ 'bread_crumb_import_finished' => 'Import ":key" finished',
+ 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.',
+ 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".',
+ 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.',
];
\ No newline at end of file
diff --git a/resources/lang/hr_HR/form.php b/resources/lang/hr_HR/form.php
index 9e644c52f6..af698dcb73 100644
--- a/resources/lang/hr_HR/form.php
+++ b/resources/lang/hr_HR/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+ 'email' => 'Email address',
+ 'password' => 'Password',
+ 'password_confirmation' => 'Password (again)',
+ 'blocked' => 'Is blocked?',
+ 'blocked_code' => 'Reason for block',
+
+
// admin
- 'domain' => 'Domain',
- 'single_user_mode' => 'Single user mode',
- 'must_confirm_account' => 'New users must activate account',
- 'is_demo_site' => 'Is demo site',
+ 'domain' => 'Domain',
+ 'single_user_mode' => 'Single user mode',
+ 'must_confirm_account' => 'New users must activate account',
+ 'is_demo_site' => 'Is demo site',
+ 'mail_for_lockout' => 'Locked out',
+ 'mail_for_blocked_domain' => 'Blocked domain',
+ 'mail_for_blocked_email' => 'Blocked email address',
+ 'mail_for_bad_login' => 'Login failure',
+ 'mail_for_blocked_login' => 'Blocked user',
+
// import
- 'import_file' => 'Import file',
- 'configuration_file' => 'Configuration file',
- 'import_file_type' => 'Import file type',
- 'csv_comma' => 'A comma (,)',
- 'csv_semicolon' => 'A semicolon (;)',
- 'csv_tab' => 'A tab (invisible)',
- 'csv_delimiter' => 'CSV field delimiter',
- 'csv_import_account' => 'Default import account',
- 'csv_config' => 'CSV import configuration',
+ 'import_file' => 'Import file',
+ 'configuration_file' => 'Configuration file',
+ 'import_file_type' => 'Import file type',
+ 'csv_comma' => 'A comma (,)',
+ 'csv_semicolon' => 'A semicolon (;)',
+ 'csv_tab' => 'A tab (invisible)',
+ 'csv_delimiter' => 'CSV field delimiter',
+ 'csv_import_account' => 'Default import account',
+ 'csv_config' => 'CSV import configuration',
'due_date' => 'Due date',
diff --git a/resources/lang/hr_HR/list.php b/resources/lang/hr_HR/list.php
index 953f3f1838..90625d54e6 100644
--- a/resources/lang/hr_HR/list.php
+++ b/resources/lang/hr_HR/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => 'Buttons',
'icon' => 'Icon',
+ 'id' => 'ID',
'create_date' => 'Created at',
'update_date' => 'Updated at',
'balance_before' => 'Balance before',
@@ -76,7 +77,7 @@ return [
'destination_account' => 'Destination account',
'accounts_count' => 'Number of accounts',
- 'journals_count' => 'Number of journals',
+ 'journals_count' => 'Number of transactions',
'attachments_count' => 'Number of attachments',
'bills_count' => 'Number of bills',
'categories_count' => 'Number of categories',
diff --git a/resources/lang/hr_HR/validation.php b/resources/lang/hr_HR/validation.php
index b391855829..6d412f04fe 100644
--- a/resources/lang/hr_HR/validation.php
+++ b/resources/lang/hr_HR/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => 'This is not a valid IBAN.',
'unique_account_number_for_user' => 'It looks like this account number is already in use.',
+ 'deleted_user' => 'Due to security constraints, you cannot register using this email address.',
'rule_trigger_value' => 'This value is invalid for the selected trigger.',
'rule_action_value' => 'This value is invalid for the selected action.',
'invalid_domain' => 'Due to security constraints, you cannot register from this domain.',
diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php
index 133343784a..a2abe3ea25 100644
--- a/resources/lang/nl_NL/firefly.php
+++ b/resources/lang/nl_NL/firefly.php
@@ -236,7 +236,7 @@ return [
'rule_trigger_description_contains_choice' => 'Omschrijving bevat..',
'rule_trigger_description_is_choice' => 'Omschrijving is..',
'rule_trigger_store_journal' => 'Als een transactie wordt gemaakt',
- 'rule_trigger_update_journal' => 'Als een transactie wordt bewerkt',
+ 'rule_trigger_update_journal' => 'Als een transactie wordt bijgewerkt',
'rule_action_set_category' => 'Verander categorie naar ":action_value"',
'rule_action_clear_category' => 'Maak categorie-veld leeg',
'rule_action_set_budget' => 'Sla op onder budget ":action_value"',
@@ -426,7 +426,7 @@ return [
'deleted_currency' => 'Valuta :name verwijderd',
'created_currency' => 'Nieuwe valuta :name opgeslagen',
'updated_currency' => 'Valuta :name bijgewerkt',
- 'ask_site_owner' => 'Vraag :owner of deze valuta wilt toevoegen, verwijderen of wijzigen.',
+ 'ask_site_owner' => 'Vraag :site_owner of deze valuta wilt toevoegen, verwijderen of wijzigen.',
'currencies_intro' => 'Firefly III ondersteunt diverse valuta die je hier kan instellen en bewerken.',
'make_default_currency' => 'maak standaard',
'default_currency' => 'standaard',
@@ -525,6 +525,8 @@ return [
'no_data_for_chart' => 'Er is (nog) niet genoeg informatie om deze grafiek te tekenen.',
'select_more_than_one_account' => 'Selecteer meer dan één rekening',
'select_more_than_one_category' => 'Selecteer meer dan één categorie',
+ 'select_more_than_one_budget' => 'Selecteer meer dan één budget',
+ 'from_to' => 'Van :start tot en met :end',
// categories:
'new_category' => 'Nieuwe categorie',
@@ -633,6 +635,7 @@ return [
'report_default' => 'Standaard financieel rapport (:start tot :end)',
'report_audit' => 'Transactiehistorie-overzicht van :start tot :end',
'report_category' => 'Categorierapport van :start tot :end',
+ 'report_budget' => 'Budgetrapport van :start tot en met :end',
'quick_link_reports' => 'Snelle links',
'quick_link_default_report' => 'Standaard financieel rapport',
'quick_link_audit_report' => 'Transactiehistorie-overzicht',
@@ -679,6 +682,7 @@ return [
'report_type_default' => 'Standard financieel rapport',
'report_type_audit' => 'Transactiehistorie-overzicht (audit)',
'report_type_category' => 'Categorierapport',
+ 'report_type_budget' => 'Budgetrapport',
'report_type_meta-history' => 'Overzicht van categorieën, budgetten en contracten',
'more_info_help' => 'Meer informatie over deze rapporten vind je in de hulppagina\'s. Klik daarvoor op het (?) icoontje rechtsboven.',
'report_included_accounts' => 'Accounts in rapport',
@@ -698,11 +702,14 @@ return [
'reports_submit' => 'Bekijk overzicht',
'end_after_start_date' => 'Einddatum moet na begindatum liggen.',
'select_category' => 'Selecteer minstens één categorie.',
+ 'select_budget' => 'Selecteer minstens één budget.',
'income_per_category' => 'Inkomen per categorie',
'expense_per_category' => 'Uitgaven per categorie',
+ 'expense_per_budget' => 'Uitgaven per budget',
'income_per_account' => 'Inkomen per rekening',
'expense_per_account' => 'Uitgaven per rekening',
- 'include_not_in_category' => 'Laat ook transacties buiten deze categorieën zien',
+ 'include_not_in_category' => 'Laat ook categoriën zien van buiten dit rapport',
+ 'include_not_in_budget' => 'Laat ook budgetten zien van buiten dit rapport',
'everything_else' => 'De rest',
'income_and_expenses' => 'Inkomsten en uitgaven',
'spent_average' => 'Uitgegeven (gemiddeld)',
@@ -712,6 +719,8 @@ return [
'average_income_per_account' => 'Gemiddeld verdiend per rekening',
'total' => 'Totaal',
'description' => 'Omschrijving',
+ 'sum_of_period' => 'Som van periode',
+ 'average_in_period' => 'Gemiddelde in periode',
// charts:
@@ -720,13 +729,15 @@ return [
'month' => 'Maand',
'budget' => 'Budget',
'spent' => 'Uitgegeven',
+ 'spent_in_budget' => 'Uitgegeven in budget',
+ 'left_to_spend' => 'Over om uit te geven',
'earned' => 'Verdiend',
'overspent' => 'Teveel uitgegeven',
'left' => 'Over',
'no_budget' => '(geen budget)',
- 'maxAmount' => 'Maximaal bedrag',
- 'minAmount' => 'Minimaal bedrag',
- 'billEntry' => 'Bedrag voor dit contract',
+ 'max-amount' => 'Maximumbedrag',
+ 'min-amount' => 'Minimumbedrag',
+ 'journal-amount' => 'Bedrag voor dit contract',
'name' => 'Naam',
'date' => 'Datum',
'paid' => 'Betaald',
@@ -825,6 +836,7 @@ return [
'setting_single_user_mode_explain' => 'Standaard accepteert Firefly III maar één (1) gebruiker: jijzelf. Dit is een veiligheidsmaatregel, zodat anderen niet zomaar jouw installatie kunnen gebruiken, tenzij je dit aanzet. Toekomstige registraties zijn nu geblokkeerd. Als je dit vinkje uitzet kunnen anderen jouw installatie ook gebruiken, gegeven dat ze er bij kunnen (je installatie hangt aan het internet).',
'store_configuration' => 'Configuratie opslaan',
'single_user_administration' => 'Gebruikersadministratie voor :email',
+ 'edit_user' => 'Wijzig gebruiker :email',
'hidden_fields_preferences' => 'Niet alle velden zijn zichtbaar. Zet ze aan in je instellingen.',
'user_data_information' => 'Gebruikersgegevens',
'user_information' => 'Gebruikersinformatie',
@@ -838,6 +850,17 @@ return [
'configuration_updated' => 'De configuratie is bijgewerkt',
'setting_is_demo_site' => 'Demo website',
'setting_is_demo_site_explain' => 'Als je dit aanzet doet jouw installatie alsof het een demo-site is, en dat kan problemen opleveren.',
+ 'setting_send_email_notifications' => 'Stuur e-mail notificaties',
+ 'setting_send_email_explain' => 'Firefly III kan je email notificaties sturen over allerhande zaken. Deze worden gestuurd naar :site_owner. Dat e-mailadres kan je instellen in het .env-bestand.',
+ 'mail_for_lockout_help' => 'Als een user tijdelijk niet mag inloggen',
+ 'mail_for_blocked_domain_help' => 'Als een gebruiker wil registreren met een geblokkeerd domein',
+ 'mail_for_blocked_email_help' => 'Als een gebruiker wil registreren met een geblokkeerd e-mailadres',
+ 'mail_for_bad_login_help' => 'Als een user met de verkeerde gegevens probeert in te loggen',
+ 'mail_for_blocked_login_help' => 'Als een geblokkeerde gebruiker probeert in te loggen',
+ 'block_code_bounced' => 'Email kwam niet aan',
+ 'block_code_expired' => 'Demo-account verlopen',
+ 'no_block_code' => 'Geen reden of gebruiker niet geblokkeerd',
+
// split a transaction:
'transaction_meta_data' => 'Transactie meta-data',
@@ -911,4 +934,10 @@ return [
'import_finished_link' => 'De geimporteerde transacties kan je vinden onder tag :tag.',
'need_at_least_one_account' => 'Je moet minstens één betaalrekening hebben voor je spaarpotjes kan maken',
'see_help_top_right' => 'Meer informatie vind je in de help pagina\'s. Gebruik daarvoor het icoontje rechtsboven in de hoek.',
+ 'bread_crumb_import_complete' => 'Import ":key" compleet',
+ 'bread_crumb_configure_import' => 'Import ":key" instellen',
+ 'bread_crumb_import_finished' => 'Import ":key" klaar',
+ 'import_finished_intro' => 'De import is klaar! Je kan de transacties nu terugvinden in Firefly.',
+ 'import_finished_text_without_link' => 'Er is geen tag die al je transacties bevat. Kijk links in het menu onder "Transacties" en zoek daar je nieuwe transacties op.',
+ 'import_finished_text_with_link' => 'Je kan je geïmporteerde transacties op deze pagina terug vinden.',
];
\ No newline at end of file
diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php
index f2ec1de019..8119fed791 100644
--- a/resources/lang/nl_NL/form.php
+++ b/resources/lang/nl_NL/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'De transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.',
'tag_keep_transactions' => 'De transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.',
+ 'email' => 'E-mailadres',
+ 'password' => 'Wachtwoord',
+ 'password_confirmation' => 'Wachtwoord (nogmaals)',
+ 'blocked' => 'Is geblokkeerd?',
+ 'blocked_code' => 'Reden voor blokkade',
+
+
// admin
- 'domain' => 'Domein',
- 'single_user_mode' => 'Enkele gebruiker-modus',
- 'must_confirm_account' => 'Nieuwe gebruikers moeten hun account activeren',
- 'is_demo_site' => 'Is demo website',
+ 'domain' => 'Domein',
+ 'single_user_mode' => 'Enkele gebruiker-modus',
+ 'must_confirm_account' => 'Nieuwe gebruikers moeten hun account activeren',
+ 'is_demo_site' => 'Is demo website',
+ 'mail_for_lockout' => 'Tijdelijk niet inloggen',
+ 'mail_for_blocked_domain' => 'Geblokkeerd domein',
+ 'mail_for_blocked_email' => 'Geblokkeerd e-mailadres',
+ 'mail_for_bad_login' => 'Mislukte login',
+ 'mail_for_blocked_login' => 'Geblokkeerde gebruiker',
+
// import
- 'import_file' => 'Importbestand',
- 'configuration_file' => 'Configuratiebestand',
- 'import_file_type' => 'Importbestandstype',
- 'csv_comma' => 'Een komma (,)',
- 'csv_semicolon' => 'Een puntkomma (;)',
- 'csv_tab' => 'Een tab (onzichtbaar)',
- 'csv_delimiter' => 'CSV scheidingsteken',
- 'csv_import_account' => 'Standaard rekening voor importeren',
- 'csv_config' => 'Configuratiebestand',
+ 'import_file' => 'Importbestand',
+ 'configuration_file' => 'Configuratiebestand',
+ 'import_file_type' => 'Importbestandstype',
+ 'csv_comma' => 'Een komma (,)',
+ 'csv_semicolon' => 'Een puntkomma (;)',
+ 'csv_tab' => 'Een tab (onzichtbaar)',
+ 'csv_delimiter' => 'CSV scheidingsteken',
+ 'csv_import_account' => 'Standaard rekening voor importeren',
+ 'csv_config' => 'Configuratiebestand',
'due_date' => 'Vervaldatum',
diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php
index ced437cc40..95efafc5d6 100644
--- a/resources/lang/nl_NL/list.php
+++ b/resources/lang/nl_NL/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => 'Knoppen',
'icon' => 'Icoon',
+ 'id' => 'ID',
'create_date' => 'Aangemaakt op',
'update_date' => 'Bijgewerkt op',
'balance_before' => 'Saldo voor',
diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php
index a32fb3024c..2aa1a9ab5f 100644
--- a/resources/lang/nl_NL/validation.php
+++ b/resources/lang/nl_NL/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => 'Dit is niet een geldige IBAN.',
'unique_account_number_for_user' => 'Het lijkt erop dat dit rekeningnummer al in gebruik is.',
+ 'deleted_user' => 'Je kan je niet registreren met dit e-mailadres.',
'rule_trigger_value' => 'Deze waarde is niet geldig voor de geselecteerde trigger.',
'rule_action_value' => 'Deze waarde is niet geldig voor de geselecteerde actie.',
'invalid_domain' => 'Kan niet registereren vanaf dit domein.',
diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php
index 5368d3515d..71fb3492b6 100644
--- a/resources/lang/pt_BR/firefly.php
+++ b/resources/lang/pt_BR/firefly.php
@@ -235,8 +235,8 @@ return [
'rule_trigger_description_ends_choice' => 'Descrição termina com..',
'rule_trigger_description_contains_choice' => 'Descrição contém..',
'rule_trigger_description_is_choice' => 'Descrição é..',
- 'rule_trigger_store_journal' => 'Quando um período é criado',
- 'rule_trigger_update_journal' => 'Quando um período é atualizado',
+ 'rule_trigger_store_journal' => 'When a transaction is created',
+ 'rule_trigger_update_journal' => 'When a transaction is updated',
'rule_action_set_category' => 'Definir categoria para ":action_value"',
'rule_action_clear_category' => 'Limpar categoria',
'rule_action_set_budget' => 'Definir orçamento para ":action_value"',
@@ -525,6 +525,8 @@ return [
'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.',
'select_more_than_one_account' => 'Por favor, selecione mais de uma conta',
'select_more_than_one_category' => 'Por favor, selecione mais de uma categoria',
+ 'select_more_than_one_budget' => 'Please select more than one budget',
+ 'from_to' => 'From :start to :end',
// categories:
'new_category' => 'Nova categoria',
@@ -633,6 +635,7 @@ return [
'report_default' => 'Relatório financeiro padrão de :start até :end',
'report_audit' => 'Visão geral do histórico de transação de :start até :end',
'report_category' => 'Category report for :start until :end',
+ 'report_budget' => 'Budget report for :start until :end',
'quick_link_reports' => 'Ligações rápidas',
'quick_link_default_report' => 'Relatório financeiro padrão',
'quick_link_audit_report' => 'Visão geral do histórico de transação',
@@ -679,6 +682,7 @@ return [
'report_type_default' => 'Relatório financeiro padrão',
'report_type_audit' => 'Visão geral do histórico de transação (auditoria)',
'report_type_category' => 'Relatório por Categorias',
+ 'report_type_budget' => 'Budget report',
'report_type_meta-history' => 'Visão geral de categorias, orçamentos e faturas',
'more_info_help' => 'Mais informações sobre esses tipos de relatórios podem ser encontradas nas páginas de ajuda. Pressione o ícone (?) no canto superior direito.',
'report_included_accounts' => 'Contas incluídas',
@@ -698,11 +702,14 @@ return [
'reports_submit' => 'Visualizar relatório',
'end_after_start_date' => 'Data de término do relatório deve ser depois da data de início.',
'select_category' => 'Selecione uma ou mais categorias.',
+ 'select_budget' => 'Select one or more budgets.',
'income_per_category' => 'Receitas por categoria',
'expense_per_category' => 'Despesa por categoria',
+ 'expense_per_budget' => 'Expense per budget',
'income_per_account' => 'Rendimento por conta',
'expense_per_account' => 'Por conta de despesas',
- 'include_not_in_category' => 'Incluir transações não selecionadas para este relatório',
+ 'include_not_in_category' => 'Include categories not selected for this report',
+ 'include_not_in_budget' => 'Include budgets not selected for this report',
'everything_else' => 'Todo o resto',
'income_and_expenses' => 'Receitas e despesas',
'spent_average' => 'Gastos (média)',
@@ -712,6 +719,8 @@ return [
'average_income_per_account' => 'Rendimento médio por conta',
'total' => 'Total',
'description' => 'Descrição',
+ 'sum_of_period' => 'Sum of period',
+ 'average_in_period' => 'Average in period',
// charts:
@@ -720,13 +729,15 @@ return [
'month' => 'Mês',
'budget' => 'Orçamento',
'spent' => 'Gasto',
+ 'spent_in_budget' => 'Spent in budget',
+ 'left_to_spend' => 'Left to spend',
'earned' => 'Ganho',
'overspent' => 'Gasto excedido',
'left' => 'Esquerda',
'no_budget' => '(sem orçamento)',
- 'maxAmount' => 'Valor Máximo',
- 'minAmount' => 'Valor Mínimo',
- 'billEntry' => 'Entrada de fatura atual',
+ 'max-amount' => 'Maximum amount',
+ 'min-amount' => 'Minumum amount',
+ 'journal-amount' => 'Current bill entry',
'name' => 'Nome',
'date' => 'Data',
'paid' => 'Pago',
@@ -825,6 +836,7 @@ return [
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Configuração da Loja',
'single_user_administration' => 'User administration for :email',
+ 'edit_user' => 'Edit user :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.',
'user_data_information' => 'Dados de usuário',
'user_information' => 'Informações do usuário',
@@ -838,6 +850,17 @@ return [
'configuration_updated' => 'A configuração foi atualizada',
'setting_is_demo_site' => 'Site demo',
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
+ 'setting_send_email_notifications' => 'Send email notifications',
+ 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.',
+ 'mail_for_lockout_help' => 'When a user is locked out',
+ 'mail_for_blocked_domain_help' => 'When a user tries to register using a blocked domain',
+ 'mail_for_blocked_email_help' => 'When a user tries to register using a blocked email address',
+ 'mail_for_bad_login_help' => 'When a user fails to login',
+ 'mail_for_blocked_login_help' => 'When a blocked user tries to login',
+ 'block_code_bounced' => 'Email message(s) bounced',
+ 'block_code_expired' => 'Demo account expired',
+ 'no_block_code' => 'No reason for block or user not blocked',
+
// split a transaction:
'transaction_meta_data' => 'Dados de transação',
@@ -911,4 +934,10 @@ return [
'import_finished_link' => 'The transactions imported can be found in tag :tag.',
'need_at_least_one_account' => 'Você precisa de pelo menos uma conta de ativo para ser capaz de criar cofrinhos',
'see_help_top_right' => 'Para obter mais informações, por favor, confira as páginas de ajuda usando o ícone no canto superior direito da página.',
+ 'bread_crumb_import_complete' => 'Import ":key" complete',
+ 'bread_crumb_configure_import' => 'Configure import ":key"',
+ 'bread_crumb_import_finished' => 'Import ":key" finished',
+ 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.',
+ 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".',
+ 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.',
];
\ No newline at end of file
diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php
index 1b24d4419f..b9e54eeeaf 100644
--- a/resources/lang/pt_BR/form.php
+++ b/resources/lang/pt_BR/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'A única transação ligada a esta categoria não será excluída.|Todos :count transações ligadas a esta categoria não serão excluídos.',
'tag_keep_transactions' => 'A única transação ligada a essa marca não será excluída.|Todos :count transações ligadas a essa marca não serão excluídos.',
+ 'email' => 'Email address',
+ 'password' => 'Password',
+ 'password_confirmation' => 'Password (again)',
+ 'blocked' => 'Is blocked?',
+ 'blocked_code' => 'Reason for block',
+
+
// admin
- 'domain' => 'Domínio',
- 'single_user_mode' => 'Modo de usuário único',
- 'must_confirm_account' => 'Novos usuários devem ativar a conta',
- 'is_demo_site' => 'É o site de demonstração',
+ 'domain' => 'Domínio',
+ 'single_user_mode' => 'Modo de usuário único',
+ 'must_confirm_account' => 'Novos usuários devem ativar a conta',
+ 'is_demo_site' => 'É o site de demonstração',
+ 'mail_for_lockout' => 'Locked out',
+ 'mail_for_blocked_domain' => 'Blocked domain',
+ 'mail_for_blocked_email' => 'Blocked email address',
+ 'mail_for_bad_login' => 'Login failure',
+ 'mail_for_blocked_login' => 'Blocked user',
+
// import
- 'import_file' => 'Importar arquivo',
- 'configuration_file' => 'Arquivo de configuração',
- 'import_file_type' => 'Tipo de arquivo de importação',
- 'csv_comma' => 'Uma vírgula (,)',
- 'csv_semicolon' => 'Um ponto e vírgula (;)',
- 'csv_tab' => 'Um Tab (invisível)',
- 'csv_delimiter' => 'Delimitador de campo CSV',
- 'csv_import_account' => 'Conta de importação padrão',
- 'csv_config' => 'Importar CSV de configuração',
+ 'import_file' => 'Importar arquivo',
+ 'configuration_file' => 'Arquivo de configuração',
+ 'import_file_type' => 'Tipo de arquivo de importação',
+ 'csv_comma' => 'Uma vírgula (,)',
+ 'csv_semicolon' => 'Um ponto e vírgula (;)',
+ 'csv_tab' => 'Um Tab (invisível)',
+ 'csv_delimiter' => 'Delimitador de campo CSV',
+ 'csv_import_account' => 'Conta de importação padrão',
+ 'csv_config' => 'Importar CSV de configuração',
'due_date' => 'Data de vencimento',
diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php
index 5b31acdaaa..4cc4bbbd49 100644
--- a/resources/lang/pt_BR/list.php
+++ b/resources/lang/pt_BR/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => 'Botões',
'icon' => 'Ícone',
+ 'id' => 'ID',
'create_date' => 'Criado em',
'update_date' => 'Atualizado em',
'balance_before' => 'Saldo Antes',
@@ -76,7 +77,7 @@ return [
'destination_account' => 'Conta de destino',
'accounts_count' => 'Número de Contas',
- 'journals_count' => 'Número de período',
+ 'journals_count' => 'Number of transactions',
'attachments_count' => 'Número de anexos',
'bills_count' => 'Número de contas',
'categories_count' => 'Número de categorias',
diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php
index fa6003e890..8d4c63e854 100644
--- a/resources/lang/pt_BR/validation.php
+++ b/resources/lang/pt_BR/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => 'Este não é um válido IBAN.',
'unique_account_number_for_user' => 'Parece que este número de conta já está em uso.',
+ 'deleted_user' => 'Due to security constraints, you cannot register using this email address.',
'rule_trigger_value' => 'Este valor é inválido para o disparo selecionado.',
'rule_action_value' => 'Este valor é inválido para a ação selecionada.',
'invalid_domain' => 'Devido a restrições de segurança, você não pode registrar deste domínio.',
diff --git a/resources/lang/zh_HK/firefly.php b/resources/lang/zh_HK/firefly.php
index 377f4c2ca3..7d587786c5 100644
--- a/resources/lang/zh_HK/firefly.php
+++ b/resources/lang/zh_HK/firefly.php
@@ -235,8 +235,8 @@ return [
'rule_trigger_description_ends_choice' => 'Description ends with..',
'rule_trigger_description_contains_choice' => 'Description contains..',
'rule_trigger_description_is_choice' => 'Description is..',
- 'rule_trigger_store_journal' => 'When a journal is created',
- 'rule_trigger_update_journal' => 'When a journal is updated',
+ 'rule_trigger_store_journal' => 'When a transaction is created',
+ 'rule_trigger_update_journal' => 'When a transaction is updated',
'rule_action_set_category' => 'Set category to ":action_value"',
'rule_action_clear_category' => 'Clear category',
'rule_action_set_budget' => 'Set budget to ":action_value"',
@@ -525,6 +525,8 @@ return [
'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.',
'select_more_than_one_account' => 'Please select more than one account',
'select_more_than_one_category' => 'Please select more than one category',
+ 'select_more_than_one_budget' => 'Please select more than one budget',
+ 'from_to' => 'From :start to :end',
// categories:
'new_category' => 'New category',
@@ -633,6 +635,7 @@ return [
'report_default' => 'Default financial report for :start until :end',
'report_audit' => 'Transaction history overview for :start until :end',
'report_category' => 'Category report for :start until :end',
+ 'report_budget' => 'Budget report for :start until :end',
'quick_link_reports' => 'Quick links',
'quick_link_default_report' => 'Default financial report',
'quick_link_audit_report' => 'Transaction history overview',
@@ -679,6 +682,7 @@ return [
'report_type_default' => 'Default financial report',
'report_type_audit' => 'Transaction history overview (audit)',
'report_type_category' => 'Category report',
+ 'report_type_budget' => 'Budget report',
'report_type_meta-history' => 'Categories, budgets and bills overview',
'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.',
'report_included_accounts' => 'Included accounts',
@@ -698,11 +702,14 @@ return [
'reports_submit' => 'View report',
'end_after_start_date' => 'End date of report must be after start date.',
'select_category' => 'Select one or more categories.',
+ 'select_budget' => 'Select one or more budgets.',
'income_per_category' => 'Income per category',
'expense_per_category' => 'Expense per category',
+ 'expense_per_budget' => 'Expense per budget',
'income_per_account' => 'Income per account',
'expense_per_account' => 'Expense per account',
- 'include_not_in_category' => 'Include transactions not selected for this report',
+ 'include_not_in_category' => 'Include categories not selected for this report',
+ 'include_not_in_budget' => 'Include budgets not selected for this report',
'everything_else' => 'Everything else',
'income_and_expenses' => 'Income and expenses',
'spent_average' => 'Spent (average)',
@@ -712,6 +719,8 @@ return [
'average_income_per_account' => 'Average income per account',
'total' => 'Total',
'description' => 'Description',
+ 'sum_of_period' => 'Sum of period',
+ 'average_in_period' => 'Average in period',
// charts:
@@ -720,13 +729,15 @@ return [
'month' => 'Month',
'budget' => 'Budget',
'spent' => 'Spent',
+ 'spent_in_budget' => 'Spent in budget',
+ 'left_to_spend' => 'Left to spend',
'earned' => 'Earned',
'overspent' => 'Overspent',
'left' => 'Left',
'no_budget' => '(no budget)',
- 'maxAmount' => 'Maximum amount',
- 'minAmount' => 'Minumum amount',
- 'billEntry' => 'Current bill entry',
+ 'max-amount' => 'Maximum amount',
+ 'min-amount' => 'Minumum amount',
+ 'journal-amount' => 'Current bill entry',
'name' => 'Name',
'date' => 'Date',
'paid' => 'Paid',
@@ -825,6 +836,7 @@ return [
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email',
+ 'edit_user' => 'Edit user :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.',
'user_data_information' => 'User data',
'user_information' => 'User information',
@@ -838,6 +850,17 @@ return [
'configuration_updated' => 'The configuration has been updated',
'setting_is_demo_site' => 'Demo site',
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
+ 'setting_send_email_notifications' => 'Send email notifications',
+ 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.',
+ 'mail_for_lockout_help' => 'When a user is locked out',
+ 'mail_for_blocked_domain_help' => 'When a user tries to register using a blocked domain',
+ 'mail_for_blocked_email_help' => 'When a user tries to register using a blocked email address',
+ 'mail_for_bad_login_help' => 'When a user fails to login',
+ 'mail_for_blocked_login_help' => 'When a blocked user tries to login',
+ 'block_code_bounced' => 'Email message(s) bounced',
+ 'block_code_expired' => 'Demo account expired',
+ 'no_block_code' => 'No reason for block or user not blocked',
+
// split a transaction:
'transaction_meta_data' => 'Transaction meta-data',
@@ -911,4 +934,10 @@ return [
'import_finished_link' => 'The transactions imported can be found in tag :tag.',
'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks',
'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.',
+ 'bread_crumb_import_complete' => 'Import ":key" complete',
+ 'bread_crumb_configure_import' => 'Configure import ":key"',
+ 'bread_crumb_import_finished' => 'Import ":key" finished',
+ 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.',
+ 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".',
+ 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.',
];
\ No newline at end of file
diff --git a/resources/lang/zh_HK/form.php b/resources/lang/zh_HK/form.php
index 9e644c52f6..af698dcb73 100644
--- a/resources/lang/zh_HK/form.php
+++ b/resources/lang/zh_HK/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+ 'email' => 'Email address',
+ 'password' => 'Password',
+ 'password_confirmation' => 'Password (again)',
+ 'blocked' => 'Is blocked?',
+ 'blocked_code' => 'Reason for block',
+
+
// admin
- 'domain' => 'Domain',
- 'single_user_mode' => 'Single user mode',
- 'must_confirm_account' => 'New users must activate account',
- 'is_demo_site' => 'Is demo site',
+ 'domain' => 'Domain',
+ 'single_user_mode' => 'Single user mode',
+ 'must_confirm_account' => 'New users must activate account',
+ 'is_demo_site' => 'Is demo site',
+ 'mail_for_lockout' => 'Locked out',
+ 'mail_for_blocked_domain' => 'Blocked domain',
+ 'mail_for_blocked_email' => 'Blocked email address',
+ 'mail_for_bad_login' => 'Login failure',
+ 'mail_for_blocked_login' => 'Blocked user',
+
// import
- 'import_file' => 'Import file',
- 'configuration_file' => 'Configuration file',
- 'import_file_type' => 'Import file type',
- 'csv_comma' => 'A comma (,)',
- 'csv_semicolon' => 'A semicolon (;)',
- 'csv_tab' => 'A tab (invisible)',
- 'csv_delimiter' => 'CSV field delimiter',
- 'csv_import_account' => 'Default import account',
- 'csv_config' => 'CSV import configuration',
+ 'import_file' => 'Import file',
+ 'configuration_file' => 'Configuration file',
+ 'import_file_type' => 'Import file type',
+ 'csv_comma' => 'A comma (,)',
+ 'csv_semicolon' => 'A semicolon (;)',
+ 'csv_tab' => 'A tab (invisible)',
+ 'csv_delimiter' => 'CSV field delimiter',
+ 'csv_import_account' => 'Default import account',
+ 'csv_config' => 'CSV import configuration',
'due_date' => 'Due date',
diff --git a/resources/lang/zh_HK/list.php b/resources/lang/zh_HK/list.php
index 953f3f1838..90625d54e6 100644
--- a/resources/lang/zh_HK/list.php
+++ b/resources/lang/zh_HK/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => 'Buttons',
'icon' => 'Icon',
+ 'id' => 'ID',
'create_date' => 'Created at',
'update_date' => 'Updated at',
'balance_before' => 'Balance before',
@@ -76,7 +77,7 @@ return [
'destination_account' => 'Destination account',
'accounts_count' => 'Number of accounts',
- 'journals_count' => 'Number of journals',
+ 'journals_count' => 'Number of transactions',
'attachments_count' => 'Number of attachments',
'bills_count' => 'Number of bills',
'categories_count' => 'Number of categories',
diff --git a/resources/lang/zh_HK/validation.php b/resources/lang/zh_HK/validation.php
index b391855829..6d412f04fe 100644
--- a/resources/lang/zh_HK/validation.php
+++ b/resources/lang/zh_HK/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => 'This is not a valid IBAN.',
'unique_account_number_for_user' => 'It looks like this account number is already in use.',
+ 'deleted_user' => 'Due to security constraints, you cannot register using this email address.',
'rule_trigger_value' => 'This value is invalid for the selected trigger.',
'rule_action_value' => 'This value is invalid for the selected action.',
'invalid_domain' => 'Due to security constraints, you cannot register from this domain.',
diff --git a/resources/lang/zh_TW/firefly.php b/resources/lang/zh_TW/firefly.php
index 9ed680fc3c..6e9d309c22 100644
--- a/resources/lang/zh_TW/firefly.php
+++ b/resources/lang/zh_TW/firefly.php
@@ -235,8 +235,8 @@ return [
'rule_trigger_description_ends_choice' => '描述以…結尾',
'rule_trigger_description_contains_choice' => '描述包含…',
'rule_trigger_description_is_choice' => '描述是…',
- 'rule_trigger_store_journal' => 'When a journal is created',
- 'rule_trigger_update_journal' => 'When a journal is updated',
+ 'rule_trigger_store_journal' => 'When a transaction is created',
+ 'rule_trigger_update_journal' => 'When a transaction is updated',
'rule_action_set_category' => 'Set category to ":action_value"',
'rule_action_clear_category' => 'Clear category',
'rule_action_set_budget' => 'Set budget to ":action_value"',
@@ -525,6 +525,8 @@ return [
'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.',
'select_more_than_one_account' => 'Please select more than one account',
'select_more_than_one_category' => 'Please select more than one category',
+ 'select_more_than_one_budget' => 'Please select more than one budget',
+ 'from_to' => 'From :start to :end',
// categories:
'new_category' => 'New category',
@@ -633,6 +635,7 @@ return [
'report_default' => 'Default financial report for :start until :end',
'report_audit' => 'Transaction history overview for :start until :end',
'report_category' => 'Category report for :start until :end',
+ 'report_budget' => 'Budget report for :start until :end',
'quick_link_reports' => 'Quick links',
'quick_link_default_report' => 'Default financial report',
'quick_link_audit_report' => 'Transaction history overview',
@@ -679,6 +682,7 @@ return [
'report_type_default' => 'Default financial report',
'report_type_audit' => 'Transaction history overview (audit)',
'report_type_category' => 'Category report',
+ 'report_type_budget' => 'Budget report',
'report_type_meta-history' => '類別、 預算與賬單的概覽',
'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.',
'report_included_accounts' => 'Included accounts',
@@ -698,11 +702,14 @@ return [
'reports_submit' => 'View report',
'end_after_start_date' => 'End date of report must be after start date.',
'select_category' => 'Select one or more categories.',
+ 'select_budget' => 'Select one or more budgets.',
'income_per_category' => 'Income per category',
'expense_per_category' => 'Expense per category',
+ 'expense_per_budget' => 'Expense per budget',
'income_per_account' => 'Income per account',
'expense_per_account' => 'Expense per account',
- 'include_not_in_category' => 'Include transactions not selected for this report',
+ 'include_not_in_category' => 'Include categories not selected for this report',
+ 'include_not_in_budget' => 'Include budgets not selected for this report',
'everything_else' => 'Everything else',
'income_and_expenses' => 'Income and expenses',
'spent_average' => 'Spent (average)',
@@ -712,6 +719,8 @@ return [
'average_income_per_account' => 'Average income per account',
'total' => 'Total',
'description' => 'Description',
+ 'sum_of_period' => 'Sum of period',
+ 'average_in_period' => 'Average in period',
// charts:
@@ -720,13 +729,15 @@ return [
'month' => 'Month',
'budget' => 'Budget',
'spent' => 'Spent',
+ 'spent_in_budget' => 'Spent in budget',
+ 'left_to_spend' => 'Left to spend',
'earned' => 'Earned',
'overspent' => 'Overspent',
'left' => 'Left',
'no_budget' => '(no budget)',
- 'maxAmount' => 'Maximum amount',
- 'minAmount' => 'Minumum amount',
- 'billEntry' => 'Current bill entry',
+ 'max-amount' => 'Maximum amount',
+ 'min-amount' => 'Minumum amount',
+ 'journal-amount' => 'Current bill entry',
'name' => 'Name',
'date' => 'Date',
'paid' => 'Paid',
@@ -825,6 +836,7 @@ return [
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email',
+ 'edit_user' => 'Edit user :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.',
'user_data_information' => 'User data',
'user_information' => 'User information',
@@ -838,6 +850,17 @@ return [
'configuration_updated' => 'The configuration has been updated',
'setting_is_demo_site' => 'Demo site',
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
+ 'setting_send_email_notifications' => 'Send email notifications',
+ 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.',
+ 'mail_for_lockout_help' => 'When a user is locked out',
+ 'mail_for_blocked_domain_help' => 'When a user tries to register using a blocked domain',
+ 'mail_for_blocked_email_help' => 'When a user tries to register using a blocked email address',
+ 'mail_for_bad_login_help' => 'When a user fails to login',
+ 'mail_for_blocked_login_help' => 'When a blocked user tries to login',
+ 'block_code_bounced' => 'Email message(s) bounced',
+ 'block_code_expired' => 'Demo account expired',
+ 'no_block_code' => 'No reason for block or user not blocked',
+
// split a transaction:
'transaction_meta_data' => 'Transaction meta-data',
@@ -911,4 +934,10 @@ return [
'import_finished_link' => '匯入成功的所有交易都可以在標籤 :tag 內找到。',
'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks',
'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.',
+ 'bread_crumb_import_complete' => 'Import ":key" complete',
+ 'bread_crumb_configure_import' => 'Configure import ":key"',
+ 'bread_crumb_import_finished' => 'Import ":key" finished',
+ 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.',
+ 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".',
+ 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.',
];
\ No newline at end of file
diff --git a/resources/lang/zh_TW/form.php b/resources/lang/zh_TW/form.php
index 7c3b46d8db..b50b217c62 100644
--- a/resources/lang/zh_TW/form.php
+++ b/resources/lang/zh_TW/form.php
@@ -150,22 +150,35 @@ return [
'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+ 'email' => 'Email address',
+ 'password' => 'Password',
+ 'password_confirmation' => 'Password (again)',
+ 'blocked' => 'Is blocked?',
+ 'blocked_code' => 'Reason for block',
+
+
// admin
- 'domain' => 'Domain',
- 'single_user_mode' => 'Single user mode',
- 'must_confirm_account' => 'New users must activate account',
- 'is_demo_site' => 'Is demo site',
+ 'domain' => 'Domain',
+ 'single_user_mode' => 'Single user mode',
+ 'must_confirm_account' => 'New users must activate account',
+ 'is_demo_site' => 'Is demo site',
+ 'mail_for_lockout' => 'Locked out',
+ 'mail_for_blocked_domain' => 'Blocked domain',
+ 'mail_for_blocked_email' => 'Blocked email address',
+ 'mail_for_bad_login' => 'Login failure',
+ 'mail_for_blocked_login' => 'Blocked user',
+
// import
- 'import_file' => '匯入檔案',
- 'configuration_file' => 'Configuration file',
- 'import_file_type' => '匯入檔案類型',
- 'csv_comma' => 'A comma (,)',
- 'csv_semicolon' => 'A semicolon (;)',
- 'csv_tab' => 'A tab (invisible)',
- 'csv_delimiter' => 'CSV field delimiter',
- 'csv_import_account' => 'Default import account',
- 'csv_config' => 'CSV import configuration',
+ 'import_file' => '匯入檔案',
+ 'configuration_file' => 'Configuration file',
+ 'import_file_type' => '匯入檔案類型',
+ 'csv_comma' => 'A comma (,)',
+ 'csv_semicolon' => 'A semicolon (;)',
+ 'csv_tab' => 'A tab (invisible)',
+ 'csv_delimiter' => 'CSV field delimiter',
+ 'csv_import_account' => 'Default import account',
+ 'csv_config' => 'CSV import configuration',
'due_date' => '到期日',
diff --git a/resources/lang/zh_TW/list.php b/resources/lang/zh_TW/list.php
index 104a8cba1c..d5df766461 100644
--- a/resources/lang/zh_TW/list.php
+++ b/resources/lang/zh_TW/list.php
@@ -12,6 +12,7 @@
return [
'buttons' => '按鈕',
'icon' => '圖標',
+ 'id' => 'ID',
'create_date' => '建立於',
'update_date' => '更新於',
'balance_before' => '交易前餘額',
@@ -76,7 +77,7 @@ return [
'destination_account' => 'Destination account',
'accounts_count' => 'Number of accounts',
- 'journals_count' => 'Number of journals',
+ 'journals_count' => 'Number of transactions',
'attachments_count' => 'Number of attachments',
'bills_count' => 'Number of bills',
'categories_count' => 'Number of categories',
diff --git a/resources/lang/zh_TW/validation.php b/resources/lang/zh_TW/validation.php
index 9334e9cfa3..69f8078ff3 100644
--- a/resources/lang/zh_TW/validation.php
+++ b/resources/lang/zh_TW/validation.php
@@ -12,6 +12,7 @@
return [
'iban' => '這不是有效的 IBAN。',
'unique_account_number_for_user' => '此帳號號碼已經存在。',
+ 'deleted_user' => 'Due to security constraints, you cannot register using this email address.',
'rule_trigger_value' => '此值不能用於所選擇的事件。',
'rule_action_value' => '此值不能用於所選擇的動作。',
'invalid_domain' => '基於安全理由,你無法使用此域名註冊。',
diff --git a/storage/database/seed.bill-test.json b/resources/seeds/seed.bill-test.json
similarity index 100%
rename from storage/database/seed.bill-test.json
rename to resources/seeds/seed.bill-test.json
diff --git a/storage/database/seed.import-test.json b/resources/seeds/seed.import-test.json
similarity index 100%
rename from storage/database/seed.import-test.json
rename to resources/seeds/seed.import-test.json
diff --git a/storage/database/seed.local.json b/resources/seeds/seed.local.json
similarity index 100%
rename from storage/database/seed.local.json
rename to resources/seeds/seed.local.json
diff --git a/storage/database/seed.split.json b/resources/seeds/seed.split.json
similarity index 100%
rename from storage/database/seed.split.json
rename to resources/seeds/seed.split.json
diff --git a/storage/database/seed.testing.json b/resources/seeds/seed.testing.json
similarity index 83%
rename from storage/database/seed.testing.json
rename to resources/seeds/seed.testing.json
index d6f9dfe2ea..5ec4e900f2 100644
--- a/storage/database/seed.testing.json
+++ b/resources/seeds/seed.testing.json
@@ -995,6 +995,184 @@
]
}
],
- "import-jobs": [],
+ "import-jobs": [
+ {
+ "user_id": 1,
+ "key": "testImport",
+ "file_type": "csv",
+ "status": "settings_complete",
+ "extended_status": {
+ "steps_done": 0,
+ "total_steps": 0,
+ "errors": [],
+ "import_count": 0,
+ "importTag": 0
+ },
+ "configuration": {
+ "has-headers": false,
+ "date-format": "Ymd",
+ "delimiter": ",",
+ "import-account": 1,
+ "specifics": {
+ "RabobankDescription": 1
+ },
+ "column-count": 19,
+ "column-roles": [
+ "account-iban",
+ "currency-code",
+ "date-interest",
+ "rabo-debet-credit",
+ "amount",
+ "opposing-iban",
+ "opposing-name",
+ "date-book",
+ "description",
+ "_ignore",
+ "description",
+ "description",
+ "description",
+ "description",
+ "description",
+ "description",
+ "sepa-ct-id",
+ "sepa-ct-op",
+ "sepa-db"
+ ],
+ "column-do-mapping": [
+ true,
+ true,
+ false,
+ false,
+ false,
+ true,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ],
+ "column-roles-complete": false,
+ "column-mapping-config": {
+ "0": [],
+ "1": {
+ "EUR": 1
+ },
+ "5": [],
+ "6": []
+ },
+ "column-mapping-complete": false
+ }
+ },
+
+ {
+ "user_id": 1,
+ "key": "complete",
+ "file_type": "csv",
+ "status": "settings_complete",
+ "extended_status": {
+ "steps_done": 0,
+ "total_steps": 0,
+ "errors": [],
+ "import_count": 0,
+ "importTag": 0
+ },
+ "configuration": {
+ "has-headers": false,
+ "date-format": "Ymd",
+ "delimiter": ",",
+ "import-account": 1
+ }
+ },
+ {
+ "user_id": 1,
+ "key": "configure",
+ "file_type": "csv",
+ "status": "import_status_never_started",
+ "extended_status": {
+ "steps_done": 0,
+ "total_steps": 0,
+ "errors": [],
+ "import_count": 0,
+ "importTag": 0
+ },
+ "configuration": {
+ }
+ },
+ {
+ "user_id": 1,
+ "key": "settings",
+ "file_type": "csv",
+ "status": "import_configuration_saved",
+ "extended_status": {
+ "steps_done": 0,
+ "total_steps": 0,
+ "errors": [],
+ "import_count": 0,
+ "importTag": 0
+ },
+ "configuration": {
+ }
+ },
+ {
+ "user_id": 1,
+ "key": "p-settings",
+ "file_type": "csv",
+ "status": "import_configuration_saved",
+ "extended_status": {
+ "steps_done": 0,
+ "total_steps": 0,
+ "errors": [],
+ "import_count": 0,
+ "importTag": 0
+ },
+ "configuration": {
+ }
+ },
+ {
+ "user_id": 1,
+ "key": "p-configure",
+ "file_type": "csv",
+ "status": "import_status_never_started",
+ "extended_status": {
+ "steps_done": 0,
+ "total_steps": 0,
+ "errors": [],
+ "import_count": 0,
+ "importTag": 0
+ },
+ "configuration": {
+ }
+ },
+ {
+ "user_id": 1,
+ "key": "finished",
+ "file_type": "csv",
+ "status": "import_complete",
+ "extended_status": {
+ "steps_done": 0,
+ "total_steps": 0,
+ "errors": [],
+ "import_count": 0,
+ "importTag": 1
+ },
+ "configuration": {
+ }
+ }
+ ],
+ "export-jobs": [
+ {
+ "user_id": 1,
+ "key": "testExport",
+ "status": "unknown"
+ }
+ ],
"currencies": []
}
\ No newline at end of file
diff --git a/resources/stubs/csv.csv b/resources/stubs/csv.csv
new file mode 100644
index 0000000000..05de7d7338
--- /dev/null
+++ b/resources/stubs/csv.csv
@@ -0,0 +1,3 @@
+Header
+Value
+Value
\ No newline at end of file
diff --git a/resources/views/accounts/show-by-date.twig b/resources/views/accounts/show-by-date.twig
deleted file mode 100644
index fbe746b110..0000000000
--- a/resources/views/accounts/show-by-date.twig
+++ /dev/null
@@ -1,110 +0,0 @@
-{% extends "./layout/default" %}
-
-{% block breadcrumbs %}
-
- {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, account, carbon) }}
-{% endblock %}
-
-{% block content %}
-
-
-
-
-
-
-
-
-
-
- {% include 'list.journals-tasker' with {sorting:true} %}
-
-
-
-
-
-
-
-{% endblock %}
-
-{% block scripts %}
-
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig
index 9826d1f358..42068245e4 100644
--- a/resources/views/accounts/show.twig
+++ b/resources/views/accounts/show.twig
@@ -1,7 +1,7 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
- {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, account) }}
+ {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, account, start, end) }}
{% endblock %}
{% block content %}
@@ -9,7 +9,8 @@