diff --git a/CHANGELOG.md b/CHANGELOG.md
index c245627b66..90ab0ae211 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,8 +2,29 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## [4.1] - 2016-10-22
+### Added
+- Option to show deposit accounts on the front page.
+- Script to upgrade split transactions
+- Can now save notes on piggy banks.
+- Extend user admin options.
+- Run import jobs from the command line
+### Changed
+- New preferences screen layout.
+
+### Deprecated
+- ``firefly:import`` is now ``firefly:start-import``
+
+### Removed
+- Lots of old code
+
+### Fixed
+- #357, where non utf-8 files would break Firefly.
+- Tab delimiter is not properly loaded from import configuration (@roberthorlings)
+- System response to yearly bills
+
## [4.0.2] - 2016-10-14
### Added
- Added ``intl`` dependency to composer file to ease installation (thanks @telyn)
diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php
new file mode 100644
index 0000000000..0bf65903fa
--- /dev/null
+++ b/app/Console/Commands/CreateImport.php
@@ -0,0 +1,152 @@
+argument('file');
+ $configuration = $this->argument('configuration');
+ $user = $userRepository->find(intval($this->option('user')));
+ $cwd = getcwd();
+ $type = strtolower($this->option('type'));
+
+ if (!$this->validArguments()) {
+ return;
+ }
+
+ // try to parse configuration data:
+ $configurationData = json_decode(file_get_contents($configuration));
+ if (is_null($configurationData)) {
+ $this->error(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
+
+ return;
+ }
+
+ $this->info(sprintf('Going to create a job to import file: %s', $file));
+ $this->info(sprintf('Using configuration file: %s', $configuration));
+ $this->info(sprintf('Import into user: #%d (%s)', $user->id, $user->email));
+ $this->info(sprintf('Type of import: %s', $type));
+
+ /** @var ImportJobRepositoryInterface $jobRepository */
+ $jobRepository = app(ImportJobRepositoryInterface::class, [$user]);
+
+ $job = $jobRepository->create($type);
+ $this->line(sprintf('Created job "%s"...', $job->key));
+
+ // put the file in the proper place:
+ Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]);
+ $this->line('Stored import data...');
+
+ // store the configuration in the job:
+ $job->configuration = $configurationData;
+ $job->status = 'settings_complete';
+ $job->save();
+ $this->line('Stored configuration...');
+
+ // if user wants to run it, do!
+ if ($this->option('start') === true) {
+ $this->line('The import will start in a moment. This process is not visible...');
+ Log::debug('Go for import!');
+ Artisan::call('firefly:start-import', ['key' => $job->key]);
+ $this->line('Done!');
+ }
+
+ return;
+ }
+
+ /**
+ * @return bool
+ */
+ private function validArguments(): bool
+ {
+ // find the file
+ /** @var UserRepositoryInterface $userRepository */
+ $userRepository = app(UserRepositoryInterface::class);
+ $file = $this->argument('file');
+ $configuration = $this->argument('configuration');
+ $user = $userRepository->find(intval($this->option('user')));
+ $cwd = getcwd();
+ $validTypes = array_keys(config('firefly.import_formats'));
+ $type = strtolower($this->option('type'));
+
+ if (is_null($user->id)) {
+ $this->error(sprintf('There is no user with ID %d.', $this->option('user')));
+
+ return false;
+ }
+ if (!in_array($type, $validTypes)) {
+ $this->error(sprintf('Cannot import file of type "%s"', $type));
+
+ return false;
+ }
+
+ if (!file_exists($file)) {
+ $this->error(sprintf('Firefly III cannot find file "%s" (working directory: "%s").', $file, $cwd));
+
+ return false;
+ }
+
+ if (!file_exists($configuration)) {
+ $this->error(sprintf('Firefly III cannot find configuration file "%s" (working directory: "%s").', $configuration, $cwd));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php
index 7b8c77e9f3..0459e1c75b 100644
--- a/app/Console/Commands/Import.php
+++ b/app/Console/Commands/Import.php
@@ -32,14 +32,14 @@ class Import extends Command
*
* @var string
*/
- protected $description = 'Import stuff into Firefly III.';
+ protected $description = 'This will start a new import.';
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'firefly:import {key}';
+ protected $signature = 'firefly:start-import {key}';
/**
* Create a new command instance.
@@ -57,9 +57,12 @@ class Import extends Command
*/
public function handle()
{
+ Log::debug('Start start-import command');
$jobKey = $this->argument('key');
$job = ImportJob::whereKey($jobKey)->first();
if (!$this->isValid($job)) {
+ Log::error('Job is not valid for some reason. Exit.');
+
return;
}
diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php
new file mode 100644
index 0000000000..e525531d2f
--- /dev/null
+++ b/app/Console/Commands/UpgradeDatabase.php
@@ -0,0 +1,121 @@
+setTransactionIdentifier();
+
+
+ }
+
+ /**
+ * This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
+ */
+ private function setTransactionIdentifier()
+ {
+ $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')]);
+
+ $result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
+ ->mergeBindings($subQuery->getQuery())
+ ->where('t_count', '>', 2)
+ ->select(['id', 't_count']);
+ $journalIds = array_unique($result->pluck('id')->toArray());
+
+ foreach ($journalIds as $journalId) {
+ // grab all positive transactiosn from this journal that are not deleted.
+ // for each one, grab the negative opposing one which has 0 as an identifier and give it the same identifier.
+ $identifier = 0;
+ $processed = [];
+ $transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get();
+ /** @var Transaction $transaction */
+ foreach ($transactions as $transaction) {
+ // find opposing:
+ $amount = bcmul(strval($transaction->amount), '-1');
+
+ try {
+ /** @var Transaction $opposing */
+ $opposing = Transaction
+ ::where('transaction_journal_id', $journalId)
+ ->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.');
+ $this->error('This field is required for Firefly III version ' . config('firefly.version') . ' to run.');
+ $this->error('Please run "php artisan migrate" to add this field to the table.');
+ $this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
+ break 2;
+ }
+ if (!is_null($opposing)) {
+ // give both a new identifier:
+ $transaction->identifier = $identifier;
+ $transaction->save();
+ $opposing->identifier = $identifier;
+ $opposing->save();
+ $processed[] = $transaction->id;
+ $processed[] = $opposing->id;
+ $this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id));
+ }
+ $identifier++;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index d3f6037429..d62f5371ca 100755
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -13,9 +13,11 @@ declare(strict_types = 1);
namespace FireflyIII\Console;
+use FireflyIII\Console\Commands\CreateImport;
use FireflyIII\Console\Commands\EncryptFile;
use FireflyIII\Console\Commands\Import;
use FireflyIII\Console\Commands\ScanAttachments;
+use FireflyIII\Console\Commands\UpgradeDatabase;
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
use FireflyIII\Console\Commands\VerifyDatabase;
use Illuminate\Console\Scheduling\Schedule;
@@ -57,8 +59,10 @@ class Kernel extends ConsoleKernel
UpgradeFireflyInstructions::class,
VerifyDatabase::class,
Import::class,
+ CreateImport::class,
EncryptFile::class,
ScanAttachments::class,
+ UpgradeDatabase::class,
];
diff --git a/app/Crud/Split/Journal.php b/app/Crud/Split/Journal.php
deleted file mode 100644
index 6fdc6b33ab..0000000000
--- a/app/Crud/Split/Journal.php
+++ /dev/null
@@ -1,221 +0,0 @@
-user = $user;
- }
-
- /**
- * @param $journal
- *
- * @return bool
- */
- public function markAsComplete(TransactionJournal $journal)
- {
- $journal->completed = 1;
- $journal->save();
-
- return true;
- }
-
- /**
- * @param TransactionJournal $journal
- * @param array $transaction
- * @param int $identifier
- *
- * @return Collection
- */
- public function storeTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection
- {
- // store accounts (depends on type)
- list($sourceAccount, $destinationAccount) = $this->storeAccounts($journal->transactionType->type, $transaction);
-
- // store transaction one way:
- /** @var Transaction $one */
- $one = Transaction::create(
- ['account_id' => $sourceAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'] * -1,
- 'description' => $transaction['description'], 'identifier' => $identifier]
- );
- $two = Transaction::create(
- ['account_id' => $destinationAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'],
- 'description' => $transaction['description'], 'identifier' => $identifier]
- );
-
- if (strlen($transaction['category']) > 0) {
- $category = Category::firstOrCreateEncrypted(['name' => $transaction['category'], 'user_id' => $journal->user_id]);
- $one->categories()->save($category);
- $two->categories()->save($category);
- }
- if (intval($transaction['budget_id']) > 0) {
- $budget = Budget::find($transaction['budget_id']);
- $one->budgets()->save($budget);
- $two->budgets()->save($budget);
- }
-
- if ($transaction['piggy_bank_id'] > 0) {
- $transaction['date'] = $journal->date->format('Y-m-d');
- event(new TransactionStored($transaction));
- }
-
- return new Collection([$one, $two]);
- }
-
- /**
- * @param TransactionJournal $journal
- * @param array $data
- *
- * @return TransactionJournal
- */
- public function updateJournal(TransactionJournal $journal, array $data): TransactionJournal
- {
- $journal->description = $data['journal_description'];
- $journal->transaction_currency_id = $data['journal_currency_id'];
- $journal->date = $data['date'];
- $journal->interest_date = $data['interest_date'];
- $journal->book_date = $data['book_date'];
- $journal->process_date = $data['process_date'];
- $journal->save();
-
- // delete original transactions, and recreate them.
- $journal->transactions()->delete();
-
- $identifier = 0;
- foreach ($data['transactions'] as $transaction) {
- $this->storeTransaction($journal, $transaction, $identifier);
- $identifier++;
- }
-
- $journal->completed = true;
- $journal->save();
-
- return $journal;
- }
-
- /**
- * @param string $type
- * @param array $transaction
- *
- * @return array
- * @throws FireflyException
- */
- private function storeAccounts(string $type, array $transaction): array
- {
- $sourceAccount = null;
- $destinationAccount = null;
- switch ($type) {
- case TransactionType::WITHDRAWAL:
- list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($transaction);
- break;
- case TransactionType::DEPOSIT:
- list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction);
- break;
- case TransactionType::TRANSFER:
- $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['source_account_id'])->first();
- $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['destination_account_id'])->first();
- break;
- default:
- throw new FireflyException('Cannot handle ' . e($type));
- }
-
- return [$sourceAccount, $destinationAccount];
- }
-
- /**
- * @param array $data
- *
- * @return array
- */
- private function storeDepositAccounts(array $data): array
- {
- $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
-
-
- if (isset($data['source_account_name']) && strlen($data['source_account_name']) > 0) {
- $sourceType = AccountType::where('type', 'Revenue account')->first();
- $sourceAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
- );
-
- return [$sourceAccount, $destinationAccount];
- }
-
- $sourceType = AccountType::where('type', 'Cash account')->first();
- $sourceAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
- );
-
- return [$sourceAccount, $destinationAccount];
- }
-
- /**
- * @param array $data
- *
- * @return array
- */
- private function storeWithdrawalAccounts(array $data): array
- {
- $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
-
- if (strlen($data['destination_account_name']) > 0) {
- $destinationType = AccountType::where('type', 'Expense account')->first();
- $destinationAccount = Account::firstOrCreateEncrypted(
- [
- 'user_id' => $this->user->id,
- 'account_type_id' => $destinationType->id,
- 'name' => $data['destination_account_name'],
- 'active' => 1,
- ]
- );
-
- return [$sourceAccount, $destinationAccount];
- }
- $destinationType = AccountType::where('type', 'Cash account')->first();
- $destinationAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
- );
-
- return [$sourceAccount, $destinationAccount];
-
-
- }
-}
diff --git a/app/Crud/Split/JournalInterface.php b/app/Crud/Split/JournalInterface.php
deleted file mode 100644
index e296a1114c..0000000000
--- a/app/Crud/Split/JournalInterface.php
+++ /dev/null
@@ -1,50 +0,0 @@
-transaction = $transaction;
- }
-
-}
diff --git a/app/Events/BudgetLimitUpdated.php b/app/Events/UpdatedBudgetLimit.php
similarity index 91%
rename from app/Events/BudgetLimitUpdated.php
rename to app/Events/UpdatedBudgetLimit.php
index 596c59c8b2..3642531e75 100644
--- a/app/Events/BudgetLimitUpdated.php
+++ b/app/Events/UpdatedBudgetLimit.php
@@ -1,6 +1,6 @@
user = $user;
- $this->ipAddress = $ipAddress;
- }
-}
diff --git a/app/Export/Processor.php b/app/Export/Processor.php
index f5c69be451..7c33ae04b2 100644
--- a/app/Export/Processor.php
+++ b/app/Export/Processor.php
@@ -19,7 +19,7 @@ use FireflyIII\Export\Collector\UploadCollector;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\TransactionJournal;
-use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Collection;
use Storage;
@@ -95,9 +95,9 @@ class Processor
*/
public function collectJournals(): bool
{
- /** @var JournalRepositoryInterface $repository */
- $repository = app(JournalRepositoryInterface::class);
- $this->journals = $repository->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
+ /** @var JournalTaskerInterface $tasker */
+ $tasker = app(JournalTaskerInterface::class);
+ $this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
return true;
}
diff --git a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php
index c27376b837..0cb57197da 100644
--- a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php
+++ b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php
@@ -24,7 +24,6 @@ use Illuminate\Support\Collection;
*/
interface AccountChartGeneratorInterface
{
-
/**
* @param Collection $accounts
* @param Carbon $start
@@ -43,6 +42,15 @@ interface AccountChartGeneratorInterface
*/
public function frontpage(Collection $accounts, Carbon $start, Carbon $end): array;
+ /**
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return array
+ */
+ public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array;
+
/**
* @param Account $account
* @param array $labels
diff --git a/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php b/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php
index 215a9969b0..a8a222e16b 100644
--- a/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php
+++ b/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php
@@ -83,6 +83,30 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
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
@@ -105,5 +129,4 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
return $data;
}
-
}
diff --git a/app/Handlers/Events/AttachUserRole.php b/app/Handlers/Events/AttachUserRole.php
deleted file mode 100644
index 15855e3391..0000000000
--- a/app/Handlers/Events/AttachUserRole.php
+++ /dev/null
@@ -1,52 +0,0 @@
-count() == 1) {
- $repository->attachRole($event->user, 'owner');
- }
- }
-
-}
diff --git a/app/Handlers/Events/BudgetLimitEventHandler.php b/app/Handlers/Events/BudgetEventHandler.php
similarity index 79%
rename from app/Handlers/Events/BudgetLimitEventHandler.php
rename to app/Handlers/Events/BudgetEventHandler.php
index a05a752cd2..cd02a373ef 100644
--- a/app/Handlers/Events/BudgetLimitEventHandler.php
+++ b/app/Handlers/Events/BudgetEventHandler.php
@@ -1,6 +1,6 @@
budgetLimit;
$end = $event->end;
@@ -71,12 +65,18 @@ class BudgetLimitEventHandler
}
+ return true;
}
+
/**
- * @param BudgetLimitUpdated $event
+ * Updates, if present the budget limit repetition part of a budget limit.
+ *
+ * @param UpdatedBudgetLimit $event
+ *
+ * @return bool
*/
- public function update(BudgetLimitUpdated $event)
+ public function update(UpdatedBudgetLimit $event): bool
{
$budgetLimit = $event->budgetLimit;
$end = $event->end;
@@ -104,6 +104,7 @@ class BudgetLimitEventHandler
$repetition->save();
}
- }
-}
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/app/Handlers/Events/ConnectJournalToPiggyBank.php b/app/Handlers/Events/ConnectJournalToPiggyBank.php
deleted file mode 100644
index 1d7d75d28c..0000000000
--- a/app/Handlers/Events/ConnectJournalToPiggyBank.php
+++ /dev/null
@@ -1,72 +0,0 @@
-journal;
- $piggyBankId = $event->piggyBankId;
-
- /** @var PiggyBank $piggyBank */
- $piggyBank = auth()->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
-
- if (is_null($piggyBank)) {
- return true;
- }
- // update piggy bank rep for date of transaction journal.
- $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
- if (is_null($repetition)) {
- return true;
- }
-
- $amount = TransactionJournal::amountPositive($journal);
- // if piggy account matches source account, the amount is positive
- $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
- if (in_array($piggyBank->account_id, $sources)) {
- $amount = bcmul($amount, '-1');
- }
-
-
- $repetition->currentamount = bcadd($repetition->currentamount, $amount);
- $repetition->save();
-
- PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]);
-
- return true;
-
- }
-
-
-}
diff --git a/app/Handlers/Events/ConnectTransactionToPiggyBank.php b/app/Handlers/Events/ConnectTransactionToPiggyBank.php
deleted file mode 100644
index 8e56987a0d..0000000000
--- a/app/Handlers/Events/ConnectTransactionToPiggyBank.php
+++ /dev/null
@@ -1,70 +0,0 @@
-transaction;
-
- $piggyBank = $repository->find($transaction['piggy_bank_id']);
-
- // valid piggy:
- if (is_null($piggyBank->id)) {
- return true;
- }
- $amount = strval($transaction['amount']);
- // piggy bank account something with amount:
- if ($transaction['source_account_id'] == $piggyBank->account_id) {
- // if the source of this transaction is the same as the piggy bank,
- // the money is being removed from the piggy bank. So the
- // amount must be negative:
- $amount = bcmul($amount, '-1');
- }
-
- $repetition = $piggyBank->currentRelevantRep();
- // add or remove the money from the piggy bank:
- $newAmount = bcadd(strval($repetition->currentamount), $amount);
- $repetition->currentamount = $newAmount;
- $repetition->save();
-
- // now generate a piggy bank event:
- PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'date' => $transaction['date'], 'amount' => $newAmount]);
-
- return true;
- }
-
-
-}
diff --git a/app/Handlers/Events/FireRulesForStore.php b/app/Handlers/Events/FireRulesForStore.php
deleted file mode 100644
index 65cc16d67a..0000000000
--- a/app/Handlers/Events/FireRulesForStore.php
+++ /dev/null
@@ -1,68 +0,0 @@
-user();
- $groups = $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
- //
- /** @var RuleGroup $group */
- foreach ($groups as $group) {
- $rules = $group->rules()
- ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
- ->where('rule_triggers.trigger_type', 'user_action')
- ->where('rule_triggers.trigger_value', 'store-journal')
- ->where('rules.active', 1)
- ->get(['rules.*']);
- /** @var Rule $rule */
- foreach ($rules as $rule) {
-
- $processor = Processor::make($rule);
- $processor->handleTransactionJournal($event->journal);
-
- if ($rule->stop_processing) {
- return true;
- }
-
- }
- }
-
- return true;
- }
-}
diff --git a/app/Handlers/Events/FireRulesForUpdate.php b/app/Handlers/Events/FireRulesForUpdate.php
deleted file mode 100644
index 889d1e3156..0000000000
--- a/app/Handlers/Events/FireRulesForUpdate.php
+++ /dev/null
@@ -1,65 +0,0 @@
-user();
- $groups = $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
- //
- /** @var RuleGroup $group */
- foreach ($groups as $group) {
- $rules = $group->rules()
- ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
- ->where('rule_triggers.trigger_type', 'user_action')
- ->where('rule_triggers.trigger_value', 'update-journal')
- ->where('rules.active', 1)
- ->get(['rules.*']);
- /** @var Rule $rule */
- foreach ($rules as $rule) {
- $processor = Processor::make($rule);
- $processor->handleTransactionJournal($event->journal);
-
- if ($rule->stop_processing) {
- break;
- }
-
- }
- }
-
- return true;
- }
-}
diff --git a/app/Handlers/Events/ScanForBillsAfterStore.php b/app/Handlers/Events/ScanForBillsAfterStore.php
deleted file mode 100644
index 499370eab9..0000000000
--- a/app/Handlers/Events/ScanForBillsAfterStore.php
+++ /dev/null
@@ -1,42 +0,0 @@
-journal;
- BillScanner::scan($journal);
-
- return true;
- }
-
-}
diff --git a/app/Handlers/Events/ScanForBillsAfterUpdate.php b/app/Handlers/Events/ScanForBillsAfterUpdate.php
deleted file mode 100644
index 11ddfee6aa..0000000000
--- a/app/Handlers/Events/ScanForBillsAfterUpdate.php
+++ /dev/null
@@ -1,41 +0,0 @@
-journal;
- BillScanner::scan($journal);
-
- return true;
- }
-
-}
diff --git a/app/Handlers/Events/SendRegistrationMail.php b/app/Handlers/Events/SendRegistrationMail.php
deleted file mode 100644
index aabb3714af..0000000000
--- a/app/Handlers/Events/SendRegistrationMail.php
+++ /dev/null
@@ -1,69 +0,0 @@
-user->email;
- $address = route('index');
- $ipAddress = $event->ipAddress;
- // send email.
- try {
- Mail::send(
- ['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
- $message->to($email, $email)->subject('Welcome to Firefly III! ');
- }
- );
- } catch (Swift_TransportException $e) {
- Log::error($e->getMessage());
- }
-
- return true;
- }
-}
diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php
new file mode 100644
index 0000000000..1410147625
--- /dev/null
+++ b/app/Handlers/Events/StoredJournalEventHandler.php
@@ -0,0 +1,124 @@
+journal;
+ $piggyBankId = $event->piggyBankId;
+
+ /** @var PiggyBank $piggyBank */
+ $piggyBank = $journal->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
+
+ if (is_null($piggyBank)) {
+ return true;
+ }
+ // update piggy bank rep for date of transaction journal.
+ $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
+ if (is_null($repetition)) {
+ return true;
+ }
+
+ $amount = TransactionJournal::amountPositive($journal);
+ // if piggy account matches source account, the amount is positive
+ $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
+ if (in_array($piggyBank->account_id, $sources)) {
+ $amount = bcmul($amount, '-1');
+ }
+
+
+ $repetition->currentamount = bcadd($repetition->currentamount, $amount);
+ $repetition->save();
+
+ PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]);
+
+ return true;
+ }
+
+ /**
+ * This method grabs all the users rules and processes them.
+ *
+ * @param StoredTransactionJournal $event
+ *
+ * @return bool
+ */
+ public function processRules(StoredTransactionJournal $event): bool
+ {
+ // get all the user's rule groups, with the rules, order by 'order'.
+ $journal = $event->journal;
+ $groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
+ //
+ /** @var RuleGroup $group */
+ foreach ($groups as $group) {
+ $rules = $group->rules()
+ ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
+ ->where('rule_triggers.trigger_type', 'user_action')
+ ->where('rule_triggers.trigger_value', 'store-journal')
+ ->where('rules.active', 1)
+ ->get(['rules.*']);
+ /** @var Rule $rule */
+ foreach ($rules as $rule) {
+
+ $processor = Processor::make($rule);
+ $processor->handleTransactionJournal($journal);
+
+ if ($rule->stop_processing) {
+ return true;
+ }
+
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * This method calls a special bill scanner that will check if the stored journal is part of a bill.
+ *
+ * @param StoredTransactionJournal $event
+ *
+ * @return bool
+ */
+ public function scanBills(StoredTransactionJournal $event): bool
+ {
+ $journal = $event->journal;
+ BillScanner::scan($journal);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/app/Handlers/Events/UpdateJournalConnection.php b/app/Handlers/Events/UpdateJournalConnection.php
deleted file mode 100644
index a30f63923a..0000000000
--- a/app/Handlers/Events/UpdateJournalConnection.php
+++ /dev/null
@@ -1,74 +0,0 @@
-journal;
-
- if (!$journal->isTransfer()) {
- return true;
- }
-
- // get the event connected to this journal:
- /** @var PiggyBankEvent $event */
- $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first();
- if (is_null($event)) {
- return false;
- }
- $piggyBank = $event->piggyBank()->first();
- $repetition = null;
- if (!is_null($piggyBank)) {
- /** @var PiggyBankRepetition $repetition */
- $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
- }
-
- if (is_null($repetition)) {
- return false;
- }
-
- $amount = TransactionJournal::amount($journal);
- $diff = bcsub($amount, $event->amount); // update current repetition
-
- $repetition->currentamount = bcadd($repetition->currentamount, $diff);
- $repetition->save();
-
-
- $event->amount = $amount;
- $event->save();
-
- return true;
- }
-
-}
diff --git a/app/Handlers/Events/UpdatedJournalEventHandler.php b/app/Handlers/Events/UpdatedJournalEventHandler.php
new file mode 100644
index 0000000000..9626120ae1
--- /dev/null
+++ b/app/Handlers/Events/UpdatedJournalEventHandler.php
@@ -0,0 +1,128 @@
+journal;
+
+ if (!$journal->isTransfer()) {
+ return true;
+ }
+
+ // get the event connected to this journal:
+ /** @var PiggyBankEvent $event */
+ $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first();
+ if (is_null($event)) {
+ return false;
+ }
+ $piggyBank = $event->piggyBank()->first();
+ $repetition = null;
+ if (!is_null($piggyBank)) {
+ /** @var PiggyBankRepetition $repetition */
+ $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
+ }
+
+ if (is_null($repetition)) {
+ return false;
+ }
+
+ $amount = TransactionJournal::amount($journal);
+ $diff = bcsub($amount, $event->amount); // update current repetition
+
+ $repetition->currentamount = bcadd($repetition->currentamount, $diff);
+ $repetition->save();
+
+
+ $event->amount = $amount;
+ $event->save();
+
+ return true;
+ }
+
+ /**
+ * This method will check all the rules when a journal is updated.
+ *
+ * @param UpdatedTransactionJournal $event
+ *
+ * @return bool
+ */
+ public function processRules(UpdatedTransactionJournal $event):bool
+ {
+ // get all the user's rule groups, with the rules, order by 'order'.
+ $journal = $event->journal;
+ $groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
+ //
+ /** @var RuleGroup $group */
+ foreach ($groups as $group) {
+ $rules = $group->rules()
+ ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
+ ->where('rule_triggers.trigger_type', 'user_action')
+ ->where('rule_triggers.trigger_value', 'update-journal')
+ ->where('rules.active', 1)
+ ->get(['rules.*']);
+ /** @var Rule $rule */
+ foreach ($rules as $rule) {
+ $processor = Processor::make($rule);
+ $processor->handleTransactionJournal($journal);
+
+ if ($rule->stop_processing) {
+ break;
+ }
+
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * This method calls a special bill scanner that will check if the updated journal is part of a bill.
+ *
+ * @param UpdatedTransactionJournal $event
+ *
+ * @return bool
+ */
+ public function scanBills(UpdatedTransactionJournal $event): bool
+ {
+ $journal = $event->journal;
+ BillScanner::scan($journal);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/app/Handlers/Events/UserConfirmation.php b/app/Handlers/Events/UserConfirmation.php
deleted file mode 100644
index 439db35e75..0000000000
--- a/app/Handlers/Events/UserConfirmation.php
+++ /dev/null
@@ -1,109 +0,0 @@
-user;
- $ipAddress = $event->ipAddress;
- $this->doConfirm($user, $ipAddress);
-
- return true;
- }
-
- /**
- * Handle the event.
- *
- * @param UserRegistration $event
- *
- * @return bool
- */
- public function sendConfirmation(UserRegistration $event): bool
- {
- $user = $event->user;
- $ipAddress = $event->ipAddress;
- $this->doConfirm($user, $ipAddress);
-
- return true;
- }
-
- /**
- * @param User $user
- * @param string $ipAddress
- */
- private function doConfirm(User $user, string $ipAddress)
- {
- $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
- if ($confirmAccount === false) {
- Preferences::setForUser($user, 'user_confirmed', true);
- Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
- Preferences::mark();
-
- return;
- }
- $email = $user->email;
- $code = str_random(16);
- $route = route('do_confirm_account', [$code]);
- Preferences::setForUser($user, 'user_confirmed', false);
- Preferences::setForUser($user, 'user_confirmed_last_mail', time());
- Preferences::setForUser($user, 'user_confirmed_code', $code);
- try {
- Mail::send(
- ['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
- function (Message $message) use ($email) {
- $message->to($email, $email)->subject('Please confirm your Firefly III account');
- }
- );
- } catch (Swift_TransportException $e) {
- Log::error($e->getMessage());
- } catch (Exception $e) {
- Log::error($e->getMessage());
- }
-
- return;
- }
-
-}
diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php
new file mode 100644
index 0000000000..2d8523e3c9
--- /dev/null
+++ b/app/Handlers/Events/UserEventHandler.php
@@ -0,0 +1,225 @@
+count() === 1) {
+ $repository->attachRole($event->user, 'owner');
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle user logout events.
+ *
+ * @return bool
+ */
+ public function onUserLogout(): bool
+ {
+ // dump stuff from the session:
+ Session::forget('twofactor-authenticated');
+ Session::forget('twofactor-authenticated-date');
+
+ return true;
+ }
+
+ /**
+ * This method will send a newly registered user a confirmation message, urging him or her to activate their account.
+ *
+ * @param RegisteredUser $event
+ *
+ * @return bool
+ */
+ public function sendConfirmationMessage(RegisteredUser $event): bool
+ {
+ $user = $event->user;
+ $ipAddress = $event->ipAddress;
+ $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
+ if ($confirmAccount === false) {
+ Preferences::setForUser($user, 'user_confirmed', true);
+ Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
+ Preferences::mark();
+
+ return true;
+ }
+ $email = $user->email;
+ $code = str_random(16);
+ $route = route('do_confirm_account', [$code]);
+ Preferences::setForUser($user, 'user_confirmed', false);
+ Preferences::setForUser($user, 'user_confirmed_last_mail', time());
+ Preferences::setForUser($user, 'user_confirmed_code', $code);
+ try {
+ Mail::send(
+ ['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
+ function (Message $message) use ($email) {
+ $message->to($email, $email)->subject('Please confirm your Firefly III account');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ } catch (Exception $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * If the user has somehow lost his or her confirmation message, this event will send it to the user again.
+ *
+ * At the moment, this method is exactly the same as the ::sendConfirmationMessage method, but that will change.
+ *
+ * @param ResentConfirmation $event
+ *
+ * @return bool
+ */
+ function sendConfirmationMessageAgain(ResentConfirmation $event): bool
+ {
+ $user = $event->user;
+ $ipAddress = $event->ipAddress;
+ $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
+ if ($confirmAccount === false) {
+ Preferences::setForUser($user, 'user_confirmed', true);
+ Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
+ Preferences::mark();
+
+ return true;
+ }
+ $email = $user->email;
+ $code = str_random(16);
+ $route = route('do_confirm_account', [$code]);
+ Preferences::setForUser($user, 'user_confirmed', false);
+ Preferences::setForUser($user, 'user_confirmed_last_mail', time());
+ Preferences::setForUser($user, 'user_confirmed_code', $code);
+ try {
+ Mail::send(
+ ['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
+ function (Message $message) use ($email) {
+ $message->to($email, $email)->subject('Please confirm your Firefly III account');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ } catch (Exception $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+
+ }
+
+ /**
+ * This method will send the user a registration mail, welcoming him or her to Firefly III.
+ * This message is only sent when the configuration of Firefly III says so.
+ *
+ * @param RegisteredUser $event
+ *
+ * @return bool
+ */
+ public function sendRegistrationMail(RegisteredUser $event)
+ {
+
+ $sendMail = env('SEND_REGISTRATION_MAIL', true);
+ if (!$sendMail) {
+ return true;
+ }
+ // get the email address
+ $email = $event->user->email;
+ $address = route('index');
+ $ipAddress = $event->ipAddress;
+ // send email.
+ try {
+ Mail::send(
+ ['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
+ $message->to($email, $email)->subject('Welcome to Firefly III! ');
+ }
+ );
+ } catch (Swift_TransportException $e) {
+ Log::error($e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * When the user is confirmed, this method stores the IP address of the user
+ * as a preference. Since this preference cannot be edited, it is effectively hidden
+ * from the user yet stored conveniently.
+ *
+ * @param ConfirmedUser $event
+ *
+ * @return bool
+ */
+ public function storeConfirmationIpAddress(ConfirmedUser $event): bool
+ {
+ Preferences::setForUser($event->user, 'confirmation_ip_address', $event->ipAddress);
+
+ return true;
+ }
+
+ /**
+ * This message stores the users IP address on registration, in much the same
+ * fashion as the previous method.
+ *
+ * @param RegisteredUser $event
+ *
+ * @return bool
+ */
+ public function storeRegistrationIpAddress(RegisteredUser $event): bool
+ {
+ Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress);
+
+ return true;
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/Handlers/Events/UserEventListener.php b/app/Handlers/Events/UserEventListener.php
deleted file mode 100644
index f81303d084..0000000000
--- a/app/Handlers/Events/UserEventListener.php
+++ /dev/null
@@ -1,38 +0,0 @@
-user, 'confirmation_ip_address', $event->ipAddress);
-
- return true;
- }
-
- /**
- * Handle the event.
- *
- * @param UserRegistration $event
- *
- * @return bool
- */
- public function saveFromRegistration(UserRegistration $event): bool
- {
- Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress);
-
- return true;
- }
-}
diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
index 23e71d6e4b..bad619e64a 100644
--- a/app/Http/Controllers/AccountController.php
+++ b/app/Http/Controllers/AccountController.php
@@ -114,12 +114,11 @@ class AccountController extends Controller
}
/**
- * @param ARI $repository
* @param Account $account
*
* @return View
*/
- public function edit(ARI $repository, Account $account)
+ public function edit(Account $account)
{
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php
index ad91ad8c58..5557539f35 100644
--- a/app/Http/Controllers/Admin/UserController.php
+++ b/app/Http/Controllers/Admin/UserController.php
@@ -78,5 +78,52 @@ class UserController extends Controller
}
+ /**
+ * @param UserRepositoryInterface $repository
+ * @param User $user
+ *
+ * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+ */
+ public function show(UserRepositoryInterface $repository, User $user)
+ {
+ $title = strval(trans('firefly.administration'));
+ $mainTitleIcon = 'fa-hand-spock-o';
+ $subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email]));
+ $subTitleIcon = 'fa-user';
+
+ // get IP info:
+ $defaultIp = '0.0.0.0';
+ $regPref = Preferences::getForUser($user, 'registration_ip_address');
+ $registration = $defaultIp;
+ $conPref = Preferences::getForUser($user, 'confirmation_ip_address');
+ $confirmation = $defaultIp;
+ if (!is_null($regPref)) {
+ $registration = $regPref->data;
+ }
+ if (!is_null($conPref)) {
+ $confirmation = $conPref->data;
+ }
+
+ $registrationHost = '';
+ $confirmationHost = '';
+
+ if ($registration != $defaultIp) {
+ $registrationHost = gethostbyaddr($registration);
+ }
+ if ($confirmation != $defaultIp) {
+ $confirmationHost = gethostbyaddr($confirmation);
+ }
+
+ $information = $repository->getUserData($user);
+
+ return view(
+ 'admin.users.show',
+ compact(
+ 'title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'information',
+ 'user', 'registration', 'confirmation', 'registrationHost', 'confirmationHost'
+ )
+ );
+ }
+
}
diff --git a/app/Http/Controllers/Auth/ConfirmationController.php b/app/Http/Controllers/Auth/ConfirmationController.php
index 4e34c7dcb8..3221971227 100644
--- a/app/Http/Controllers/Auth/ConfirmationController.php
+++ b/app/Http/Controllers/Auth/ConfirmationController.php
@@ -13,8 +13,8 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Auth;
-use FireflyIII\Events\ResendConfirmation;
-use FireflyIII\Events\UserIsConfirmed;
+use FireflyIII\Events\ConfirmedUser;
+use FireflyIII\Events\ResentConfirmation;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -56,7 +56,7 @@ class ConfirmationController extends Controller
if ($database === $code && ($now - $time <= $maxDiff)) {
// trigger user registration event:
- event(new UserIsConfirmed(auth()->user(), $request->ip()));
+ event(new ConfirmedUser(auth()->user(), $request->ip()));
Preferences::setForUser(auth()->user(), 'user_confirmed', true);
Preferences::setForUser(auth()->user(), 'user_confirmed_confirmed', time());
@@ -80,7 +80,7 @@ class ConfirmationController extends Controller
$owner = env('SITE_OWNER', 'mail@example.com');
$view = 'auth.confirmation.no-resent';
if ($now - $time > $maxDiff) {
- event(new ResendConfirmation(auth()->user(), $request->ip()));
+ event(new ResentConfirmation(auth()->user(), $request->ip()));
$view = 'auth.confirmation.resent';
}
diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php
index bb87bd81e3..e7d6dc6692 100755
--- a/app/Http/Controllers/Auth/RegisterController.php
+++ b/app/Http/Controllers/Auth/RegisterController.php
@@ -14,7 +14,7 @@ namespace FireflyIII\Http\Controllers\Auth;
use Auth;
use Config;
-use FireflyIII\Events\UserRegistration;
+use FireflyIII\Events\RegisteredUser;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\User;
@@ -102,7 +102,7 @@ class RegisterController extends Controller
$user = $this->create($request->all());
// trigger user registration event:
- event(new UserRegistration($user, $request->ip()));
+ event(new RegisteredUser($user, $request->ip()));
Auth::login($user);
diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php
index e9a3e7b3fc..3e8c162572 100755
--- a/app/Http/Controllers/Auth/ResetPasswordController.php
+++ b/app/Http/Controllers/Auth/ResetPasswordController.php
@@ -41,6 +41,8 @@ class ResetPasswordController extends Controller
*/
public function __construct()
{
+ parent::__construct();
+
$this->middleware('guest');
}
}
diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php
index fc0e907480..aef8266959 100644
--- a/app/Http/Controllers/BillController.php
+++ b/app/Http/Controllers/BillController.php
@@ -137,7 +137,7 @@ class BillController extends Controller
$bills = $repository->getBills();
$bills->each(
function (Bill $bill) use ($repository, $start, $end) {
- $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
+ $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
$bill->lastFoundMatch = $repository->lastFoundMatch($bill);
$journals = $repository->getJournalsInRange($bill, $start, $end);
// loop journals, find average:
@@ -204,7 +204,7 @@ class BillController extends Controller
$yearAverage = $repository->getYearAverage($bill, $date);
$overallAverage = $repository->getOverallAverage($bill);
$journals->setPath('/bills/show/' . $bill->id);
- $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
+ $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
$hideBill = true;
$subTitle = e($bill->name);
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index e1a18afd09..ade478c200 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -190,6 +190,56 @@ class AccountController extends Controller
return Response::json($data);
}
+ /**
+ * Shows the balances for all the user's revenue accounts.
+ *
+ * @param AccountRepositoryInterface $repository
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function revenueAccounts(AccountRepositoryInterface $repository)
+ {
+ $start = clone session('start', Carbon::now()->startOfMonth());
+ $end = clone session('end', Carbon::now()->endOfMonth());
+ $cache = new CacheProperties;
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('revenueAccounts');
+ $cache->addProperty('accounts');
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+ $accounts = $repository->getAccountsByType([AccountType::REVENUE]);
+
+ $start->subDay();
+ $ids = $accounts->pluck('id')->toArray();
+ $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);
+ }
+ );
+
+
+ $accounts = $accounts->sortByDesc(
+ function (Account $account) {
+ return $account->difference;
+ }
+ );
+
+ $data = $this->generator->revenueAccounts($accounts, $start, $end);
+ $cache->store($data);
+
+ return Response::json($data);
+ }
+
/**
* Shows an account's balance for a single month.
*
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index 8f8d40a535..8afd02006f 100644
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -138,9 +138,10 @@ class HomeController extends Controller
/** @var Carbon $start */
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
- $end = session('end', Carbon::now()->endOfMonth());
- $showTour = Preferences::get('tour', true)->data;
- $accounts = $repository->getAccountsById($frontPage->data);
+ $end = session('end', Carbon::now()->endOfMonth());
+ $showTour = Preferences::get('tour', true)->data;
+ $accounts = $repository->getAccountsById($frontPage->data);
+ $showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
foreach ($accounts as $account) {
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
@@ -152,7 +153,7 @@ class HomeController extends Controller
}
return view(
- 'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions')
+ 'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage')
);
}
@@ -195,6 +196,18 @@ class HomeController extends Controller
return '
';
}
+ /**
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ */
+ public function testFlash()
+ {
+ Session::flash('success', 'This is a success message.');
+ Session::flash('info', 'This is an info message.');
+ Session::flash('warning', 'This is a warning.');
+ Session::flash('error', 'This is an error!');
+
+ return redirect(route('home'));
+ }
/**
* @param array $array
diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php
index 65465a8e9f..5eea913966 100644
--- a/app/Http/Controllers/JsonController.php
+++ b/app/Http/Controllers/JsonController.php
@@ -20,7 +20,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
-use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Input;
@@ -270,17 +270,17 @@ class JsonController extends Controller
}
/**
- * @param JournalRepositoryInterface $repository
+ * @param JournalTaskerInterface $tasker
* @param $what
*
* @return \Symfony\Component\HttpFoundation\Response
*/
- public function transactionJournals(JournalRepositoryInterface $repository, $what)
+ public function transactionJournals(JournalTaskerInterface $tasker, $what)
{
$descriptions = [];
$type = config('firefly.transactionTypesByWhat.' . $what);
$types = [$type];
- $journals = $repository->getJournals($types, 1, 50);
+ $journals = $tasker->getJournals($types, 1, 50);
foreach ($journals as $j) {
$descriptions[] = $j->description;
}
diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php
index 59e79a74c7..ae9f639afb 100644
--- a/app/Http/Controllers/PiggyBankController.php
+++ b/app/Http/Controllers/PiggyBankController.php
@@ -168,6 +168,7 @@ class PiggyBankController extends Controller
'account_id' => $piggyBank->account_id,
'targetamount' => $piggyBank->targetamount,
'targetdate' => $targetDate,
+ 'note' => $piggyBank->notes()->first()->text,
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'piggy-banks');
@@ -346,10 +347,11 @@ class PiggyBankController extends Controller
*/
public function show(PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
{
+ $note = $piggyBank->notes()->first();
$events = $repository->getEvents($piggyBank);
$subTitle = e($piggyBank->name);
- return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle'));
+ return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'note'));
}
@@ -369,6 +371,7 @@ class PiggyBankController extends Controller
'targetamount' => round($request->get('targetamount'), 2),
'order' => $repository->getMaxOrder() + 1,
'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null,
+ 'note' => $request->get('note'),
];
$piggyBank = $repository->store($piggyBankData);
@@ -402,6 +405,7 @@ class PiggyBankController extends Controller
'account_id' => intval($request->get('account_id')),
'targetamount' => round($request->get('targetamount'), 2),
'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null,
+ 'note' => $request->get('note'),
];
$piggyBank = $repository->update($piggyBank, $piggyBankData);
diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php
index 269eb9546b..ae371c7e98 100644
--- a/app/Http/Controllers/PreferencesController.php
+++ b/app/Http/Controllers/PreferencesController.php
@@ -76,26 +76,27 @@ class PreferencesController extends Controller
*/
public function index(AccountRepositoryInterface $repository)
{
- $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
- $viewRangePref = Preferences::get('viewRange', '1M');
- $viewRange = $viewRangePref->data;
- $frontPageAccounts = Preferences::get('frontPageAccounts', []);
- $language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
- $transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
- $customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
- $fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
- $fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
- $tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
- $is2faEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; // twoFactorAuthEnabled
- $has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); // hasTwoFactorAuthSecret
- $showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
+ $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
+ $viewRangePref = Preferences::get('viewRange', '1M');
+ $viewRange = $viewRangePref->data;
+ $frontPageAccounts = Preferences::get('frontPageAccounts', []);
+ $language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
+ $transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
+ $customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
+ $showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
+ $fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
+ $fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
+ $tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
+ $is2faEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; // twoFactorAuthEnabled
+ $has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); // hasTwoFactorAuthSecret
+ $showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
return view(
'preferences.index',
compact(
'language', 'accounts', 'frontPageAccounts', 'tjOptionalFields',
'viewRange', 'customFiscalYear', 'transactionPageSize', 'fiscalYearStart', 'is2faEnabled',
- 'has2faSecret', 'showIncomplete'
+ 'has2faSecret', 'showIncomplete', 'showDepositsFrontpage'
)
);
}
@@ -145,6 +146,10 @@ class PreferencesController extends Controller
Preferences::set('customFiscalYear', $customFiscalYear);
Preferences::set('fiscalYearStart', $fiscalYearStart);
+ // show deposits frontpage:
+ $showDepositsFrontpage = intval($request->get('showDepositsFrontpage')) === 1;
+ Preferences::set('showDepositsFrontpage', $showDepositsFrontpage);
+
// save page size:
$transactionPageSize = intval($request->get('transactionPageSize'));
if ($transactionPageSize > 0 && $transactionPageSize < 1337) {
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index 24939b2406..5d368c09f5 100644
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -13,7 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
-use FireflyIII\Events\UserIsDeleted;
+use FireflyIII\Events\DeletedUser;
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest;
use FireflyIII\User;
@@ -35,6 +35,9 @@ class ProfileController extends Controller
public function __construct()
{
parent::__construct();
+
+ View::share('title', trans('firefly.profile'));
+ View::share('mainTitleIcon', 'fa-user');
}
/**
@@ -63,7 +66,10 @@ class ProfileController extends Controller
*/
public function index()
{
- return view('profile.index')->with('title', trans('firefly.profile'))->with('subTitle', auth()->user()->email)->with('mainTitleIcon', 'fa-user');
+ $subTitle = auth()->user()->email;
+ $userId = auth()->user()->id;
+
+ return view('profile.index', compact('subTitle', 'userId'));
}
/**
@@ -110,9 +116,6 @@ class ProfileController extends Controller
return redirect(route('profile.delete-account'));
}
- // respond to deletion:
- event(new UserIsDeleted(auth()->user(), $request->ip()));
-
// store some stuff for the future:
$registration = Preferences::get('registration_ip_address')->data;
$confirmation = Preferences::get('confirmation_ip_address')->data;
diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php
index 9834dab7c0..ee09d7e9cd 100644
--- a/app/Http/Controllers/ReportController.php
+++ b/app/Http/Controllers/ReportController.php
@@ -153,8 +153,6 @@ class ReportController extends Controller
*/
private function auditReport(Carbon $start, Carbon $end, Collection $accounts)
{
- /** @var ARI $repos */
- $repos = app(ARI::class);
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
$auditData = [];
diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php
new file mode 100644
index 0000000000..c46d50167b
--- /dev/null
+++ b/app/Http/Controllers/Transaction/SingleController.php
@@ -0,0 +1,331 @@
+middleware(
+ function ($request, $next) {
+ $this->accounts = app(AccountRepositoryInterface::class);
+ $this->budgets = app(BudgetRepositoryInterface::class);
+ $this->piggyBanks = app(PiggyBankRepositoryInterface::class);
+ $this->attachments = app(AttachmentHelperInterface::class);
+
+ return $next($request);
+ }
+ );
+
+
+ }
+
+ /**
+ * @param string $what
+ *
+ * @return View
+ */
+ public function create(string $what = TransactionType::DEPOSIT)
+ {
+ $what = strtolower($what);
+ $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
+ $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
+ $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
+ $piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount();
+ $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
+ $preFilled = Session::has('preFilled') ? session('preFilled') : [];
+ $subTitle = trans('form.add_new_' . $what);
+ $subTitleIcon = 'fa-plus';
+ $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
+
+ Session::put('preFilled', $preFilled);
+
+ // put previous url in session if not redirect from store (not "create another").
+ if (session('transactions.create.fromStore') !== true) {
+ $url = URL::previous();
+ Session::put('transactions.create.url', $url);
+ }
+ Session::forget('transactions.create.fromStore');
+ Session::flash('gaEventCategory', 'transactions');
+ Session::flash('gaEventAction', 'create-' . $what);
+
+ asort($piggies);
+
+ return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields'));
+ }
+
+ /**
+ * Shows the form that allows a user to delete a transaction journal.
+ *
+ * @param TransactionJournal $journal
+ *
+ * @return View
+ */
+ public function delete(TransactionJournal $journal)
+ {
+ $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
+ $subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]);
+
+ // put previous url in session
+ Session::put('transactions.delete.url', URL::previous());
+ Session::flash('gaEventCategory', 'transactions');
+ Session::flash('gaEventAction', 'delete-' . $what);
+
+ return view('transactions.delete', compact('journal', 'subTitle', 'what'));
+
+
+ }
+
+ /**
+ * @param JournalRepositoryInterface $repository
+ * @param TransactionJournal $transactionJournal
+ *
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal)
+ {
+ $type = TransactionJournal::transactionTypeStr($transactionJournal);
+ Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)])));
+
+ $repository->delete($transactionJournal);
+
+ Preferences::mark();
+
+ // redirect to previous URL:
+ return redirect(session('transactions.delete.url'));
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ *
+ * @return mixed
+ */
+ public function edit(TransactionJournal $journal)
+ {
+ $count = $journal->transactions()->count();
+ if ($count > 2) {
+ return redirect(route('transactions.edit-split', [$journal->id]));
+ }
+
+ $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
+ $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
+ $piggyBankList = ExpandedForm::makeSelectListWithEmpty($this->piggyBanks->getPiggyBanks());
+
+ // view related code
+ $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
+ $what = strtolower(TransactionJournal::transactionTypeStr($journal));
+
+ // journal related code
+ $sourceAccounts = TransactionJournal::sourceAccountList($journal);
+ $destinationAccounts = TransactionJournal::destinationAccountList($journal);
+ $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
+ $preFilled = [
+ 'date' => TransactionJournal::dateAsString($journal),
+ 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'),
+ 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'),
+ 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'),
+ 'category' => TransactionJournal::categoryAsString($journal),
+ 'budget_id' => TransactionJournal::budgetId($journal),
+ 'piggy_bank_id' => TransactionJournal::piggyBankId($journal),
+ 'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
+ 'source_account_id' => $sourceAccounts->first()->id,
+ 'source_account_name' => $sourceAccounts->first()->name,
+ 'destination_account_id' => $destinationAccounts->first()->id,
+ 'destination_account_name' => $destinationAccounts->first()->name,
+ 'amount' => TransactionJournal::amountPositive($journal),
+
+ // new custom fields:
+ 'due_date' => TransactionJournal::dateAsString($journal, 'due_date'),
+ 'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'),
+ 'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'),
+ 'interal_reference' => $journal->getMeta('internal_reference'),
+ 'notes' => $journal->getMeta('notes'),
+ ];
+
+ if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
+ $preFilled['destination_account_name'] = '';
+ }
+
+ if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) {
+ $preFilled['source_account_name'] = '';
+ }
+
+
+ Session::flash('preFilled', $preFilled);
+ Session::flash('gaEventCategory', 'transactions');
+ Session::flash('gaEventAction', 'edit-' . $what);
+
+ // put previous url in session if not redirect from store (not "return_to_edit").
+ if (session('transactions.edit.fromUpdate') !== true) {
+ Session::put('transactions.edit.url', URL::previous());
+ }
+ Session::forget('transactions.edit.fromUpdate');
+
+ return view(
+ 'transactions.edit',
+ compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle')
+ )->with('data', $preFilled);
+ }
+
+ /**
+ * @param JournalFormRequest $request
+ * @param JournalRepositoryInterface $repository
+ *
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
+ {
+ $doSplit = intval($request->get('split_journal')) === 1;
+ $createAnother = intval($request->get('create_another')) === 1;
+ $data = $request->getJournalData();
+ $journal = $repository->store($data);
+ if (is_null($journal->id)) {
+ // error!
+ Log::error('Could not store transaction journal: ', $journal->getErrors()->toArray());
+ Session::flash('error', $journal->getErrors()->first());
+
+ return redirect(route('transactions.create', [$request->input('what')]))->withInput();
+ }
+
+ $this->attachments->saveAttachmentsForModel($journal);
+
+ // store the journal only, flash the rest.
+ if (count($this->attachments->getErrors()->get('attachments')) > 0) {
+ Session::flash('error', $this->attachments->getErrors()->get('attachments'));
+ }
+ // flash messages
+ if (count($this->attachments->getMessages()->get('attachments')) > 0) {
+ Session::flash('info', $this->attachments->getMessages()->get('attachments'));
+ }
+
+ event(new StoredTransactionJournal($journal, $data['piggy_bank_id']));
+
+ Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
+ Preferences::mark();
+
+ if ($createAnother === true) {
+ // set value so create routine will not overwrite URL:
+ Session::put('transactions.create.fromStore', true);
+
+ return redirect(route('transactions.create', [$request->input('what')]))->withInput();
+ }
+
+ if ($doSplit === true) {
+ // redirect to edit screen:
+ return redirect(route('transactions.edit-split', [$journal->id]));
+ }
+
+
+ // redirect to previous URL.
+ return redirect(session('transactions.create.url'));
+
+ }
+
+ /**
+ * @param JournalFormRequest $request
+ * @param JournalRepositoryInterface $repository
+ * @param TransactionJournal $journal
+ *
+ * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ */
+ public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
+ {
+ $data = $request->getJournalData();
+ $journal = $repository->update($journal, $data);
+ $this->attachments->saveAttachmentsForModel($journal);
+
+ // flash errors
+ if (count($this->attachments->getErrors()->get('attachments')) > 0) {
+ Session::flash('error', $this->attachments->getErrors()->get('attachments'));
+ }
+ // flash messages
+ if (count($this->attachments->getMessages()->get('attachments')) > 0) {
+ Session::flash('info', $this->attachments->getMessages()->get('attachments'));
+ }
+
+ event(new UpdatedTransactionJournal($journal));
+ // update, get events by date and sort DESC
+
+ $type = strtolower(TransactionJournal::transactionTypeStr($journal));
+ Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])])));
+ Preferences::mark();
+
+ // if wishes to split:
+
+
+ if (intval($request->get('return_to_edit')) === 1) {
+ // set value so edit routine will not overwrite URL:
+ Session::put('transactions.edit.fromUpdate', true);
+
+ return redirect(route('transactions.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
+ }
+
+ // redirect to previous URL.
+ return redirect(session('transactions.edit.url'));
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php
index 17f5994d39..a50e31ccbf 100644
--- a/app/Http/Controllers/Transaction/SplitController.php
+++ b/app/Http/Controllers/Transaction/SplitController.php
@@ -15,19 +15,17 @@ namespace FireflyIII\Http\Controllers\Transaction;
use ExpandedForm;
-use FireflyIII\Crud\Split\JournalInterface;
-use FireflyIII\Events\TransactionJournalUpdated;
+use FireflyIII\Events\UpdatedTransactionJournal;
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Http\Controllers\Controller;
-use FireflyIII\Http\Requests\SplitJournalFormRequest;
-use FireflyIII\Models\Account;
-use FireflyIII\Models\Transaction;
+use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
-use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
+use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
+use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Http\Request;
-use Illuminate\Support\Collection;
-use Log;
use Preferences;
use Session;
use Steam;
@@ -42,6 +40,22 @@ use View;
*/
class SplitController extends Controller
{
+
+ /** @var AccountRepositoryInterface */
+ private $accounts;
+
+ /** @var AttachmentHelperInterface */
+ private $attachments;
+
+ /** @var BudgetRepositoryInterface */
+ private $budgets;
+
+ /** @var CurrencyRepositoryInterface */
+ private $currencies;
+
+ /** @var JournalTaskerInterface */
+ private $tasker;
+
/**
*
*/
@@ -50,47 +64,18 @@ class SplitController extends Controller
parent::__construct();
View::share('mainTitleIcon', 'fa-share-alt');
View::share('title', trans('firefly.split-transactions'));
- }
- /**
- * @param TransactionJournal $journal
- *
- * @return View
- */
- public function create(TransactionJournal $journal)
- {
- $currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface');
- $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
- $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
+ // some useful repositories:
+ $this->middleware(
+ function ($request, $next) {
+ $this->accounts = app(AccountRepositoryInterface::class);
+ $this->budgets = app(BudgetRepositoryInterface::class);
+ $this->tasker = app(JournalTaskerInterface::class);
+ $this->attachments = app(AttachmentHelperInterface::class);
+ $this->currencies = app(CurrencyRepositoryInterface::class);
- /** @var AccountRepositoryInterface $accountRepository */
- $accountRepository = app(AccountRepositoryInterface::class);
-
- $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account']));
- $sessionData = session('journal-data', []);
- $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
- $currencies = ExpandedForm::makeSelectList($currencyRepository->get());
- $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
- $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanksWithAmount());
- $subTitle = trans('form.add_new_' . $sessionData['what']);
- $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
- $subTitleIcon = 'fa-plus';
- $preFilled = [
- 'what' => $sessionData['what'] ?? 'withdrawal',
- 'journal_amount' => $sessionData['amount'] ?? 0,
- 'journal_source_account_id' => $sessionData['source_account_id'] ?? 0,
- 'journal_source_account_name' => $sessionData['source_account_name'] ?? '',
- 'description' => [$journal->description],
- 'destination_account_name' => [$sessionData['destination_account_name']],
- 'destination_account_id' => [$sessionData['destination_account_id']],
- 'amount' => [$sessionData['amount']],
- 'budget_id' => [$sessionData['budget_id']],
- 'category' => [$sessionData['category']],
- ];
-
- return view(
- 'split.journals.create',
- compact('journal', 'piggyBanks', 'subTitle', 'optionalFields', 'subTitleIcon', 'preFilled', 'assetAccounts', 'currencies', 'budgets', 'uploadSize')
+ return $next($request);
+ }
);
}
@@ -102,17 +87,11 @@ class SplitController extends Controller
*/
public function edit(Request $request, TransactionJournal $journal)
{
- $currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface');
- $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
-
- /** @var AccountRepositoryInterface $accountRepository */
- $accountRepository = app(AccountRepositoryInterface::class);
-
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
- $currencies = ExpandedForm::makeSelectList($currencyRepository->get());
- $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account']));
+ $currencies = ExpandedForm::makeSelectList($this->currencies->get());
+ $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
- $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
+ $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
$preFilled = $this->arrayFromJournal($request, $journal);
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
$subTitleIcon = 'fa-pencil';
@@ -127,7 +106,7 @@ class SplitController extends Controller
Session::forget('transactions.edit-split.fromUpdate');
return view(
- 'split.journals.edit',
+ 'transactions.edit-split',
compact(
'subTitleIcon', 'currencies', 'optionalFields',
'preFilled', 'subTitle', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts',
@@ -136,63 +115,31 @@ class SplitController extends Controller
);
}
- /**
- * @param JournalInterface $repository
- * @param SplitJournalFormRequest $request
- * @param TransactionJournal $journal
- *
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
- */
- public function store(JournalInterface $repository, SplitJournalFormRequest $request, TransactionJournal $journal)
- {
- $data = $request->getSplitData();
- foreach ($data['transactions'] as $transaction) {
- $repository->storeTransaction($journal, $transaction);
- }
-
- $repository->markAsComplete($journal);
-
- Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
- Preferences::mark();
-
- if (intval($request->get('create_another')) === 1) {
- // set value so create routine will not overwrite URL:
- Session::put('transactions.create.fromStore', true);
-
- return redirect(route('transactions.create', [$request->input('what')]))->withInput();
- }
-
- // redirect to previous URL.
- return redirect(session('transactions.create.url'));
- }
/**
- * @param TransactionJournal $journal
- * @param SplitJournalFormRequest $request
- * @param JournalInterface $repository
- * @param AttachmentHelperInterface $att
+ * @param Request $request
+ * @param JournalRepositoryInterface $repository
+ * @param TransactionJournal $journal
*
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
- public function update(TransactionJournal $journal, SplitJournalFormRequest $request, JournalInterface $repository, AttachmentHelperInterface $att)
+ public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
{
-
- $data = $request->getSplitData();
- $journal = $repository->updateJournal($journal, $data);
+ $data = $this->arrayFromInput($request);
+ $journal = $repository->updateSplitJournal($journal, $data);
// save attachments:
- $att->saveAttachmentsForModel($journal);
+ $this->attachments->saveAttachmentsForModel($journal);
- event(new TransactionJournalUpdated($journal));
+ event(new UpdatedTransactionJournal($journal));
// update, get events by date and sort DESC
// flash messages
- if (count($att->getMessages()->get('attachments')) > 0) {
- Session::flash('info', $att->getMessages()->get('attachments'));
+ if (count($this->attachments->getMessages()->get('attachments')) > 0) {
+ Session::flash('info', $this->attachments->getMessages()->get('attachments'));
}
-
- $type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal));
+ $type = strtolower(TransactionJournal::transactionTypeStr($journal));
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])])));
Preferences::mark();
@@ -200,7 +147,7 @@ class SplitController extends Controller
// set value so edit routine will not overwrite URL:
Session::put('transactions.edit-split.fromUpdate', true);
- return redirect(route('split.journal.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
+ return redirect(route('transactions.edit-split', [$journal->id]))->withInput(['return_to_edit' => 1]);
}
// redirect to previous URL.
@@ -208,6 +155,39 @@ class SplitController extends Controller
}
+ /**
+ * @param Request $request
+ *
+ * @return array
+ */
+ private function arrayFromInput(Request $request): array
+ {
+ $array = [
+ 'journal_description' => $request->get('journal_description'),
+ 'journal_source_account_id' => $request->get('journal_source_account_id'),
+ 'journal_source_account_name' => $request->get('journal_source_account_name'),
+ 'journal_destination_account_id' => $request->get('journal_destination_account_id'),
+ 'currency_id' => $request->get('currency_id'),
+ 'what' => $request->get('what'),
+ 'date' => $request->get('date'),
+ // all custom fields:
+ 'interest_date' => $request->get('interest_date'),
+ 'book_date' => $request->get('book_date'),
+ 'process_date' => $request->get('process_date'),
+ 'due_date' => $request->get('due_date'),
+ 'payment_date' => $request->get('payment_date'),
+ 'invoice_date' => $request->get('invoice_date'),
+ 'internal_reference' => $request->get('internal_reference'),
+ 'notes' => $request->get('notes'),
+ 'tags' => explode(',', $request->get('tags')),
+
+ // transactions.
+ 'transactions' => $this->getTransactionDataFromRequest($request),
+ ];
+
+ return $array;
+ }
+
/**
* @param Request $request
* @param TransactionJournal $journal
@@ -222,149 +202,79 @@ class SplitController extends Controller
'journal_description' => $request->old('journal_description', $journal->description),
'journal_amount' => TransactionJournal::amountPositive($journal),
'sourceAccounts' => $sourceAccounts,
- 'journal_source_account_id' => $sourceAccounts->first()->id,
- 'journal_source_account_name' => $sourceAccounts->first()->name,
- 'journal_destination_account_id' => $destinationAccounts->first()->id,
- 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id),
+ 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id),
+ 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name),
+ 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id),
+ 'currency_id' => $request->old('currency_id', $journal->transaction_currency_id),
'destinationAccounts' => $destinationAccounts,
'what' => strtolower(TransactionJournal::transactionTypeStr($journal)),
'date' => $request->old('date', $journal->date),
- 'interest_date' => $request->old('interest_date', $journal->interest_date),
- 'book_date' => $request->old('book_date', $journal->book_date),
- 'process_date' => $request->old('process_date', $journal->process_date),
- 'description' => [],
- 'source_account_id' => [],
- 'source_account_name' => [],
- 'destination_account_id' => [],
- 'destination_account_name' => [],
- 'amount' => [],
- 'budget_id' => [],
- 'category' => [],
+ 'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
+
+ // all custom fields:
+ 'interest_date' => $request->old('interest_date', $journal->getMeta('interest_date')),
+ 'book_date' => $request->old('book_date', $journal->getMeta('book_date')),
+ 'process_date' => $request->old('process_date', $journal->getMeta('process_date')),
+ 'due_date' => $request->old('due_date', $journal->getMeta('due_date')),
+ 'payment_date' => $request->old('payment_date', $journal->getMeta('payment_date')),
+ 'invoice_date' => $request->old('invoice_date', $journal->getMeta('invoice_date')),
+ 'internal_reference' => $request->old('internal_reference', $journal->getMeta('internal_reference')),
+ 'notes' => $request->old('notes', $journal->getMeta('notes')),
+
+ // transactions.
+ 'transactions' => $this->getTransactionDataFromJournal($journal),
];
- // number of transactions present in old input:
- $previousCount = count($request->old('description'));
-
- if ($previousCount === 0) {
- // build from scratch
- $transactions = $this->transactionsFromJournal($request, $journal);
- $array['description'] = $transactions['description'];
- $array['source_account_id'] = $transactions['source_account_id'];
- $array['source_account_name'] = $transactions['source_account_name'];
- $array['destination_account_id'] = $transactions['destination_account_id'];
- $array['destination_account_name'] = $transactions['destination_account_name'];
- $array['amount'] = $transactions['amount'];
- $array['budget_id'] = $transactions['budget_id'];
- $array['category'] = $transactions['category'];
-
- return $array;
- }
-
- $index = 0;
- while ($index < $previousCount) {
- $description = $request->old('description')[$index] ?? '';
- $destinationId = $request->old('destination_account_id')[$index] ?? 0;
- $destinationName = $request->old('destination_account_name')[$index] ?? '';
- $sourceId = $request->old('source_account_id')[$index] ?? 0;
- $sourceName = $request->old('source_account_name')[$index] ?? '';
- $amount = $request->old('amount')[$index] ?? '';
- $budgetId = $request->old('budget_id')[$index] ?? 0;
- $categoryName = $request->old('category')[$index] ?? '';
-
-
- // any transfer not from the source:
- $array['description'][] = $description;
- $array['source_account_id'][] = $sourceId;
- $array['source_account_name'][] = $sourceName;
- $array['destination_account_id'][] = $destinationId;
- $array['destination_account_name'][] = $destinationName;
- $array['amount'][] = $amount;
- $array['budget_id'][] = intval($budgetId);
- $array['category'][] = $categoryName;
- $index++;
- }
-
return $array;
}
/**
- * @param Request $request
* @param TransactionJournal $journal
*
* @return array
*/
- private function transactionsFromJournal(Request $request, TransactionJournal $journal): array
+ private function getTransactionDataFromJournal(TransactionJournal $journal): array
{
- /** @var Collection $transactions */
- $transactions = $journal->transactions()->get();
-
- /*
- * Splitted journals always have ONE source OR ONE destination.
- * Withdrawals have ONE source (asset account)
- * Deposits have ONE destination (asset account)
- * Transfers have ONE of both (asset account)
- */
- /** @var Account $singular */
- $singular = TransactionJournal::sourceAccountList($journal)->first();
- if ($journal->transactionType->type == TransactionType::DEPOSIT) {
- /** @var Account $singular */
- $singular = TransactionJournal::destinationAccountList($journal)->first();
+ $transactions = $this->tasker->getTransactionsOverview($journal);
+ $return = [];
+ /** @var array $transaction */
+ foreach ($transactions as $transaction) {
+ $return[] = [
+ 'description' => $transaction['description'],
+ 'source_account_id' => $transaction['source_account_id'],
+ 'source_account_name' => $transaction['source_account_name'],
+ 'destination_account_id' => $transaction['destination_account_id'],
+ 'destination_account_name' => $transaction['destination_account_name'],
+ 'amount' => round($transaction['destination_amount'], 2),
+ 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
+ 'category' => $transaction['category'],
+ ];
}
- /*
- * Loop all transactions. Collect info ONLY from the transaction that is NOT related to
- * the singular account.
- */
- $index = 0;
- $return = [
- 'description' => [],
- 'source_account_id' => [],
- 'source_account_name' => [],
- 'destination_account_id' => [],
- 'destination_account_name' => [],
- 'amount' => [],
- 'budget_id' => [],
- 'category' => [],
- ];
+ return $return;
+ }
- Log::debug('now at transactionsFromJournal');
-
- /**
- * @var int $current
- * @var Transaction $transaction
- */
- foreach ($transactions as $current => $transaction) {
- $budget = $transaction->budgets()->first();
- $category = $transaction->categories()->first();
- $budgetId = 0;
- $categoryName = '';
- if (!is_null($budget)) {
- $budgetId = $budget->id;
- }
-
- if (!is_null($category)) {
- $categoryName = $category->name;
- }
-
- $budgetId = $request->old('budget_id')[$index] ?? $budgetId;
- $categoryName = $request->old('category')[$index] ?? $categoryName;
- $amount = $request->old('amount')[$index] ?? $transaction->amount;
- $description = $request->old('description')[$index] ?? $transaction->description;
- $destinationName = $request->old('destination_account_name')[$index] ?? $transaction->account->name;
- $sourceName = $request->old('source_account_name')[$index] ?? $transaction->account->name;
- $amount = bccomp($amount, '0') === -1 ? bcmul($amount, '-1') : $amount;
-
- if ($transaction->account_id !== $singular->id) {
- $return['description'][] = $description;
- $return['destination_account_id'][] = $transaction->account_id;
- $return['destination_account_name'][] = $destinationName;
- $return['source_account_name'][] = $sourceName;
- $return['amount'][] = $amount;
- $return['budget_id'][] = intval($budgetId);
- $return['category'][] = $categoryName;
- // only add one when "valid" transaction
- $index++;
- }
+ /**
+ * @param Request $request
+ *
+ * @return array
+ */
+ private function getTransactionDataFromRequest(Request $request): array
+ {
+ $return = [];
+ $transactions = $request->get('transactions');
+ foreach ($transactions as $transaction) {
+ $return[] = [
+ 'description' => $transaction['description'],
+ 'source_account_id' => $transaction['source_account_id'] ?? 0,
+ 'source_account_name' => $transaction['source_account_name'] ?? '',
+ 'destination_account_id' => $transaction['destination_account_id'] ?? 0,
+ 'destination_account_name' => $transaction['destination_account_name'] ?? '',
+ 'amount' => round($transaction['amount'] ?? 0, 2),
+ 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
+ 'category' => $transaction['category'] ?? '',
+ 'user' => auth()->user()->id, // needed for accounts.
+ ];
}
return $return;
diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php
index 98ce9f9317..8cd732a617 100644
--- a/app/Http/Controllers/TransactionController.php
+++ b/app/Http/Controllers/TransactionController.php
@@ -14,22 +14,12 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
-use ExpandedForm;
-use FireflyIII\Events\TransactionJournalStored;
-use FireflyIII\Events\TransactionJournalUpdated;
-use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
-use FireflyIII\Http\Requests\JournalFormRequest;
-use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
-use FireflyIII\Models\TransactionType;
-use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Http\Request;
use Preferences;
use Response;
-use Session;
-use Steam;
-use URL;
use View;
/**
@@ -40,7 +30,7 @@ use View;
class TransactionController extends Controller
{
/**
- *
+ * TransactionController constructor.
*/
public function __construct()
{
@@ -48,187 +38,23 @@ class TransactionController extends Controller
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat');
- $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
- $maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
- $uploadSize = min($maxFileSize, $maxPostSize);
-
- View::share('uploadSize', $uploadSize);
}
/**
- * @param string $what
+ * @param Request $request
+ * @param JournalTaskerInterface $tasker
+ * @param string $what
*
* @return View
*/
- public function create(string $what = TransactionType::DEPOSIT)
- {
- /** @var AccountRepositoryInterface $accountRepository */
- $accountRepository = app(AccountRepositoryInterface::class);
- $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
- $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
- $what = strtolower($what);
- $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
- $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getActiveAccountsByType(['Default account', 'Asset account']));
- $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
- $piggyBanks = $piggyRepository->getPiggyBanksWithAmount();
- $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
- $preFilled = Session::has('preFilled') ? session('preFilled') : [];
- $subTitle = trans('form.add_new_' . $what);
- $subTitleIcon = 'fa-plus';
- $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
-
- Session::put('preFilled', $preFilled);
-
- // put previous url in session if not redirect from store (not "create another").
- if (session('transactions.create.fromStore') !== true) {
- $url = URL::previous();
- Session::put('transactions.create.url', $url);
- }
- Session::forget('transactions.create.fromStore');
- Session::flash('gaEventCategory', 'transactions');
- Session::flash('gaEventAction', 'create-' . $what);
-
- asort($piggies);
-
-
- return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields'));
- }
-
- /**
- * Shows the form that allows a user to delete a transaction journal.
- *
- * @param TransactionJournal $journal
- *
- * @return View
- */
- public function delete(TransactionJournal $journal)
- {
- $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
- $subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]);
-
- // put previous url in session
- Session::put('transactions.delete.url', URL::previous());
- Session::flash('gaEventCategory', 'transactions');
- Session::flash('gaEventAction', 'delete-' . $what);
-
- return view('transactions.delete', compact('journal', 'subTitle', 'what'));
-
-
- }
-
- /**
- * @param JournalRepositoryInterface $repository
- * @param TransactionJournal $transactionJournal
- *
- * @return \Illuminate\Http\RedirectResponse
- */
- public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal)
- {
- $type = TransactionJournal::transactionTypeStr($transactionJournal);
- Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)])));
-
- $repository->delete($transactionJournal);
-
- Preferences::mark();
-
- // redirect to previous URL:
- return redirect(session('transactions.delete.url'));
- }
-
- /**
- * @param TransactionJournal $journal
- *
- * @return mixed
- */
- public function edit(TransactionJournal $journal)
- {
- $count = $journal->transactions()->count();
- if ($count > 2) {
- return redirect(route('split.journal.edit', [$journal->id]));
- }
-
- // code to get list data:
- $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
- $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
-
- $assetAccounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account']));
- $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
- $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks());
-
- // view related code
- $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
- $what = strtolower(TransactionJournal::transactionTypeStr($journal));
-
- // journal related code
- $sourceAccounts = TransactionJournal::sourceAccountList($journal);
- $destinationAccounts = TransactionJournal::destinationAccountList($journal);
- $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
- $preFilled = [
- 'date' => TransactionJournal::dateAsString($journal),
- 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'),
- 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'),
- 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'),
- 'category' => TransactionJournal::categoryAsString($journal),
- 'budget_id' => TransactionJournal::budgetId($journal),
- 'piggy_bank_id' => TransactionJournal::piggyBankId($journal),
- 'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
- 'source_account_id' => $sourceAccounts->first()->id,
- 'source_account_name' => $sourceAccounts->first()->name,
- 'destination_account_id' => $destinationAccounts->first()->id,
- 'destination_account_name' => $destinationAccounts->first()->name,
- 'amount' => TransactionJournal::amountPositive($journal),
-
- // new custom fields:
- 'due_date' => TransactionJournal::dateAsString($journal, 'due_date'),
- 'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'),
- 'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'),
- 'interal_reference' => $journal->getMeta('internal_reference'),
- 'notes' => $journal->getMeta('notes'),
- ];
-
- if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
- $preFilled['destination_account_name'] = '';
- }
-
- if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) {
- $preFilled['source_account_name'] = '';
- }
-
-
- Session::flash('preFilled', $preFilled);
- Session::flash('gaEventCategory', 'transactions');
- Session::flash('gaEventAction', 'edit-' . $what);
-
- // put previous url in session if not redirect from store (not "return_to_edit").
- if (session('transactions.edit.fromUpdate') !== true) {
- Session::put('transactions.edit.url', URL::previous());
- }
- Session::forget('transactions.edit.fromUpdate');
-
- return view(
- 'transactions.edit',
- compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle')
- )->with('data', $preFilled);
- }
-
- /**
- * @param Request $request
- * @param JournalRepositoryInterface $repository
- * @param string $what
- *
- * @return View
- */
- public function index(Request $request, JournalRepositoryInterface $repository, string $what)
+ public function index(Request $request, JournalTaskerInterface $tasker, string $what)
{
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
$types = config('firefly.transactionTypesByWhat.' . $what);
$subTitle = trans('firefly.title_' . $what);
$page = intval($request->get('page'));
- $journals = $repository->getJournals($types, $page, $pageSize);
+ $journals = $tasker->getJournals($types, $page, $pageSize);
$journals->setPath('transactions/' . $what);
@@ -265,139 +91,20 @@ class TransactionController extends Controller
}
/**
- * @param TransactionJournal $journal
- * @param JournalRepositoryInterface $repository
+ * @param TransactionJournal $journal
+ * @param JournalTaskerInterface $tasker
*
* @return View
*/
- public function show(TransactionJournal $journal, JournalRepositoryInterface $repository)
+ public function show(TransactionJournal $journal, JournalTaskerInterface $tasker)
{
- $events = $repository->getPiggyBankEvents($journal);
- $transactions = $repository->getTransactions($journal);
+ $events = $tasker->getPiggyBankEvents($journal);
+ $transactions = $tasker->getTransactionsOverview($journal);
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
- if ($transactions->count() > 2) {
- return view('split.journals.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
- }
-
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
}
-
- /**
- * @param JournalFormRequest $request
- * @param JournalRepositoryInterface $repository
- *
- * @return \Illuminate\Http\RedirectResponse
- */
- public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
- {
- $att = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface');
- $doSplit = intval($request->get('split_journal')) === 1;
- $journalData = $request->getJournalData();
-
- // store the journal only, flash the rest.
- if ($doSplit) {
- $journal = $repository->storeJournal($journalData);
- $journal->completed = false;
- $journal->save();
-
- // store attachments:
- $att->saveAttachmentsForModel($journal);
-
- // flash errors
- if (count($att->getErrors()->get('attachments')) > 0) {
- Session::flash('error', $att->getErrors()->get('attachments'));
- }
- // flash messages
- if (count($att->getMessages()->get('attachments')) > 0) {
- Session::flash('info', $att->getMessages()->get('attachments'));
- }
-
- Session::put('journal-data', $journalData);
-
- return redirect(route('split.journal.create', [$journal->id]));
- }
-
-
- // if not withdrawal, unset budgetid.
- if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) {
- $journalData['budget_id'] = 0;
- }
-
- $journal = $repository->store($journalData);
- $att->saveAttachmentsForModel($journal);
-
- // flash errors
- if (count($att->getErrors()->get('attachments')) > 0) {
- Session::flash('error', $att->getErrors()->get('attachments'));
- }
- // flash messages
- if (count($att->getMessages()->get('attachments')) > 0) {
- Session::flash('info', $att->getMessages()->get('attachments'));
- }
-
- event(new TransactionJournalStored($journal, intval($journalData['piggy_bank_id'])));
-
- Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
- Preferences::mark();
-
- if (intval($request->get('create_another')) === 1) {
- // set value so create routine will not overwrite URL:
- Session::put('transactions.create.fromStore', true);
-
- return redirect(route('transactions.create', [$request->input('what')]))->withInput();
- }
-
- // redirect to previous URL.
- return redirect(session('transactions.create.url'));
-
- }
-
-
- /**
- * @param JournalFormRequest $request
- * @param JournalRepositoryInterface $repository
- * @param AttachmentHelperInterface $att
- * @param TransactionJournal $journal
- *
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
- */
- public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal)
- {
- $journalData = $request->getJournalData();
- $repository->update($journal, $journalData);
-
- // save attachments:
- $att->saveAttachmentsForModel($journal);
-
- // flash errors
- if (count($att->getErrors()->get('attachments')) > 0) {
- Session::flash('error', $att->getErrors()->get('attachments'));
- }
- // flash messages
- if (count($att->getMessages()->get('attachments')) > 0) {
- Session::flash('info', $att->getMessages()->get('attachments'));
- }
-
- event(new TransactionJournalUpdated($journal));
- // update, get events by date and sort DESC
-
- $type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal));
- Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($journalData['description'])])));
- Preferences::mark();
-
- if (intval($request->get('return_to_edit')) === 1) {
- // set value so edit routine will not overwrite URL:
- Session::put('transactions.edit.fromUpdate', true);
-
- return redirect(route('transactions.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
- }
-
- // redirect to previous URL.
- return redirect(session('transactions.edit.url'));
-
- }
}
diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php
index 9f1a5fcdf5..289c0b77fa 100644
--- a/app/Http/Requests/JournalFormRequest.php
+++ b/app/Http/Requests/JournalFormRequest.php
@@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Carbon\Carbon;
-use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionType;
use Input;
@@ -37,86 +36,110 @@ class JournalFormRequest extends Request
}
/**
+ * Returns and validates the data required to store a new journal. Can handle both single transaction journals and split journals.
+ *
* @return array
*/
public function getJournalData()
{
- $tags = $this->getFieldOrEmptyString('tags');
+ $data = [
+ 'what' => $this->get('what'), // type. can be 'deposit', 'withdrawal' or 'transfer'
+ 'user' => auth()->user()->id,
+ 'date' => new Carbon($this->get('date')),
+ 'tags' => explode(',', $this->getFieldOrEmptyString('tags')),
+ 'currency_id' => intval($this->get('amount_currency_id_amount')),
- return [
- 'what' => $this->get('what'),
- 'description' => trim($this->get('description')),
- 'source_account_id' => intval($this->get('source_account_id')),
- 'source_account_name' => trim($this->getFieldOrEmptyString('source_account_name')),
- 'destination_account_id' => intval($this->get('destination_account_id')),
- 'destination_account_name' => trim($this->getFieldOrEmptyString('destination_account_name')),
- 'amount' => round($this->get('amount'), 2),
- 'user' => auth()->user()->id,
- 'amount_currency_id_amount' => intval($this->get('amount_currency_id_amount')),
- 'date' => new Carbon($this->get('date')),
- 'interest_date' => $this->getDateOrNull('interest_date'),
- 'book_date' => $this->getDateOrNull('book_date'),
- 'process_date' => $this->getDateOrNull('process_date'),
- 'budget_id' => intval($this->get('budget_id')),
- 'category' => trim($this->getFieldOrEmptyString('category')),
- 'tags' => explode(',', $tags),
- 'piggy_bank_id' => intval($this->get('piggy_bank_id')),
+ // all custom fields:
+ 'interest_date' => $this->getDateOrNull('interest_date'),
+ 'book_date' => $this->getDateOrNull('book_date'),
+ 'process_date' => $this->getDateOrNull('process_date'),
+ 'due_date' => $this->getDateOrNull('due_date'),
+ 'payment_date' => $this->getDateOrNull('payment_date'),
+ 'invoice_date' => $this->getDateOrNull('invoice_date'),
+ 'internal_reference' => trim(strval($this->get('internal_reference'))),
+ 'notes' => trim(strval($this->get('notes'))),
- // new custom fields here:
- 'due_date' => $this->getDateOrNull('due_date'),
- 'payment_date' => $this->getDateOrNull('payment_date'),
- 'invoice_date' => $this->getDateOrNull('invoice_date'),
- 'internal_reference' => trim(strval($this->get('internal_reference'))),
- 'notes' => trim(strval($this->get('notes'))),
+ // transaction / journal data:
+ 'description' => $this->getFieldOrEmptyString('description'),
+ 'amount' => round($this->get('amount'), 2),
+ 'budget_id' => intval($this->get('budget_id')),
+ 'category' => $this->getFieldOrEmptyString('category'),
+ 'source_account_id' => intval($this->get('source_account_id')),
+ 'source_account_name' => $this->getFieldOrEmptyString('source_account_name'),
+ 'destination_account_id' => $this->getFieldOrEmptyString('destination_account_id'),
+ 'destination_account_name' => $this->getFieldOrEmptyString('destination_account_name'),
+ 'piggy_bank_id' => intval($this->get('piggy_bank_id')),
];
+
+ return $data;
}
/**
* @return array
- * @throws Exception
*/
public function rules()
{
$what = Input::get('what');
$rules = [
- 'description' => 'required|min:1,max:255',
- 'what' => 'required|in:withdrawal,deposit,transfer',
- 'amount' => 'numeric|required|min:0.01',
- 'date' => 'required|date',
- 'process_date' => 'date',
- 'book_date' => 'date',
- 'interest_date' => 'date',
- 'category' => 'between:1,255',
- 'amount_currency_id_amount' => 'required|exists:transaction_currencies,id',
- 'piggy_bank_id' => 'numeric',
+ 'what' => 'required|in:withdrawal,deposit,transfer',
+ 'date' => 'required|date',
- // new custom fields here:
- 'due_date' => 'date',
- 'payment_date' => 'date',
- 'internal_reference' => 'min:1,max:255',
- 'notes' => 'min:1,max:65536',
+ // then, custom fields:
+ 'interest_date' => 'date',
+ 'book_date' => 'date',
+ 'process_date' => 'date',
+ 'due_date' => 'date',
+ 'payment_date' => 'date',
+ 'invoice_date' => 'date',
+ 'internal_reference' => 'min:1,max:255',
+ 'notes' => 'min:1,max:50000',
+ // and then transaction rules:
+ 'description' => 'required|between:1,255',
+ 'amount' => 'numeric|required|min:0.01',
+ 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id',
+ 'category' => 'between:1,255',
+ 'source_account_id' => 'numeric|belongsToUser:accounts,id',
+ 'source_account_name' => 'between:1,255',
+ 'destination_account_id' => 'numeric|belongsToUser:accounts,id',
+ 'destination_account_name' => 'between:1,255',
+ 'piggy_bank_id' => 'between:1,255',
];
+ // some rules get an upgrade depending on the type of data:
+ $rules = $this->enhanceRules($what, $rules);
+
+ return $rules;
+ }
+
+ /**
+ * Inspired by https://www.youtube.com/watch?v=WwnI0RS6J5A
+ *
+ * @param string $what
+ * @param array $rules
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ private function enhanceRules(string $what, array $rules): array
+ {
switch ($what) {
case strtolower(TransactionType::WITHDRAWAL):
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
$rules['destination_account_name'] = 'between:1,255';
- if (intval(Input::get('budget_id')) != 0) {
- $rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets';
- }
break;
case strtolower(TransactionType::DEPOSIT):
$rules['source_account_name'] = 'between:1,255';
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
break;
case strtolower(TransactionType::TRANSFER):
+ // this may not work:
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id';
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id';
break;
default:
- throw new FireflyException('Cannot handle transaction type of type ' . e($what) . '.');
+ throw new FireflyException('Cannot handle transaction type of type ' . e($what) . ' . ');
}
return $rules;
diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php
index 284269215c..44c4fc707e 100644
--- a/app/Http/breadcrumbs.php
+++ b/app/Http/breadcrumbs.php
@@ -25,6 +25,7 @@ use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
+use FireflyIII\User;
/**
* HOME
@@ -115,6 +116,13 @@ Breadcrumbs::register(
}
);
+Breadcrumbs::register(
+ 'admin.users.show', function (BreadCrumbGenerator $breadcrumbs, User $user) {
+ $breadcrumbs->parent('admin.users');
+ $breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', [$user->id]));
+}
+);
+
Breadcrumbs::register(
'admin.users.domains', function (BreadCrumbGenerator $breadcrumbs) {
$breadcrumbs->parent('admin.index');
@@ -598,9 +606,9 @@ Breadcrumbs::register(
* SPLIT
*/
Breadcrumbs::register(
- 'split.journal.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
+ 'transactions.edit-split', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
$breadcrumbs->parent('transactions.show', $journal);
- $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('split.journal.edit', [$journal->id]));
+ $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit-split', [$journal->id]));
}
);
diff --git a/app/Import/Converter/OpposingAccountIban.php b/app/Import/Converter/OpposingAccountIban.php
index b1f8dc63e7..c3dacbd712 100644
--- a/app/Import/Converter/OpposingAccountIban.php
+++ b/app/Import/Converter/OpposingAccountIban.php
@@ -13,6 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Import\Converter;
+use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Log;
@@ -69,11 +70,20 @@ class OpposingAccountIban extends BasicConverter implements ConverterInterface
return $account;
}
- $account = $repository->store(
- ['name' => $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true,
- 'openingBalance' => 0]
- );
- $this->setCertainty(100);
+ // the IBAN given may not be a valid IBAN. If not, we cannot store by
+ // iban and we have no opposing account. There should be some kind of fall back
+ // routine.
+ try {
+ $account = $repository->store(
+ ['name' => $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true,
+ 'openingBalance' => 0]
+ );
+ $this->setCertainty(100);
+ } catch (FireflyException $e) {
+ Log::error($e);
+
+ $account = new Account;
+ }
return $account;
}
diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php
index 797335cb1d..0cc869fcd4 100644
--- a/app/Import/Importer/CsvImporter.php
+++ b/app/Import/Importer/CsvImporter.php
@@ -111,9 +111,14 @@ class CsvImporter implements ImporterInterface
// set some vars:
$object->setUser($this->job->user);
$config = $this->job->configuration;
+ $json = json_encode($row);
+
+ if ($json === false) {
+ throw new FireflyException(sprintf('Could not process row #%d. Are you sure the uploaded file is encoded as "UTF-8"?', $index));
+ }
// hash the row:
- $hash = hash('sha256', json_encode($row));
+ $hash = hash('sha256', $json);
$object->importValue('hash', 100, $hash);
// and this is the point where the specifix go to work.
diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php
index e6437ec74d..8048fe9cd7 100644
--- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php
+++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php
@@ -15,7 +15,7 @@ namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Models\RuleGroup;
-use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Rules\Processor;
use FireflyIII\User;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -155,10 +155,10 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
*/
protected function collectJournals()
{
- /** @var JournalRepositoryInterface $repository */
- $repository = app(JournalRepositoryInterface::class);
+ /** @var JournalTaskerInterface $tasker */
+ $tasker = app(JournalTaskerInterface::class);
- return $repository->getJournalsInRange($this->accounts, $this->startDate, $this->endDate);
+ return $tasker->getJournalsInRange($this->accounts, $this->startDate, $this->endDate);
}
/**
diff --git a/app/Models/Note.php b/app/Models/Note.php
new file mode 100644
index 0000000000..db1d2b4603
--- /dev/null
+++ b/app/Models/Note.php
@@ -0,0 +1,54 @@
+morphTo();
+ }
+
+}
\ No newline at end of file
diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php
index f2d2ae4e50..3d5e5da150 100644
--- a/app/Models/PiggyBank.php
+++ b/app/Models/PiggyBank.php
@@ -56,6 +56,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @mixin \Eloquent
* @property boolean $active
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\PiggyBank whereActive($value)
+ * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes
*/
class PiggyBank extends Model
{
@@ -146,6 +147,14 @@ class PiggyBank extends Model
}
+ /**
+ * Get all of the piggy bank's notes.
+ */
+ public function notes()
+ {
+ return $this->morphMany('FireflyIII\Models\Note', 'noteable');
+ }
+
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php
index d2d82d17b4..f35efcb180 100644
--- a/app/Models/Transaction.php
+++ b/app/Models/Transaction.php
@@ -55,7 +55,7 @@ class Transaction extends Model
{
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
- protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount'];
+ protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier'];
protected $hidden = ['encrypted'];
protected $rules
= [
diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php
index e4e7020d0f..f6f17b9429 100644
--- a/app/Models/TransactionJournal.php
+++ b/app/Models/TransactionJournal.php
@@ -434,6 +434,9 @@ class TransactionJournal extends TransactionJournalSupport
return new TransactionJournalMeta();
}
+ if (is_string($value) && strlen($value) === 0) {
+ return new TransactionJournalMeta();
+ }
if ($value instanceof Carbon) {
$value = $value->toW3cString();
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index d8373a2be6..6cfb886b4c 100755
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -35,42 +35,50 @@ class EventServiceProvider extends ServiceProvider
*/
protected $listen
= [
- 'FireflyIII\Events\TransactionJournalUpdated' => [
- 'FireflyIII\Handlers\Events\ScanForBillsAfterUpdate',
- 'FireflyIII\Handlers\Events\UpdateJournalConnection',
- 'FireflyIII\Handlers\Events\FireRulesForUpdate',
+ // new event handlers:
+ 'FireflyIII\Events\ConfirmedUser' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@storeConfirmationIpAddress',
+ ],
+ 'FireflyIII\Events\RegisteredUser' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
+ 'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole',
+ 'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessage',
+ 'FireflyIII\Handlers\Events\UserEventHandler@storeRegistrationIpAddress',
+ ],
+ 'FireflyIII\Events\ResentConfirmation' => // is a User related event.
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessageAgain',
+ ],
+ 'FireflyIII\Events\StoredBudgetLimit' => // is a Budget related event.
+ [
+ 'FireflyIII\Handlers\Events\BudgetEventHandler@storeRepetition',
+ ],
- ],
+ 'FireflyIII\Events\UpdatedBudgetLimit' => // is a Budget related event.
+ [
+ 'FireflyIII\Handlers\Events\BudgetEventHandler@updateRepetition',
+ ],
- 'FireflyIII\Events\BudgetLimitStored' => [
- 'FireflyIII\Handlers\Events\BudgetLimitEventHandler@store',
- ],
- 'FireflyIII\Events\BudgetLimitUpdated' => [
- 'FireflyIII\Handlers\Events\BudgetLimitEventHandler@update',
- ],
- 'FireflyIII\Events\TransactionStored' => [
- 'FireflyIII\Handlers\Events\ConnectTransactionToPiggyBank',
- ],
- 'FireflyIII\Events\TransactionJournalStored' => [
- 'FireflyIII\Handlers\Events\ScanForBillsAfterStore',
- 'FireflyIII\Handlers\Events\ConnectJournalToPiggyBank',
- 'FireflyIII\Handlers\Events\FireRulesForStore',
- ],
- 'Illuminate\Auth\Events\Logout' => [
- 'FireflyIII\Handlers\Events\UserEventListener@onUserLogout',
- ],
- 'FireflyIII\Events\UserRegistration' => [
- 'FireflyIII\Handlers\Events\SendRegistrationMail',
- 'FireflyIII\Handlers\Events\AttachUserRole',
- 'FireflyIII\Handlers\Events\UserConfirmation@sendConfirmation',
- 'FireflyIII\Handlers\Events\UserSaveIpAddress@saveFromRegistration',
- ],
- 'FireflyIII\Events\UserIsConfirmed' => [
- 'FireflyIII\Handlers\Events\UserSaveIpAddress@saveFromConfirmation',
- ],
- 'FireflyIII\Events\ResendConfirmation' => [
- 'FireflyIII\Handlers\Events\UserConfirmation@resendConfirmation',
- ],
+ 'FireflyIII\Events\StoredTransactionJournal' => // is a Transaction Journal related event.
+ [
+ 'FireflyIII\Handlers\Events\StoredJournalEventHandler@scanBills',
+ 'FireflyIII\Handlers\Events\StoredJournalEventHandler@connectToPiggyBank',
+ 'FireflyIII\Handlers\Events\StoredJournalEventHandler@processRules',
+ ],
+ 'FireflyIII\Events\UpdatedTransactionJournal' => // is a Transaction Journal related event.
+ [
+ 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@scanBills',
+ 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@connectToPiggyBank',
+ 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@processRules',
+ ],
+
+ // LARAVEL EVENTS:
+ 'Illuminate\Auth\Events\Logout' =>
+ [
+ 'FireflyIII\Handlers\Events\UserEventHandler@logoutUser',
+ ],
];
/**
@@ -83,9 +91,6 @@ class EventServiceProvider extends ServiceProvider
parent::boot();
$this->registerDeleteEvents();
$this->registerCreateEvents();
-
-
- //
}
/**
diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php
index f7efb67dec..5353941000 100644
--- a/app/Providers/JournalServiceProvider.php
+++ b/app/Providers/JournalServiceProvider.php
@@ -41,6 +41,12 @@ class JournalServiceProvider extends ServiceProvider
* @return void
*/
public function register()
+ {
+ $this->registerRepository();
+ $this->registerTasker();
+ }
+
+ private function registerRepository()
{
$this->app->bind(
'FireflyIII\Repositories\Journal\JournalRepositoryInterface',
@@ -56,4 +62,21 @@ class JournalServiceProvider extends ServiceProvider
}
);
}
+
+ private function registerTasker()
+ {
+ $this->app->bind(
+ 'FireflyIII\Repositories\Journal\JournalTaskerInterface',
+ function (Application $app, array $arguments) {
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Journal\JournalTasker', [auth()->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
+ }
+
+ return app('FireflyIII\Repositories\Journal\JournalTasker', $arguments);
+ }
+ );
+ }
}
diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php
index 2e12076d10..0412c3080c 100644
--- a/app/Repositories/Bill/BillRepository.php
+++ b/app/Repositories/Bill/BillRepository.php
@@ -16,12 +16,15 @@ namespace FireflyIII\Repositories\Bill;
use Carbon\Carbon;
use DB;
use FireflyIII\Models\Bill;
+use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
+use FireflyIII\Support\CacheProperties;
use FireflyIII\User;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
+use Log;
use Navigation;
/**
@@ -222,7 +225,8 @@ class BillRepository implements BillRepositoryInterface
/**
* Get the total amount of money paid for the users active bills in the date range given.
- * This amount will be negative (they're expenses).
+ * This amount will be negative (they're expenses). This method is equal to
+ * getBillsUnpaidInRange. So the debug comments are gone.
*
* @param Carbon $start
* @param Carbon $end
@@ -231,29 +235,22 @@ class BillRepository implements BillRepositoryInterface
*/
public function getBillsPaidInRange(Carbon $start, Carbon $end): string
{
- $amount = '0';
- $bills = $this->getActiveBills();
-
+ $bills = $this->getActiveBills();
+ $sum = '0';
/** @var Bill $bill */
foreach ($bills as $bill) {
- $ranges = $this->getRanges($bill, $start, $end);
-
- foreach ($ranges as $range) {
- $paid = $bill->transactionJournals()
- ->before($range['end'])
- ->after($range['start'])
- ->leftJoin(
- 'transactions', function (JoinClause $join) {
- $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0);
- }
- )
- ->first([DB::raw('SUM(transactions.amount) AS sum_amount')]);
- $sumAmount = $paid->sum_amount ?? '0';
- $amount = bcadd($amount, $sumAmount);
+ /** @var Collection $set */
+ $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
+ if ($set->count() > 0) {
+ $journalIds = $set->pluck('id')->toArray();
+ $amount = strval(Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'));
+ $sum = bcadd($sum, $amount);
+ Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
}
+ Log::debug('---');
}
- return $amount;
+ return $sum;
}
/**
@@ -266,32 +263,28 @@ class BillRepository implements BillRepositoryInterface
*/
public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string
{
- $amount = '0';
- $bills = $this->getActiveBills();
-
+ $bills = $this->getActiveBills();
+ $sum = '0';
/** @var Bill $bill */
foreach ($bills as $bill) {
- $ranges = $this->getRanges($bill, $start, $end);
- $paidBill = '0';
- foreach ($ranges as $range) {
- $paid = $bill->transactionJournals()
- ->before($range['end'])
- ->after($range['start'])
- ->leftJoin(
- 'transactions', function (JoinClause $join) {
- $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0);
- }
- )
- ->first([DB::raw('SUM(transactions.amount) AS sum_amount')]);
- $sumAmount = $paid->sum_amount ?? '0';
- $paidBill = bcadd($sumAmount, $paidBill);
- }
- if ($paidBill == 0) {
- $amount = bcadd($amount, $bill->expectedAmount);
+ Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
+ $dates = $this->getPayDatesInRange($bill, $start, $end);
+ $count = $bill->transactionJournals()->after($start)->before($end)->count();
+ $total = $dates->count() - $count;
+
+ Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
+
+ if ($total > 0) {
+
+ $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
+ $multi = bcmul($average, strval($total));
+ $sum = bcadd($sum, $multi);
+ Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
}
+ Log::debug('---');
}
- return $amount;
+ return $sum;
}
/**
@@ -354,6 +347,61 @@ class BillRepository implements BillRepositoryInterface
return $avg;
}
+ /**
+ * Between start and end, tells you on which date(s) the bill is expected to hit.
+ *
+ * @param Bill $bill
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection
+ {
+ $set = new Collection;
+ Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq));
+
+ /*
+ * Start at 2016-10-01, see when we expect the bill to hit:
+ */
+ $currentStart = clone $start;
+ Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d')));
+
+ while ($currentStart <= $end) {
+ Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
+ $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart);
+ Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
+ /*
+ * If nextExpectedMatch is after end, we continue:
+ */
+ if ($nextExpectedMatch > $end) {
+ Log::debug(
+ sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch->format('Y-m-d'), $end->format('Y-m-d'))
+ );
+ break;
+ }
+ // add to set
+ $set->push(clone $nextExpectedMatch);
+ Log::debug(sprintf('Now %d dates in set.', $set->count()));
+
+ // add day if necessary.
+ $nextExpectedMatch->addDay();
+
+ Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
+
+ $currentStart = clone $nextExpectedMatch;
+ }
+ $simple = $set->each(
+ function (Carbon $date) {
+ return $date->format('Y-m-d');
+ }
+ );
+ Log::debug(sprintf('Found dates between %s and %s:', $start->format('Y-m-d'), $end->format('Y-m-d')), $simple->toArray());
+
+
+ return $set;
+ }
+
/**
* @param Bill $bill
*
@@ -377,48 +425,6 @@ class BillRepository implements BillRepositoryInterface
return $journals;
}
- /**
- * Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself)
- * and returns date ranges that fall within the given range; those ranges are the bills expected. When a bill is due on the 14th of the month and
- * you give 1st and the 31st of that month as argument, you'll get one response, matching the range of your bill.
- *
- * @param Bill $bill
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function getRanges(Bill $bill, Carbon $start, Carbon $end): array
- {
- $startOfBill = Navigation::startOfPeriod($start, $bill->repeat_freq);
-
-
- // all periods of this bill up until the current period:
- $billStarts = [];
- while ($startOfBill < $end) {
-
- $endOfBill = Navigation::endOfPeriod($startOfBill, $bill->repeat_freq);
-
- $billStarts[] = [
- 'start' => clone $startOfBill,
- 'end' => clone $endOfBill,
- ];
- // actually the next one:
- $startOfBill = Navigation::addPeriod($startOfBill, $bill->repeat_freq, $bill->skip);
-
- }
- // for each
- $validRanges = [];
- foreach ($billStarts as $dateEntry) {
- if ($dateEntry['end'] > $start && $dateEntry['start'] < $end) {
- // count transactions for bill in this range (not relevant yet!):
- $validRanges[] = $dateEntry;
- }
- }
-
- return $validRanges;
- }
-
/**
* @param Bill $bill
* @param Carbon $date
@@ -461,52 +467,87 @@ class BillRepository implements BillRepositoryInterface
}
/**
- * @param Bill $bill
+ * Given a bill and a date, this method will tell you at which moment this bill expects its next
+ * transaction. Whether or not it is there already, is not relevant.
+ *
+ * @param Bill $bill
+ * @param Carbon $date
*
* @return \Carbon\Carbon
*/
- public function nextExpectedMatch(Bill $bill): Carbon
+ public function nextDateMatch(Bill $bill, Carbon $date): Carbon
{
+ $cache = new CacheProperties;
+ $cache->addProperty($bill->id);
+ $cache->addProperty('nextDateMatch');
+ $cache->addProperty($date);
+ if ($cache->has()) {
+ return $cache->get();
+ }
+ // find the most recent date for this bill NOT in the future. Cache this date:
+ $start = clone $bill->date;
+ Log::debug('nextDateMatch: Start is ' . $start->format('Y-m-d'));
- $finalDate = Carbon::now();
- $finalDate->year = 1900;
- if ($bill->active == 0) {
- return $finalDate;
+ while ($start < $date) {
+ Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
+ $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
+ Log::debug('Start is now ' . $start->format('Y-m-d'));
}
- /*
- * $today is the start of the next period, to make sure FF3 won't miss anything
- * when the current period has a transaction journal.
- */
- /** @var \Carbon\Carbon $obj */
- $obj = new Carbon;
- $today = Navigation::addPeriod($obj, $bill->repeat_freq, 0);
+ $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
- $skip = $bill->skip + 1;
- $start = Navigation::startOfPeriod($obj, $bill->repeat_freq);
- /*
- * go back exactly one month/week/etc because FF3 does not care about 'next'
- * bills if they're too far into the past.
- */
+ Log::debug('nextDateMatch: Final start is ' . $start->format('Y-m-d'));
+ Log::debug('nextDateMatch: Matching end is ' . $end->format('Y-m-d'));
- $counter = 0;
- while ($start <= $today) {
- if (($counter % $skip) == 0) {
- // do something.
- $end = Navigation::endOfPeriod(clone $start, $bill->repeat_freq);
- $journalCount = $bill->transactionJournals()->before($end)->after($start)->count();
- if ($journalCount == 0) {
- $finalDate = new Carbon($start->format('Y-m-d'));
- break;
- }
- }
+ $cache->store($start);
- // add period for next round!
- $start = Navigation::addPeriod($start, $bill->repeat_freq, 0);
- $counter++;
+ return $start;
+ }
+
+ /**
+ * Given the date in $date, this method will return a moment in the future where the bill is expected to be paid.
+ *
+ * @param Bill $bill
+ * @param Carbon $date
+ *
+ * @return Carbon
+ */
+ public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon
+ {
+ $cache = new CacheProperties;
+ $cache->addProperty($bill->id);
+ $cache->addProperty('nextExpectedMatch');
+ $cache->addProperty($date);
+ if ($cache->has()) {
+ return $cache->get();
+ }
+ // find the most recent date for this bill NOT in the future. Cache this date:
+ $start = clone $bill->date;
+ Log::debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d'));
+
+ while ($start < $date) {
+ Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
+ $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
+ Log::debug('Start is now ' . $start->format('Y-m-d'));
}
- return $finalDate;
+ $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
+
+ // see if the bill was paid in this period.
+ $journalCount = $bill->transactionJournals()->before($end)->after($start)->count();
+
+ if ($journalCount > 0) {
+ // this period had in fact a bill. The new start is the current end, and we create a new end.
+ Log::debug(sprintf('Journal count is %d, so start becomes %s', $journalCount, $end->format('Y-m-d')));
+ $start = clone $end;
+ $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
+ }
+ Log::debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d'));
+ Log::debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d'));
+
+ $cache->store($start);
+
+ return $start;
}
/**
diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php
index f740921cec..82f849c468 100644
--- a/app/Repositories/Bill/BillRepositoryInterface.php
+++ b/app/Repositories/Bill/BillRepositoryInterface.php
@@ -132,24 +132,22 @@ interface BillRepositoryInterface
public function getOverallAverage($bill): string;
/**
- * @param Bill $bill
- *
- * @return Collection
- */
- public function getPossiblyRelatedJournals(Bill $bill): Collection;
-
- /**
- * Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself)
- * and returns date ranges that fall within the given range; those ranges are the bills expected. When a bill is due on the 14th of the month and
- * you give 1st and the 31st of that month as argument, you'll get one response, matching the range of your bill (from the 14th to the 31th).
+ * Between start and end, tells you on which date(s) the bill is expected to hit.
*
* @param Bill $bill
* @param Carbon $start
* @param Carbon $end
*
- * @return array
+ * @return Collection
*/
- public function getRanges(Bill $bill, Carbon $start, Carbon $end): array;
+ public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection;
+
+ /**
+ * @param Bill $bill
+ *
+ * @return Collection
+ */
+ public function getPossiblyRelatedJournals(Bill $bill): Collection;
/**
* @param Bill $bill
@@ -168,11 +166,23 @@ interface BillRepositoryInterface
/**
- * @param Bill $bill
+ * Given a bill and a date, this method will tell you at which moment this bill expects its next
+ * transaction. Whether or not it is there already, is not relevant.
+ *
+ * @param Bill $bill
+ * @param Carbon $date
*
* @return \Carbon\Carbon
*/
- public function nextExpectedMatch(Bill $bill): Carbon;
+ public function nextDateMatch(Bill $bill, Carbon $date): Carbon;
+
+ /**
+ * @param Bill $bill
+ * @param Carbon $date
+ *
+ * @return \Carbon\Carbon
+ */
+ public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon;
/**
* @param Bill $bill
diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php
index 7d7e24dc52..b2a3440295 100644
--- a/app/Repositories/Budget/BudgetRepository.php
+++ b/app/Repositories/Budget/BudgetRepository.php
@@ -14,8 +14,8 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon;
-use FireflyIII\Events\BudgetLimitStored;
-use FireflyIII\Events\BudgetLimitUpdated;
+use FireflyIII\Events\StoredBudgetLimit;
+use FireflyIII\Events\UpdatedBudgetLimit;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition;
@@ -555,7 +555,7 @@ class BudgetRepository implements BudgetRepositoryInterface
$limit->save();
// fire event to create or update LimitRepetition.
- event(new BudgetLimitUpdated($limit, $end));
+ event(new UpdatedBudgetLimit($limit, $end));
return $limit;
}
@@ -568,7 +568,7 @@ class BudgetRepository implements BudgetRepositoryInterface
$limit->repeat_freq = $repeatFreq;
$limit->repeats = 0;
$limit->save();
- event(new BudgetLimitStored($limit, $end));
+ event(new StoredBudgetLimit($limit, $end));
// likewise, there should be a limit repetition to match the end date
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index fce3033619..9c047824d8 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -13,23 +13,18 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Journal;
-use Carbon\Carbon;
use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
-use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\User;
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Database\Query\JoinClause;
-use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
@@ -56,42 +51,6 @@ class JournalRepository implements JournalRepositoryInterface
$this->user = $user;
}
- /**
- * Returns the amount in the account before the specified transaction took place.
- *
- * @param Transaction $transaction
- *
- * @return string
- */
- public function balanceBeforeTransaction(Transaction $transaction): string
- {
- // some dates from journal
- $journal = $transaction->transactionJournal;
- $query = Transaction::
- leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.account_id', $transaction->account_id)
- ->where('transaction_journals.user_id', $this->user->id)
- ->where(
- function (Builder $q) use ($journal) {
- $q->where('transaction_journals.date', '<', $journal->date->format('Y-m-d'));
- $q->orWhere(
- function (Builder $qq) use ($journal) {
- $qq->where('transaction_journals.date', '=', $journal->date->format('Y-m-d'));
- $qq->where('transaction_journals.order', '>', $journal->order);
- }
- );
-
- }
- )
- ->where('transactions.id', '!=', $transaction->id)
- ->whereNull('transactions.deleted_at')
- ->whereNull('transaction_journals.deleted_at')
- ->groupBy('transaction_journals.id');
- $sum = $query->sum('transactions.amount');
-
- return strval($sum);
- }
-
/**
* @param TransactionJournal $journal
*
@@ -136,146 +95,6 @@ class JournalRepository implements JournalRepositoryInterface
return $entry;
}
- /**
- * @param array $types
- * @param int $page
- * @param int $pageSize
- *
- * @return LengthAwarePaginator
- */
- public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
- {
- $offset = ($page - 1) * $pageSize;
- $query = $this->user->transactionJournals()->expanded()->sortCorrectly();
- $query->where('transaction_journals.completed', 1);
- if (count($types) > 0) {
- $query->transactionTypes($types);
- }
- $count = $this->user->transactionJournals()->transactionTypes($types)->count();
- $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
- $journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
-
- return $journals;
- }
-
- /**
- * Returns a collection of ALL journals, given a specific account and a date range.
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $query = $this->user->transactionJournals()->expanded()->sortCorrectly();
- $query->where('transaction_journals.completed', 1);
- $query->before($end);
- $query->after($start);
-
- if ($accounts->count() > 0) {
- $ids = $accounts->pluck('id')->toArray();
- // join source and destination:
- $query->leftJoin(
- 'transactions as source', function (JoinClause $join) {
- $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
- }
- );
- $query->leftJoin(
- 'transactions as destination', function (JoinClause $join) {
- $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
- }
- );
-
- $query->where(
- function (Builder $q) use ($ids) {
- $q->whereIn('destination.account_id', $ids);
- $q->orWhereIn('source.account_id', $ids);
- }
- );
- }
-
- $set = $query->get(TransactionJournal::queryFields());
-
- return $set;
- }
-
- /**
- * @param TransactionJournal $journal
- *
- * @return Collection
- */
- public function getPiggyBankEvents(TransactionJournal $journal): Collection
- {
- /** @var Collection $set */
- $events = $journal->piggyBankEvents()->get();
- $events->each(
- function (PiggyBankEvent $event) {
- $event->piggyBank = $event->piggyBank()->withTrashed()->first();
- }
- );
-
- return $events;
- }
-
- /**
- * @param TransactionJournal $journal
- *
- * @return Collection
- */
- public function getTransactions(TransactionJournal $journal): Collection
- {
- $transactions = new Collection;
- $fields = ['transactions.id', 'transactions.created_at', 'transactions.updated_at', 'transactions.deleted_at', 'transactions.account_id',
- 'transactions.transaction_journal_id', 'transactions.description', 'transactions.amount',
- DB::raw('SUM(transactions.amount) AS sum')];
- $groupBy = ['transactions.id', 'transactions.created_at', 'transactions.updated_at', 'transactions.deleted_at', 'transactions.account_id',
- 'transactions.transaction_journal_id', 'transactions.description', 'transactions.amount'];
- switch ($journal->transactionType->type) {
- case TransactionType::DEPOSIT:
- /** @var Collection $transactions */
- $transactions = $journal->transactions()
- ->groupBy('transactions.account_id')
- ->where('amount', '<', 0)
- ->groupBy($groupBy)
- ->orderBy('amount', 'ASC')->get($fields);
- $final = $journal->transactions()
- ->groupBy($groupBy)
- ->where('amount', '>', 0)
- ->orderBy('amount', 'ASC')->first($fields);
- $transactions->push($final);
- break;
- case TransactionType::TRANSFER:
-
- /** @var Collection $transactions */
- $transactions = $journal->transactions()
- ->groupBy($groupBy)
- ->orderBy('transactions.id')->get($fields);
- break;
- case TransactionType::WITHDRAWAL:
-
- /** @var Collection $transactions */
- $transactions = $journal->transactions()
- ->where('amount', '>', 0)
- ->groupBy($groupBy)
- ->orderBy('amount', 'ASC')->get($fields);
- $final = $journal->transactions()
- ->where('amount', '<', 0)
- ->groupBy($groupBy)
- ->orderBy('amount', 'ASC')->first($fields);
- $transactions->push($final);
- break;
- }
- // foreach do balance thing
- $transactions->each(
- function (Transaction $t) {
- $t->before = $this->balanceBeforeTransaction($t);
- }
- );
-
- return $transactions;
- }
/**
* @param array $data
@@ -286,56 +105,47 @@ class JournalRepository implements JournalRepositoryInterface
{
// find transaction type.
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
-
- // store actual journal.
- $journal = new TransactionJournal(
+ $journal = new TransactionJournal(
[
'user_id' => $data['user'],
'transaction_type_id' => $transactionType->id,
- 'transaction_currency_id' => $data['amount_currency_id_amount'],
+ 'transaction_currency_id' => $data['currency_id'],
'description' => $data['description'],
'completed' => 0,
'date' => $data['date'],
- 'interest_date' => $data['interest_date'],
- 'book_date' => $data['book_date'],
- 'process_date' => $data['process_date'],
]
);
$journal->save();
- // store or get category
- if (strlen($data['category']) > 0) {
- $category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
- $journal->categories()->save($category);
- }
+ // store stuff:
+ $this->storeCategoryWithJournal($journal, $data['category']);
+ $this->storeBudgetWithJournal($journal, $data['budget_id']);
+ $accounts = $this->storeAccounts($transactionType, $data);
- // store or get budget
- if (intval($data['budget_id']) > 0 && $transactionType->type !== TransactionType::TRANSFER) {
- /** @var \FireflyIII\Models\Budget $budget */
- $budget = Budget::find($data['budget_id']);
- $journal->budgets()->save($budget);
- }
+ // store two transactions:
+ $one = [
+ 'journal' => $journal,
+ 'account' => $accounts['source'],
+ 'amount' => bcmul(strval($data['amount']), '-1'),
+ 'description' => null,
+ 'category' => null,
+ 'budget' => null,
+ 'identifier' => 0,
+ ];
+ $this->storeTransaction($one);
- // store accounts (depends on type)
- list($sourceAccount, $destinationAccount) = $this->storeAccounts($transactionType, $data);
+ $two = [
+ 'journal' => $journal,
+ 'account' => $accounts['destination'],
+ 'amount' => $data['amount'],
+ 'description' => null,
+ 'category' => null,
+ 'budget' => null,
+ 'identifier' => 0,
+ ];
+
+ $this->storeTransaction($two);
- // store accompanying transactions.
- Transaction::create( // first transaction.
- [
- 'account_id' => $sourceAccount->id,
- 'transaction_journal_id' => $journal->id,
- 'amount' => $data['amount'] * -1,
- ]
- );
- Transaction::create( // second transaction.
- [
- 'account_id' => $destinationAccount->id,
- 'transaction_journal_id' => $journal->id,
- 'amount' => $data['amount'],
- ]
- );
- $journal->completed = 1;
- $journal->save();
// store tags
if (isset($data['tags']) && is_array($data['tags'])) {
@@ -350,6 +160,9 @@ class JournalRepository implements JournalRepositoryInterface
Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
}
+ $journal->completed = 1;
+ $journal->save();
+
return $journal;
}
@@ -396,45 +209,24 @@ class JournalRepository implements JournalRepositoryInterface
*/
public function update(TransactionJournal $journal, array $data): TransactionJournal
{
- // update actual journal.
- $journal->transaction_currency_id = $data['amount_currency_id_amount'];
+ // update actual journal:
+ $journal->transaction_currency_id = $data['currency_id'];
$journal->description = $data['description'];
$journal->date = $data['date'];
// unlink all categories, recreate them:
$journal->categories()->detach();
- if (strlen($data['category']) > 0) {
- $category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
- $journal->categories()->save($category);
- }
-
- // unlink all budgets and recreate them:
$journal->budgets()->detach();
- if (intval($data['budget_id']) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) {
- /** @var \FireflyIII\Models\Budget $budget */
- $budget = Budget::where('user_id', $this->user->id)->where('id', $data['budget_id'])->first();
- $journal->budgets()->save($budget);
- }
- // store accounts (depends on type)
- list($fromAccount, $toAccount) = $this->storeAccounts($journal->transactionType, $data);
+ $this->storeCategoryWithJournal($journal, $data['category']);
+ $this->storeBudgetWithJournal($journal, $data['budget_id']);
+ $accounts = $this->storeAccounts($journal->transactionType, $data);
- // update the from and to transaction.
- /** @var Transaction $transaction */
- foreach ($journal->transactions()->get() as $transaction) {
- if ($transaction->amount < 0) {
- // this is the from transaction, negative amount:
- $transaction->amount = $data['amount'] * -1;
- $transaction->account_id = $fromAccount->id;
- $transaction->save();
- }
- if ($transaction->amount > 0) {
- $transaction->amount = $data['amount'];
- $transaction->account_id = $toAccount->id;
- $transaction->save();
- }
- }
+ $sourceAmount = bcmul(strval($data['amount']), '-1');
+ $this->updateSourceTransaction($journal, $accounts['source'], $sourceAmount); // negative because source loses money.
+ $amount = strval($data['amount']);
+ $this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money.
$journal->save();
@@ -460,6 +252,96 @@ class JournalRepository implements JournalRepositoryInterface
return $journal;
}
+ /**
+ * Same as above but for transaction journal with multiple transactions.
+ *
+ * @param TransactionJournal $journal
+ * @param array $data
+ *
+ * @return TransactionJournal
+ */
+ public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal
+ {
+ // update actual journal:
+ $journal->transaction_currency_id = $data['currency_id'];
+ $journal->description = $data['journal_description'];
+ $journal->date = $data['date'];
+ $journal->save();
+
+ // unlink all categories:
+ $journal->categories()->detach();
+ $journal->budgets()->detach();
+
+ // update meta fields:
+ $result = $journal->save();
+ if ($result) {
+ foreach ($data as $key => $value) {
+ if (in_array($key, $this->validMetaFields)) {
+ $journal->setMeta($key, $value);
+ continue;
+ }
+ Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
+ }
+
+ return $journal;
+ }
+
+
+ // update tags:
+ if (isset($data['tags']) && is_array($data['tags'])) {
+ $this->updateTags($journal, $data['tags']);
+ }
+
+ // delete original transactions, and recreate them.
+ $journal->transactions()->delete();
+
+ // store each transaction.
+ $identifier = 0;
+ foreach ($data['transactions'] as $transaction) {
+ Log::debug(sprintf('Split journal update split transaction %d', $identifier));
+ $transaction = $this->appendTransactionData($transaction, $data);
+ $this->storeSplitTransaction($journal, $transaction, $identifier);
+ $identifier++;
+ }
+
+ $journal->save();
+
+ return $journal;
+ }
+
+ /**
+ * When the user edits a split journal, each line is missing crucial data:
+ *
+ * - Withdrawal lines are missing the source account ID
+ * - Deposit lines are missing the destination account ID
+ * - Transfers are missing both.
+ *
+ * We need to append the array.
+ *
+ * @param array $transaction
+ * @param array $data
+ *
+ * @return array
+ */
+ private function appendTransactionData(array $transaction, array $data): array
+ {
+ switch ($data['what']) {
+ case strtolower(TransactionType::TRANSFER):
+ case strtolower(TransactionType::WITHDRAWAL):
+ $transaction['source_account_id'] = intval($data['journal_source_account_id']);
+ break;
+ }
+
+ switch ($data['what']) {
+ case strtolower(TransactionType::TRANSFER):
+ case strtolower(TransactionType::DEPOSIT):
+ $transaction['destination_account_id'] = intval($data['journal_destination_account_id']);
+ break;
+ }
+
+ return $transaction;
+ }
+
/**
*
* * Remember: a balancingAct takes at most one expense and one transfer.
@@ -496,38 +378,92 @@ class JournalRepository implements JournalRepositoryInterface
*/
private function storeAccounts(TransactionType $type, array $data): array
{
- $sourceAccount = null;
- $destinationAccount = null;
+ $accounts = [
+ 'source' => null,
+ 'destination' => null,
+ ];
+
+ Log::debug(sprintf('Going to store accounts for type %s', $type->type));
switch ($type->type) {
case TransactionType::WITHDRAWAL:
- list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data);
+ $accounts = $this->storeWithdrawalAccounts($data);
break;
case TransactionType::DEPOSIT:
- list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data);
+ $accounts = $this->storeDepositAccounts($data);
break;
case TransactionType::TRANSFER:
- $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
- $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
+ $accounts['source'] = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
+ $accounts['destination'] = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
break;
default:
- throw new FireflyException('Did not recognise transaction type.');
+ throw new FireflyException(sprintf('Did not recognise transaction type "%s".', $type->type));
}
- if (is_null($destinationAccount)) {
- Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]);
- throw new FireflyException('"destination"-account is null, so we cannot continue!');
- }
-
- if (is_null($sourceAccount)) {
+ if (is_null($accounts['source'])) {
Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]);
throw new FireflyException('"source"-account is null, so we cannot continue!');
+ }
+
+ if (is_null($accounts['destination'])) {
+ Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]);
+ throw new FireflyException('"destination"-account is null, so we cannot continue!');
}
- return [$sourceAccount, $destinationAccount];
+ return $accounts;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param int $budgetId
+ */
+ private function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId)
+ {
+ if (intval($budgetId) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) {
+ /** @var \FireflyIII\Models\Budget $budget */
+ $budget = Budget::find($budgetId);
+ $journal->budgets()->save($budget);
+ }
+ }
+
+ /**
+ * @param Transaction $transaction
+ * @param int $budgetId
+ */
+ private function storeBudgetWithTransaction(Transaction $transaction, int $budgetId)
+ {
+ if (intval($budgetId) > 0 && $transaction->transactionJournal->transactionType->type !== TransactionType::TRANSFER) {
+ /** @var \FireflyIII\Models\Budget $budget */
+ $budget = Budget::find($budgetId);
+ $transaction->budgets()->save($budget);
+ }
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param string $category
+ */
+ private function storeCategoryWithJournal(TransactionJournal $journal, string $category)
+ {
+ if (strlen($category) > 0) {
+ $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]);
+ $journal->categories()->save($category);
+ }
+ }
+
+ /**
+ * @param Transaction $transaction
+ * @param string $category
+ */
+ private function storeCategoryWithTransaction(Transaction $transaction, string $category)
+ {
+ if (strlen($category) > 0) {
+ $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $transaction->transactionJournal->user_id]);
+ $transaction->categories()->save($category);
+ }
}
/**
@@ -540,19 +476,102 @@ class JournalRepository implements JournalRepositoryInterface
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
if (strlen($data['source_account_name']) > 0) {
- $fromType = AccountType::where('type', 'Revenue account')->first();
- $fromAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
+ $sourceType = AccountType::where('type', 'Revenue account')->first();
+ $sourceAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
);
- return [$fromAccount, $destinationAccount];
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
}
- $fromType = AccountType::where('type', 'Cash account')->first();
- $fromAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
+ $sourceType = AccountType::where('type', 'Cash account')->first();
+ $sourceAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
);
- return [$fromAccount, $destinationAccount];
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param array $transaction
+ * @param int $identifier
+ *
+ * @return Collection
+ */
+ private function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection
+ {
+ // store source and destination accounts (depends on type)
+ $accounts = $this->storeAccounts($journal->transactionType, $transaction);
+
+ // store transaction one way:
+ $one = $this->storeTransaction(
+ [
+ 'journal' => $journal,
+ 'account' => $accounts['source'],
+ 'amount' => bcmul(strval($transaction['amount']), '-1'),
+ 'description' => $transaction['description'],
+ 'category' => null,
+ 'budget' => null,
+ 'identifier' => $identifier,
+ ]
+ );
+ $this->storeCategoryWithTransaction($one, $transaction['category']);
+ $this->storeBudgetWithTransaction($one, $transaction['budget_id']);
+
+ // and the other way:
+ $two = $this->storeTransaction(
+ [
+ 'journal' => $journal,
+ 'account' => $accounts['destination'],
+ 'amount' => strval($transaction['amount']),
+ 'description' => $transaction['description'],
+ 'category' => null,
+ 'budget' => null,
+ 'identifier' => $identifier,
+ ]
+ );
+ $this->storeCategoryWithTransaction($two, $transaction['category']);
+ $this->storeBudgetWithTransaction($two, $transaction['budget_id']);
+
+ return new Collection([$one, $two]);
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return Transaction
+ */
+ private function storeTransaction(array $data): Transaction
+ {
+ /** @var Transaction $transaction */
+ $transaction = Transaction::create(
+ [
+ 'transaction_journal_id' => $data['journal']->id,
+ 'account_id' => $data['account']->id,
+ 'amount' => $data['amount'],
+ 'description' => $data['description'],
+ 'identifier' => $data['identifier'],
+ ]
+ );
+
+ Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id));
+
+ if (!is_null($data['category'])) {
+ $transaction->categories()->save($data['category']);
+ }
+
+ if (!is_null($data['budget'])) {
+ $transaction->categories()->save($data['budget']);
+ }
+
+ return $transaction;
+
}
/**
@@ -565,7 +584,7 @@ class JournalRepository implements JournalRepositoryInterface
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
if (strlen($data['destination_account_name']) > 0) {
- $destinationType = AccountType::where('type', 'Expense account')->first();
+ $destinationType = AccountType::where('type', AccountType::EXPENSE)->first();
$destinationAccount = Account::firstOrCreateEncrypted(
[
'user_id' => $data['user'],
@@ -575,14 +594,69 @@ class JournalRepository implements JournalRepositoryInterface
]
);
- return [$sourceAccount, $destinationAccount];
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
}
$destinationType = AccountType::where('type', 'Cash account')->first();
$destinationAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
);
- return [$sourceAccount, $destinationAccount];
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
+
+
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param Account $account
+ * @param string $amount
+ *
+ * @throws FireflyException
+ */
+ private function updateDestinationTransaction(TransactionJournal $journal, Account $account, string $amount)
+ {
+ // should be one:
+ $set = $journal->transactions()->where('amount', '>', 0)->get();
+ if ($set->count() != 1) {
+ throw new FireflyException(
+ sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount more than zero.', $journal->id, $set->count())
+ );
+ }
+ /** @var Transaction $transaction */
+ $transaction = $set->first();
+ $transaction->amount = $amount;
+ $transaction->account_id = $account->id;
+ $transaction->save();
+
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param Account $account
+ * @param string $amount
+ *
+ * @throws FireflyException
+ */
+ private function updateSourceTransaction(TransactionJournal $journal, Account $account, string $amount)
+ {
+ // should be one:
+ $set = $journal->transactions()->where('amount', '<', 0)->get();
+ if ($set->count() != 1) {
+ throw new FireflyException(
+ sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount less than zero.', $journal->id, $set->count())
+ );
+ }
+ /** @var Transaction $transaction */
+ $transaction = $set->first();
+ $transaction->amount = $amount;
+ $transaction->account_id = $account->id;
+ $transaction->save();
}
diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php
index 56c0af0347..5db74eb818 100644
--- a/app/Repositories/Journal/JournalRepositoryInterface.php
+++ b/app/Repositories/Journal/JournalRepositoryInterface.php
@@ -13,11 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Journal;
-use Carbon\Carbon;
-use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
-use Illuminate\Pagination\LengthAwarePaginator;
-use Illuminate\Support\Collection;
/**
* Interface JournalRepositoryInterface
@@ -26,14 +22,6 @@ use Illuminate\Support\Collection;
*/
interface JournalRepositoryInterface
{
- /**
- * Returns the amount in the account before the specified transaction took place.
- *
- * @param Transaction $transaction
- *
- * @return string
- */
- public function balanceBeforeTransaction(Transaction $transaction): string;
/**
* Deletes a journal.
@@ -60,41 +48,6 @@ interface JournalRepositoryInterface
*/
public function first(): TransactionJournal;
- /**
- * Returns a page of a specific type(s) of journal.
- *
- * @param array $types
- * @param int $page
- * @param int $pageSize
- *
- * @return LengthAwarePaginator
- */
- public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator;
-
- /**
- * Returns a collection of ALL journals, given a specific account and a date range.
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection;
-
- /**
- * @param TransactionJournal $journal
- *
- * @return Collection
- */
- public function getPiggyBankEvents(TransactionJournal $journal): Collection;
-
- /**
- * @param TransactionJournal $journal
- *
- * @return Collection
- */
- public function getTransactions(TransactionJournal $journal): Collection;
/**
* @param array $data
@@ -120,4 +73,12 @@ interface JournalRepositoryInterface
*/
public function update(TransactionJournal $journal, array $data): TransactionJournal;
+ /**
+ * @param TransactionJournal $journal
+ * @param array $data
+ *
+ * @return TransactionJournal
+ */
+ public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal;
+
}
diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php
new file mode 100644
index 0000000000..7d9add0dc8
--- /dev/null
+++ b/app/Repositories/Journal/JournalTasker.php
@@ -0,0 +1,293 @@
+user = $user;
+ }
+
+ /**
+ * Returns a page of a specific type(s) of journal.
+ *
+ * @param array $types
+ * @param int $page
+ * @param int $pageSize
+ *
+ * @return LengthAwarePaginator
+ */
+ public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
+ {
+ $offset = ($page - 1) * $pageSize;
+ $query = $this->user->transactionJournals()->expanded()->sortCorrectly();
+ $query->where('transaction_journals.completed', 1);
+ if (count($types) > 0) {
+ $query->transactionTypes($types);
+ }
+ $count = $this->user->transactionJournals()->transactionTypes($types)->count();
+ $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
+ $journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
+
+ return $journals;
+ }
+
+ /**
+ * Returns a collection of ALL journals, given a specific account and a date range.
+ *
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection
+ {
+ $query = $this->user->transactionJournals()->expanded()->sortCorrectly();
+ $query->where('transaction_journals.completed', 1);
+ $query->before($end);
+ $query->after($start);
+
+ if ($accounts->count() > 0) {
+ $ids = $accounts->pluck('id')->toArray();
+ // join source and destination:
+ $query->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+ }
+ );
+ $query->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+
+ $query->where(
+ function (Builder $q) use ($ids) {
+ $q->whereIn('destination.account_id', $ids);
+ $q->orWhereIn('source.account_id', $ids);
+ }
+ );
+ }
+
+ $set = $query->get(TransactionJournal::queryFields());
+
+ return $set;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ *
+ * @return Collection
+ */
+ public function getPiggyBankEvents(TransactionJournal $journal): Collection
+ {
+ /** @var Collection $set */
+ $events = $journal->piggyBankEvents()->get();
+ $events->each(
+ function (PiggyBankEvent $event) {
+ $event->piggyBank = $event->piggyBank()->withTrashed()->first();
+ }
+ );
+
+ return $events;
+ }
+
+ /**
+ * Get an overview of the transactions of a journal, tailored to the view
+ * that shows a transaction (transaction/show/xx).
+ *
+ * @param TransactionJournal $journal
+ *
+ * @return array
+ */
+ public function getTransactionsOverview(TransactionJournal $journal): array
+ {
+ // get all transaction data + the opposite site in one list.
+ /**
+ * select
+ *
+ * source.id,
+ * source.account_id,
+ * source_accounts.name as account_name,
+ * source_accounts.encrypted as account_encrypted,
+ * source.amount,
+ * source.description,
+ *
+ * destination.id as destination_id,
+ * destination.account_id as destination_account_id,
+ * destination_accounts.name as destination_account_name,
+ * destination_accounts.encrypted as destination_account_encrypted
+ *
+ *
+ * from transactions as source
+ *
+ * left join transactions as destination ON source.transaction_journal_id =
+ * destination.transaction_journal_id AND source.amount = destination.amount * -1 AND source.identifier = destination.identifier
+ * -- left join source account name:
+ * left join accounts as source_accounts ON source.account_id = source_accounts.id
+ * left join accounts as destination_accounts ON destination.account_id = destination_accounts.id
+ *
+ * where source.transaction_journal_id = 6600
+ * and source.amount < 0
+ * and source.deleted_at is null
+ */
+ $set = $journal
+ ->transactions()// "source"
+ ->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join
+ ->on('transactions.transaction_journal_id', '=', 'destination.transaction_journal_id')
+ ->where('transactions.amount', '=', DB::raw('destination.amount * -1'))
+ ->where('transactions.identifier', '=', DB::raw('destination.identifier'))
+ ->whereNull('destination.deleted_at');
+ }
+ )
+ ->with(['budgets', 'categories'])
+ ->leftJoin('accounts as source_accounts', 'transactions.account_id', '=', 'source_accounts.id')
+ ->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
+ ->where('transactions.amount', '<', 0)
+ ->whereNull('transactions.deleted_at')
+ ->get(
+ [
+ 'transactions.id',
+ 'transactions.account_id',
+ 'source_accounts.name as account_name',
+ 'source_accounts.encrypted as account_encrypted',
+ 'transactions.amount',
+ 'transactions.description',
+ 'destination.id as destination_id',
+ 'destination.account_id as destination_account_id',
+ 'destination_accounts.name as destination_account_name',
+ 'destination_accounts.encrypted as destination_account_encrypted',
+ ]
+ );
+
+ $transactions = [];
+
+ /** @var Transaction $entry */
+ foreach ($set as $entry) {
+ $sourceBalance = $this->getBalance($entry->id);
+ $destinationBalance = $this->getBalance($entry->destination_id);
+ $budget = $entry->budgets->first();
+ $category = $entry->categories->first();
+ $transaction = [
+ 'source_id' => $entry->id,
+ 'source_amount' => $entry->amount,
+
+ 'description' => $entry->description,
+ 'source_account_id' => $entry->account_id,
+ 'source_account_name' => intval($entry->account_encrypted) === 1 ? Crypt::decrypt($entry->account_name) : $entry->account_name,
+ 'source_account_before' => $sourceBalance,
+ 'source_account_after' => bcadd($sourceBalance, $entry->amount),
+ 'destination_id' => $entry->destination_id,
+ 'destination_amount' => bcmul($entry->amount, '-1'),
+ 'destination_account_id' => $entry->destination_account_id,
+ 'destination_account_name' =>
+ intval($entry->destination_account_encrypted) === 1 ? Crypt::decrypt($entry->destination_account_name) : $entry->destination_account_name,
+ 'destination_account_before' => $destinationBalance,
+ 'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')),
+ 'budget_id' => is_null($budget) ? 0 : $budget->id,
+ 'category' => is_null($category) ? '' : $category->name,
+ ];
+
+
+ $transactions[] = $transaction;
+ }
+
+ return $transactions;
+ }
+
+ /**
+ * Collect the balance of an account before the given transaction has hit. This is tricky, because
+ * the balance does not depend on the transaction itself but the journal it's part of. And of course
+ * the order of transactions within the journal. So the query is pretty complex:
+ *
+ * @param int $transactionId
+ *
+ * @return string
+ */
+ private function getBalance(int $transactionId): string
+ {
+ // find the transaction first:
+ $transaction = Transaction::find($transactionId);
+ $date = $transaction->transactionJournal->date->format('Y-m-d');
+ $order = intval($transaction->transactionJournal->order);
+ $journalId = intval($transaction->transaction_journal_id);
+ $identifier = intval($transaction->identifier);
+
+ // go!
+ $sum
+ = Transaction
+ ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->where('account_id', $transaction->account_id)
+ ->whereNull('transactions.deleted_at')
+ ->whereNull('transaction_journals.deleted_at')
+ ->where('transactions.id', '!=', $transactionId)
+ ->where(
+ function (Builder $q1) use ($date, $order, $journalId, $identifier) {
+ $q1->where('transaction_journals.date', '<', $date); // date
+ $q1->orWhere(
+ function (Builder $q2) use ($date, $order) { // function 1
+ $q2->where('transaction_journals.date', $date);
+ $q2->where('transaction_journals.order', '>', $order);
+ }
+ );
+ $q1->orWhere(
+ function (Builder $q3) use ($date, $order, $journalId) { // function 2
+ $q3->where('transaction_journals.date', $date);
+ $q3->where('transaction_journals.order', $order);
+ $q3->where('transaction_journals.id', '<', $journalId);
+ }
+ );
+ $q1->orWhere(
+ function (Builder $q4) use ($date, $order, $journalId, $identifier) { // function 3
+ $q4->where('transaction_journals.date', $date);
+ $q4->where('transaction_journals.order', $order);
+ $q4->where('transaction_journals.id', $journalId);
+ $q4->where('transactions.identifier', '>', $identifier);
+ }
+ );
+ }
+ )->sum('transactions.amount');
+
+ return strval($sum);
+ }
+}
\ No newline at end of file
diff --git a/app/Repositories/Journal/JournalTaskerInterface.php b/app/Repositories/Journal/JournalTaskerInterface.php
new file mode 100644
index 0000000000..feada074f5
--- /dev/null
+++ b/app/Repositories/Journal/JournalTaskerInterface.php
@@ -0,0 +1,67 @@
+updateNote($piggyBank, $data['note']);
+
return $piggyBank;
}
@@ -196,6 +199,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
$piggyBank->save();
+ $this->updateNote($piggyBank, $data['note']);
+
// if the piggy bank is now smaller than the current relevant rep,
// remove money from the rep.
$repetition = $piggyBank->currentRelevantRep();
@@ -210,4 +215,31 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
return $piggyBank;
}
+
+ /**
+ * @param PiggyBank $piggyBank
+ * @param string $note
+ *
+ * @return bool
+ */
+ private function updateNote(PiggyBank $piggyBank, string $note): bool
+ {
+ if (strlen($note) === 0) {
+ $dbNote = $piggyBank->notes()->first();
+ if (!is_null($dbNote)) {
+ $dbNote->delete();
+ }
+
+ return true;
+ }
+ $dbNote= $piggyBank->notes()->first();
+ if (is_null($dbNote)) {
+ $dbNote= new Note();
+ $dbNote->noteable()->associate($piggyBank);
+ }
+ $dbNote->text = trim($note);
+ $dbNote->save();
+
+ return true;
+ }
}
diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php
index b3551077b1..97642b2ea9 100644
--- a/app/Repositories/User/UserRepository.php
+++ b/app/Repositories/User/UserRepository.php
@@ -14,9 +14,11 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\User;
+use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Role;
use FireflyIII\User;
use Illuminate\Support\Collection;
+use Preferences;
/**
* Class UserRepository
@@ -56,4 +58,74 @@ class UserRepository implements UserRepositoryInterface
{
return $this->all()->count();
}
+
+ /**
+ * @param int $userId
+ *
+ * @return User
+ */
+ public function find(int $userId): User
+ {
+ $user = User::find($userId);
+ if (!is_null($user)) {
+ return $user;
+ }
+
+ return new User;
+ }
+
+ /**
+ * Return basic user information.
+ *
+ * @param User $user
+ *
+ * @return array
+ */
+ public function getUserData(User $user): array
+ {
+ $return = [];
+
+ // two factor:
+ $is2faEnabled = Preferences::getForUser($user, 'twoFactorAuthEnabled', false)->data;
+ $has2faSecret = !is_null(Preferences::getForUser($user, 'twoFactorAuthSecret'));
+ $return['has_2fa'] = false;
+ if ($is2faEnabled && $has2faSecret) {
+ $return['has_2fa'] = true;
+ }
+
+ // is user activated?
+ $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
+ $isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data;
+ $return['is_activated'] = true;
+ if ($isConfirmed === false && $confirmAccount === true) {
+ $return['is_activated'] = false;
+ }
+
+ $return['is_admin'] = $user->hasRole('owner');
+ $return['blocked'] = intval($user->blocked) === 1;
+ $return['blocked_code'] = $user->blocked_code;
+ $return['accounts'] = $user->accounts()->count();
+ $return['journals'] = $user->transactionJournals()->count();
+ $return['transactions'] = $user->transactions()->count();
+ $return['attachments'] = $user->attachments()->count();
+ $return['attachments_size'] = $user->attachments()->sum('size');
+ $return['bills'] = $user->bills()->count();
+ $return['categories'] = $user->categories()->count();
+ $return['budgets'] = $user->budgets()->count();
+ $return['budgets_with_limits'] = BudgetLimit
+ ::distinct()
+ ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
+ ->where('amount', '>', 0)
+ ->whereNull('budgets.deleted_at')
+ ->where('budgets.user_id', $user->id)->get(['budget_limits.budget_id'])->count();
+ $return['export_jobs'] = $user->exportJobs()->count();
+ $return['export_jobs_success'] = $user->exportJobs()->where('status', 'export_downloaded')->count();
+ $return['import_jobs'] = $user->exportJobs()->count();
+ $return['import_jobs_success'] = $user->exportJobs()->where('status', 'import_complete')->count();
+ $return['rule_groups'] = $user->ruleGroups()->count();
+ $return['rules'] = $user->rules()->count();
+ $return['tags'] = $user->tags()->count();
+
+ return $return;
+ }
}
diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php
index e37cd1a39d..1855fc1da1 100644
--- a/app/Repositories/User/UserRepositoryInterface.php
+++ b/app/Repositories/User/UserRepositoryInterface.php
@@ -47,4 +47,20 @@ interface UserRepositoryInterface
* @return int
*/
public function count(): int;
+
+ /**
+ * @param int $userId
+ *
+ * @return User
+ */
+ public function find(int $userId): User;
+
+ /**
+ * Return basic user information.
+ *
+ * @param User $user
+ *
+ * @return array
+ */
+ public function getUserData(User $user): array;
}
diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php
index 82b5429777..7491bbbb88 100644
--- a/app/Rules/TransactionMatcher.php
+++ b/app/Rules/TransactionMatcher.php
@@ -15,7 +15,7 @@ namespace FireflyIII\Rules;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
-use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Support\Collection;
use Log;
@@ -31,8 +31,8 @@ class TransactionMatcher
private $limit = 10;
/** @var int Maximum number of transaction to search in (for performance reasons) * */
private $range = 200;
- /** @var JournalRepositoryInterface */
- private $repository;
+ /** @var JournalTaskerInterface */
+ private $tasker;
/** @var array */
private $transactionTypes = [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
/** @var array List of triggers to match */
@@ -41,11 +41,11 @@ class TransactionMatcher
/**
* TransactionMatcher constructor. Typehint the repository.
*
- * @param JournalRepositoryInterface $repository
+ * @param JournalTaskerInterface $tasker
*/
- public function __construct(JournalRepositoryInterface $repository)
+ public function __construct(JournalTaskerInterface $tasker)
{
- $this->repository = $repository;
+ $this->tasker = $tasker;
}
@@ -76,7 +76,7 @@ class TransactionMatcher
// - the maximum number of transactions to search in have been searched
do {
// Fetch a batch of transactions from the database
- $paginator = $this->repository->getJournals($this->transactionTypes, $page, $pagesize);
+ $paginator = $this->tasker->getJournals($this->transactionTypes, $page, $pagesize);
$set = $paginator->getCollection();
diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php
index 36682352d3..495fd2d75c 100644
--- a/app/Support/ExpandedForm.php
+++ b/app/Support/ExpandedForm.php
@@ -427,6 +427,7 @@ class ExpandedForm
*/
protected function expandOptionArray(string $name, $label, array $options): array
{
+ $name = str_replace('[]', '', $name);
$options['class'] = 'form-control';
$options['id'] = 'ffInput_' . $name;
$options['autocomplete'] = 'off';
@@ -494,6 +495,7 @@ class ExpandedForm
if (isset($options['label'])) {
return $options['label'];
}
+ $name = str_replace('[]', '', $name);
return strval(trans('form.' . $name));
diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php
index b2dfda8831..a13a62faab 100644
--- a/app/Support/Twig/General.php
+++ b/app/Support/Twig/General.php
@@ -16,7 +16,6 @@ namespace FireflyIII\Support\Twig;
use Carbon\Carbon;
use Config;
use FireflyIII\Models\Account;
-use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Route;
use Twig_Extension;
diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php
index 1e4105bf2f..9230f89cc9 100644
--- a/app/Support/Twig/Transaction.php
+++ b/app/Support/Twig/Transaction.php
@@ -29,6 +29,20 @@ use Twig_SimpleFunction;
*/
class Transaction extends Twig_Extension
{
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function formatAmountPlainWithCode(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatAmountPlainWithCode', function (string $amount, string $code): string {
+
+ return Amount::formatWithCode($code, $amount, false);
+
+ }, ['is_safe' => ['html']]
+ );
+ }
+
/**
* @return Twig_SimpleFunction
*/
@@ -62,11 +76,14 @@ class Transaction extends Twig_Extension
{
$functions = [
$this->formatAmountWithCode(),
+ $this->formatAmountPlainWithCode(),
$this->transactionSourceAccount(),
$this->transactionDestinationAccount(),
$this->optionalJournalAmount(),
$this->transactionBudgets(),
+ $this->transactionIdBudgets(),
$this->transactionCategories(),
+ $this->transactionIdCategories(),
$this->splitJournalIndicator(),
];
@@ -144,22 +161,7 @@ class Transaction extends Twig_Extension
{
return new Twig_SimpleFunction(
'transactionBudgets', function (TransactionModel $transaction): string {
- // see if the transaction has a budget:
- $budgets = $transaction->budgets()->get();
- if ($budgets->count() === 0) {
- $budgets = $transaction->transactionJournal()->first()->budgets()->get();
- }
- if ($budgets->count() > 0) {
- $str = [];
- foreach ($budgets as $budget) {
- $str[] = sprintf('%s', route('budgets.show', [$budget->id]), $budget->name, $budget->name);
- }
-
- return join(', ', $str);
- }
-
-
- return '';
+ return $this->getTransactionBudgets($transaction);
}, ['is_safe' => ['html']]
);
}
@@ -171,21 +173,7 @@ class Transaction extends Twig_Extension
{
return new Twig_SimpleFunction(
'transactionCategories', function (TransactionModel $transaction): string {
- // see if the transaction has a category:
- $categories = $transaction->categories()->get();
- if ($categories->count() === 0) {
- $categories = $transaction->transactionJournal()->first()->categories()->get();
- }
- if ($categories->count() > 0) {
- $str = [];
- foreach ($categories as $category) {
- $str[] = sprintf('%s', route('categories.show', [$category->id]), $category->name, $category->name);
- }
-
- return join(', ', $str);
- }
-
- return '';
+ return $this->getTransactionCategories($transaction);
}, ['is_safe' => ['html']]
);
}
@@ -228,6 +216,34 @@ class Transaction extends Twig_Extension
);
}
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function transactionIdBudgets(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'transactionIdBudgets', function (int $transactionId): string {
+ $transaction = TransactionModel::find($transactionId);
+
+ return $this->getTransactionBudgets($transaction);
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function transactionIdCategories(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'transactionIdCategories', function (int $transactionId): string {
+ $transaction = TransactionModel::find($transactionId);
+
+ return $this->getTransactionCategories($transaction);
+ }, ['is_safe' => ['html']]
+ );
+ }
+
/**
* @return Twig_SimpleFunction
*/
@@ -298,4 +314,53 @@ class Transaction extends Twig_Extension
}, ['is_safe' => ['html']]
);
}
+
+ /**
+ * @param TransactionModel $transaction
+ *
+ * @return string
+ */
+ private function getTransactionBudgets(TransactionModel $transaction): string
+ {
+ // see if the transaction has a budget:
+ $budgets = $transaction->budgets()->get();
+ if ($budgets->count() === 0) {
+ $budgets = $transaction->transactionJournal()->first()->budgets()->get();
+ }
+ if ($budgets->count() > 0) {
+ $str = [];
+ foreach ($budgets as $budget) {
+ $str[] = sprintf('%s', route('budgets.show', [$budget->id]), $budget->name, $budget->name);
+ }
+
+ return join(', ', $str);
+ }
+
+
+ return '';
+ }
+
+ /**
+ * @param TransactionModel $transaction
+ *
+ * @return string
+ */
+ private function getTransactionCategories(TransactionModel $transaction): string
+ {
+ // see if the transaction has a category:
+ $categories = $transaction->categories()->get();
+ if ($categories->count() === 0) {
+ $categories = $transaction->transactionJournal()->first()->categories()->get();
+ }
+ if ($categories->count() > 0) {
+ $str = [];
+ foreach ($categories as $category) {
+ $str[] = sprintf('%s', route('categories.show', [$category->id]), $category->name, $category->name);
+ }
+
+ return join(', ', $str);
+ }
+
+ return '';
+ }
}
\ No newline at end of file
diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php
index 6d569fd3e4..8a272dd397 100644
--- a/app/Validation/FireflyValidator.php
+++ b/app/Validation/FireflyValidator.php
@@ -124,6 +124,28 @@ class FireflyValidator extends Validator
return (intval($checksum) === 1);
}
+ /**
+ * @param $attribute
+ * @param $value
+ * @param $parameters
+ *
+ * @return bool
+ */
+ public function validateMustExist($attribute, $value, $parameters): bool
+ {
+ $field = $parameters[1] ?? 'id';
+
+ if (intval($value) === 0) {
+ return true;
+ }
+ $count = DB::table($parameters[0])->where($field, $value)->count();
+ if ($count === 1) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* @param $attribute
*
diff --git a/composer.json b/composer.json
index 5bfe78718a..b443593700 100755
--- a/composer.json
+++ b/composer.json
@@ -28,7 +28,7 @@
"require": {
"php": ">=7.0.0",
"ext-intl": "*",
- "laravel/framework": "5.3.*",
+ "laravel/framework": "5.3.18",
"davejamesmiller/laravel-breadcrumbs": "^3.0",
"watson/validating": "^3.0",
"doctrine/dbal": "^2.5",
@@ -76,6 +76,7 @@
"post-update-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"php artisan firefly:upgrade-instructions",
+ "php artisan firefly:upgrade-database",
"php artisan firefly:verify",
"php artisan optimize"
]
diff --git a/composer.lock b/composer.lock
index 16feffb837..0b571ce03d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "ce5008a06e815bab7e4027161939b57e",
- "content-hash": "8a35f78c306f3ddd1080d7c1b687f77d",
+ "hash": "cc3d23620e727ee1f4741b2e83f8685f",
+ "content-hash": "473d3c681e5c41989e9dced651a939df",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -1024,16 +1024,16 @@
},
{
"name": "laravel/framework",
- "version": "v5.3.10",
+ "version": "v5.3.18",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "6febb0ee61999cde3bc7b2963c8903032bb22691"
+ "reference": "9bee167d173857c25966c19afdaa66f127ca6784"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/6febb0ee61999cde3bc7b2963c8903032bb22691",
- "reference": "6febb0ee61999cde3bc7b2963c8903032bb22691",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/9bee167d173857c25966c19afdaa66f127ca6784",
+ "reference": "9bee167d173857c25966c19afdaa66f127ca6784",
"shasum": ""
},
"require": {
@@ -1148,7 +1148,7 @@
"framework",
"laravel"
],
- "time": "2016-09-20 13:46:16"
+ "time": "2016-10-08 01:51:20"
},
{
"name": "laravelcollective/html",
@@ -1332,20 +1332,20 @@
},
{
"name": "league/flysystem",
- "version": "1.0.27",
+ "version": "1.0.32",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9"
+ "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/50e2045ed70a7e75a5e30bc3662904f3b67af8a9",
- "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1b5c4a0031697f46e779a9d1b309c2e1b24daeab",
+ "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab",
"shasum": ""
},
"require": {
- "php": ">=5.4.0"
+ "php": ">=5.5.9"
},
"conflict": {
"league/flysystem-sftp": "<1.0.6"
@@ -1411,7 +1411,7 @@
"sftp",
"storage"
],
- "time": "2016-08-10 08:55:11"
+ "time": "2016-10-19 20:38:46"
},
{
"name": "maximebf/debugbar",
@@ -1696,16 +1696,16 @@
},
{
"name": "paragonie/random_compat",
- "version": "v2.0.2",
+ "version": "v2.0.3",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
- "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf"
+ "reference": "c0125896dbb151380ab47e96c621741e79623beb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf",
- "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c0125896dbb151380ab47e96c621741e79623beb",
+ "reference": "c0125896dbb151380ab47e96c621741e79623beb",
"shasum": ""
},
"require": {
@@ -1740,7 +1740,7 @@
"pseudorandom",
"random"
],
- "time": "2016-04-03 06:00:07"
+ "time": "2016-10-17 15:23:22"
},
{
"name": "pragmarx/google2fa",
@@ -1805,16 +1805,16 @@
},
{
"name": "psr/log",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
- "reference": "5277094ed527a1c4477177d102fe4c53551953e0"
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0",
- "reference": "5277094ed527a1c4477177d102fe4c53551953e0",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
@@ -1848,7 +1848,7 @@
"psr",
"psr-3"
],
- "time": "2016-09-19 16:02:08"
+ "time": "2016-10-10 12:19:37"
},
{
"name": "psy/psysh",
@@ -1924,16 +1924,16 @@
},
{
"name": "ramsey/uuid",
- "version": "3.5.0",
+ "version": "3.5.1",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
- "reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786"
+ "reference": "a07797b986671b0dc823885a81d5e3516b931599"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/uuid/zipball/a6d15c8618ea3951fd54d34e326b68d3d0bc0786",
- "reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/a07797b986671b0dc823885a81d5e3516b931599",
+ "reference": "a07797b986671b0dc823885a81d5e3516b931599",
"shasum": ""
},
"require": {
@@ -2000,7 +2000,7 @@
"identifier",
"uuid"
],
- "time": "2016-08-02 18:39:32"
+ "time": "2016-10-02 15:51:17"
},
{
"name": "rcrowe/twigbridge",
@@ -2068,23 +2068,23 @@
},
{
"name": "rmccue/requests",
- "version": "v1.6.1",
+ "version": "v1.7.0",
"source": {
"type": "git",
"url": "https://github.com/rmccue/Requests.git",
- "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea"
+ "reference": "87932f52ffad70504d93f04f15690cf16a089546"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rmccue/Requests/zipball/6aac485666c2955077d77b796bbdd25f0013a4ea",
- "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea",
+ "url": "https://api.github.com/repos/rmccue/Requests/zipball/87932f52ffad70504d93f04f15690cf16a089546",
+ "reference": "87932f52ffad70504d93f04f15690cf16a089546",
"shasum": ""
},
"require": {
"php": ">=5.2"
},
"require-dev": {
- "satooshi/php-coveralls": "dev-master"
+ "requests/test-server": "dev-master"
},
"type": "library",
"autoload": {
@@ -2113,7 +2113,7 @@
"iri",
"sockets"
],
- "time": "2014-05-18 04:59:02"
+ "time": "2016-10-13 00:11:37"
},
{
"name": "swiftmailer/swiftmailer",
@@ -2170,16 +2170,16 @@
},
{
"name": "symfony/class-loader",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
- "reference": "2d0ba77c46ecc96a6641009a98f72632216811ba"
+ "reference": "bcb072aba46ddf3b1a496438c63be6be647739aa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/class-loader/zipball/2d0ba77c46ecc96a6641009a98f72632216811ba",
- "reference": "2d0ba77c46ecc96a6641009a98f72632216811ba",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/bcb072aba46ddf3b1a496438c63be6be647739aa",
+ "reference": "bcb072aba46ddf3b1a496438c63be6be647739aa",
"shasum": ""
},
"require": {
@@ -2222,24 +2222,25 @@
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
- "time": "2016-08-23 13:39:15"
+ "time": "2016-09-06 23:30:54"
},
{
"name": "symfony/console",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563"
+ "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/8ea494c34f0f772c3954b5fbe00bffc5a435e563",
- "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563",
+ "url": "https://api.github.com/repos/symfony/console/zipball/6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
+ "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
+ "symfony/debug": "~2.8|~3.0",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
@@ -2282,20 +2283,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2016-08-19 06:48:39"
+ "time": "2016-09-28 00:11:12"
},
{
"name": "symfony/debug",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11"
+ "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/34f6ac18c2974ca5fce68adf419ee7d15def6f11",
- "reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/e2b3f74a67fc928adc3c1b9027f73e1bc01190a8",
+ "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8",
"shasum": ""
},
"require": {
@@ -2339,11 +2340,11 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2016-08-23 13:39:15"
+ "time": "2016-09-06 11:02:40"
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
@@ -2403,16 +2404,16 @@
},
{
"name": "symfony/finder",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577"
+ "reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/e568ef1784f447a0e54dcb6f6de30b9747b0f577",
- "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/205b5ffbb518a98ba2ae60a52656c4a31ab00c6f",
+ "reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f",
"shasum": ""
},
"require": {
@@ -2448,20 +2449,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2016-08-26 12:04:02"
+ "time": "2016-09-28 00:11:12"
},
{
"name": "symfony/http-foundation",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4"
+ "reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/63592e00fd90632b57ee50220a1ddb29b6bf3bb4",
- "reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5114f1becca9f29e3af94374f1689c83c1aa3d97",
+ "reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97",
"shasum": ""
},
"require": {
@@ -2501,20 +2502,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2016-08-22 12:11:19"
+ "time": "2016-09-21 20:55:10"
},
{
"name": "symfony/http-kernel",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3"
+ "reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aeda215d6b01f119508c090d2a09ebb5b0bc61f3",
- "reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/dc339d6eebadfa6e39c52868b4d4a715eff13c69",
+ "reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69",
"shasum": ""
},
"require": {
@@ -2583,7 +2584,7 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2016-09-03 15:28:24"
+ "time": "2016-10-03 19:01:06"
},
{
"name": "symfony/polyfill-mbstring",
@@ -2754,16 +2755,16 @@
},
{
"name": "symfony/process",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "e64e93041c80e77197ace5ab9385dedb5a143697"
+ "reference": "66de154ae86b1a07001da9fbffd620206e4faf94"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/e64e93041c80e77197ace5ab9385dedb5a143697",
- "reference": "e64e93041c80e77197ace5ab9385dedb5a143697",
+ "url": "https://api.github.com/repos/symfony/process/zipball/66de154ae86b1a07001da9fbffd620206e4faf94",
+ "reference": "66de154ae86b1a07001da9fbffd620206e4faf94",
"shasum": ""
},
"require": {
@@ -2799,11 +2800,11 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2016-08-16 14:58:24"
+ "time": "2016-09-29 14:13:09"
},
{
"name": "symfony/routing",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
@@ -2878,16 +2879,16 @@
},
{
"name": "symfony/translation",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "a35edc277513c9bc0f063ca174c36b346f974528"
+ "reference": "93013a18d272e59dab8e67f583155b78c68947eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/a35edc277513c9bc0f063ca174c36b346f974528",
- "reference": "a35edc277513c9bc0f063ca174c36b346f974528",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/93013a18d272e59dab8e67f583155b78c68947eb",
+ "reference": "93013a18d272e59dab8e67f583155b78c68947eb",
"shasum": ""
},
"require": {
@@ -2938,20 +2939,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2016-08-05 08:37:39"
+ "time": "2016-09-06 11:02:40"
},
{
"name": "symfony/var-dumper",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "62ee73706c421654a4c840028954510277f7dfc8"
+ "reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/62ee73706c421654a4c840028954510277f7dfc8",
- "reference": "62ee73706c421654a4c840028954510277f7dfc8",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/70bfe927b86ba9999aeebd829715b0bb2cd39a10",
+ "reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10",
"shasum": ""
},
"require": {
@@ -3001,20 +3002,20 @@
"debug",
"dump"
],
- "time": "2016-08-31 09:05:42"
+ "time": "2016-09-29 14:13:09"
},
{
"name": "twig/twig",
- "version": "v1.25.0",
+ "version": "v1.26.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "f16a634ab08d87e520da5671ec52153d627f10f6"
+ "reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/f16a634ab08d87e520da5671ec52153d627f10f6",
- "reference": "f16a634ab08d87e520da5671ec52153d627f10f6",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
+ "reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
"shasum": ""
},
"require": {
@@ -3027,7 +3028,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.25-dev"
+ "dev-master": "1.26-dev"
}
},
"autoload": {
@@ -3062,7 +3063,7 @@
"keywords": [
"templating"
],
- "time": "2016-09-21 23:05:12"
+ "time": "2016-10-05 18:57:41"
},
{
"name": "vlucas/phpdotenv",
@@ -3481,16 +3482,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
- "version": "3.1.0",
+ "version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "9270140b940ff02e58ec577c237274e92cd40cdd"
+ "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd",
- "reference": "9270140b940ff02e58ec577c237274e92cd40cdd",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
+ "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
"shasum": ""
},
"require": {
@@ -3522,7 +3523,7 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "time": "2016-06-10 09:48:41"
+ "time": "2016-09-30 07:12:33"
},
{
"name": "phpdocumentor/type-resolver",
@@ -3879,16 +3880,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "5.5.5",
+ "version": "5.6.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a57126dc681b08289fef6ac96a48e30656f84350"
+ "reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a57126dc681b08289fef6ac96a48e30656f84350",
- "reference": "a57126dc681b08289fef6ac96a48e30656f84350",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60c32c5b5e79c2248001efa2560f831da11cc2d7",
+ "reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7",
"shasum": ""
},
"require": {
@@ -3922,7 +3923,6 @@
"ext-pdo": "*"
},
"suggest": {
- "ext-tidy": "*",
"ext-xdebug": "*",
"phpunit/php-invoker": "~1.1"
},
@@ -3932,7 +3932,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.5.x-dev"
+ "dev-master": "5.6.x-dev"
}
},
"autoload": {
@@ -3958,20 +3958,20 @@
"testing",
"xunit"
],
- "time": "2016-09-21 14:40:13"
+ "time": "2016-10-07 13:03:26"
},
{
"name": "phpunit/phpunit-mock-objects",
- "version": "3.2.7",
+ "version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a"
+ "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/546898a2c0c356ef2891b39dd7d07f5d82c8ed0a",
- "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/238d7a2723bce689c79eeac9c7d5e1d623bb9dc2",
+ "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2",
"shasum": ""
},
"require": {
@@ -4017,7 +4017,7 @@
"mock",
"xunit"
],
- "time": "2016-09-06 16:07:45"
+ "time": "2016-10-09 07:01:45"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -4534,16 +4534,16 @@
},
{
"name": "symfony/css-selector",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "2851e1932d77ce727776154d659b232d061e816a"
+ "reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/2851e1932d77ce727776154d659b232d061e816a",
- "reference": "2851e1932d77ce727776154d659b232d061e816a",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/ca809c64072e0fe61c1c7fb3c76cdc32265042ac",
+ "reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac",
"shasum": ""
},
"require": {
@@ -4583,11 +4583,11 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2016-06-29 05:41:56"
+ "time": "2016-09-06 11:02:40"
},
{
"name": "symfony/dom-crawler",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
@@ -4643,16 +4643,16 @@
},
{
"name": "symfony/yaml",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d"
+ "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/f291ed25eb1435bddbe8a96caaef16469c2a092d",
- "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/368b9738d4033c8b93454cb0dbd45d305135a6d3",
+ "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3",
"shasum": ""
},
"require": {
@@ -4688,7 +4688,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2016-09-02 02:12:52"
+ "time": "2016-09-25 08:27:07"
},
{
"name": "webmozart/assert",
@@ -4747,7 +4747,8 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=7.0.0"
+ "php": ">=7.0.0",
+ "ext-intl": "*"
},
"platform-dev": []
}
diff --git a/config/app.php b/config/app.php
index 0dfd97b9f8..0611c68b7f 100755
--- a/config/app.php
+++ b/config/app.php
@@ -188,8 +188,8 @@ return [
// own stuff:
- //Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
- //Barryvdh\Debugbar\ServiceProvider::class,
+// Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
+// Barryvdh\Debugbar\ServiceProvider::class,
DaveJamesMiller\Breadcrumbs\ServiceProvider::class,
TwigBridge\ServiceProvider::class,
'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider',
@@ -226,50 +226,50 @@ return [
'aliases' => [
- 'App' => Illuminate\Support\Facades\App::class,
- 'Artisan' => Illuminate\Support\Facades\Artisan::class,
- 'Auth' => Illuminate\Support\Facades\Auth::class,
- 'Blade' => Illuminate\Support\Facades\Blade::class,
- 'Cache' => Illuminate\Support\Facades\Cache::class,
- 'Config' => Illuminate\Support\Facades\Config::class,
- 'Cookie' => Illuminate\Support\Facades\Cookie::class,
- 'Crypt' => Illuminate\Support\Facades\Crypt::class,
- 'DB' => Illuminate\Support\Facades\DB::class,
- 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
- 'Event' => Illuminate\Support\Facades\Event::class,
- 'File' => Illuminate\Support\Facades\File::class,
- 'Gate' => Illuminate\Support\Facades\Gate::class,
- 'Hash' => Illuminate\Support\Facades\Hash::class,
- 'Lang' => Illuminate\Support\Facades\Lang::class,
- 'Log' => Illuminate\Support\Facades\Log::class,
- 'Mail' => Illuminate\Support\Facades\Mail::class,
- 'Notification' => Illuminate\Support\Facades\Notification::class,
- 'Password' => Illuminate\Support\Facades\Password::class,
- 'Queue' => Illuminate\Support\Facades\Queue::class,
- 'Redirect' => Illuminate\Support\Facades\Redirect::class,
- 'Redis' => Illuminate\Support\Facades\Redis::class,
- 'Request' => Illuminate\Support\Facades\Request::class,
- 'Response' => Illuminate\Support\Facades\Response::class,
- 'Route' => Illuminate\Support\Facades\Route::class,
- 'Schema' => Illuminate\Support\Facades\Schema::class,
- 'Session' => Illuminate\Support\Facades\Session::class,
- 'Storage' => Illuminate\Support\Facades\Storage::class,
- 'URL' => Illuminate\Support\Facades\URL::class,
- 'Validator' => Illuminate\Support\Facades\Validator::class,
- 'View' => Illuminate\Support\Facades\View::class,
- 'Twig' => 'TwigBridge\Facade\Twig',
- 'Form' => Collective\Html\FormFacade::class,
- 'Html' => Collective\Html\HtmlFacade::class,
- 'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade',
- 'Preferences' => 'FireflyIII\Support\Facades\Preferences',
- 'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig',
- 'Navigation' => 'FireflyIII\Support\Facades\Navigation',
- 'Amount' => 'FireflyIII\Support\Facades\Amount',
- 'Steam' => 'FireflyIII\Support\Facades\Steam',
- 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
- 'Entrust' => 'Zizaco\Entrust\EntrustFacade',
- 'Input' => 'Illuminate\Support\Facades\Input',
- 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade',
+ 'App' => Illuminate\Support\Facades\App::class,
+ 'Artisan' => Illuminate\Support\Facades\Artisan::class,
+ 'Auth' => Illuminate\Support\Facades\Auth::class,
+ 'Blade' => Illuminate\Support\Facades\Blade::class,
+ 'Cache' => Illuminate\Support\Facades\Cache::class,
+ 'Config' => Illuminate\Support\Facades\Config::class,
+ 'Cookie' => Illuminate\Support\Facades\Cookie::class,
+ 'Crypt' => Illuminate\Support\Facades\Crypt::class,
+ 'DB' => Illuminate\Support\Facades\DB::class,
+ 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
+ 'Event' => Illuminate\Support\Facades\Event::class,
+ 'File' => Illuminate\Support\Facades\File::class,
+ 'Gate' => Illuminate\Support\Facades\Gate::class,
+ 'Hash' => Illuminate\Support\Facades\Hash::class,
+ 'Lang' => Illuminate\Support\Facades\Lang::class,
+ 'Log' => Illuminate\Support\Facades\Log::class,
+ 'Mail' => Illuminate\Support\Facades\Mail::class,
+ 'Notification' => Illuminate\Support\Facades\Notification::class,
+ 'Password' => Illuminate\Support\Facades\Password::class,
+ 'Queue' => Illuminate\Support\Facades\Queue::class,
+ 'Redirect' => Illuminate\Support\Facades\Redirect::class,
+ 'Redis' => Illuminate\Support\Facades\Redis::class,
+ 'Request' => Illuminate\Support\Facades\Request::class,
+ 'Response' => Illuminate\Support\Facades\Response::class,
+ 'Route' => Illuminate\Support\Facades\Route::class,
+ 'Schema' => Illuminate\Support\Facades\Schema::class,
+ 'Session' => Illuminate\Support\Facades\Session::class,
+ 'Storage' => Illuminate\Support\Facades\Storage::class,
+ 'URL' => Illuminate\Support\Facades\URL::class,
+ 'Validator' => Illuminate\Support\Facades\Validator::class,
+ 'View' => Illuminate\Support\Facades\View::class,
+ 'Twig' => 'TwigBridge\Facade\Twig',
+ 'Form' => Collective\Html\FormFacade::class,
+ 'Html' => Collective\Html\HtmlFacade::class,
+ 'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade',
+ 'Preferences' => 'FireflyIII\Support\Facades\Preferences',
+ 'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig',
+ 'Navigation' => 'FireflyIII\Support\Facades\Navigation',
+ 'Amount' => 'FireflyIII\Support\Facades\Amount',
+ 'Steam' => 'FireflyIII\Support\Facades\Steam',
+ 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
+ 'Entrust' => 'Zizaco\Entrust\EntrustFacade',
+ 'Input' => 'Illuminate\Support\Facades\Input',
+ 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade',
],
diff --git a/config/firefly.php b/config/firefly.php
index 6ffc2ed269..4d3f6a54cb 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -22,7 +22,7 @@ return [
'single_user_mode' => true,
],
'chart' => 'chartjs',
- 'version' => '4.0.2',
+ 'version' => '4.1',
'csv_import_enabled' => true,
'maxUploadSize' => 5242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
diff --git a/config/upgrade.php b/config/upgrade.php
index 8aec28d716..82a97f50a0 100644
--- a/config/upgrade.php
+++ b/config/upgrade.php
@@ -28,5 +28,6 @@ return [
'3.8' => 'This version of Firefly III requires PHP 7.0.',
'3.10' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-3.10',
'4.0' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0',
+ '4.1' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0',
],
];
diff --git a/database/migrations/2016_10_22_075804_changes_for_v410.php b/database/migrations/2016_10_22_075804_changes_for_v410.php
new file mode 100644
index 0000000000..bfd8c71085
--- /dev/null
+++ b/database/migrations/2016_10_22_075804_changes_for_v410.php
@@ -0,0 +1,41 @@
+increments('id');
+ $table->timestamps();
+ $table->softDeletes();
+ $table->integer('noteable_id', false, true);
+ $table->string('noteable_type');
+ $table->string('title')->nullable();
+ $table->text('text')->nullable();
+ }
+ );
+ }
+}
diff --git a/public/css/firefly.css b/public/css/firefly.css
index 354ea0b15b..bd46d1e920 100644
--- a/public/css/firefly.css
+++ b/public/css/firefly.css
@@ -20,6 +20,13 @@ body.waiting * {
height: 1px;
}
+.preferences-box {
+ border:1px #ddd solid;
+ border-radius: 4px 4px 0 0;
+ padding: 15px;
+ margin: 15px;
+}
+
#map-canvas {
height: 100%;
margin: 0;
@@ -76,6 +83,6 @@ body.waiting * {
}
.loading {
- background:url('/images/loading-small.gif') no-repeat center center;
- min-height:30px;
+ background: url('/images/loading-small.gif') no-repeat center center;
+ min-height: 30px;
}
\ No newline at end of file
diff --git a/public/js/ff/index.js b/public/js/ff/index.js
index faeed6f099..c714f83ac7 100644
--- a/public/js/ff/index.js
+++ b/public/js/ff/index.js
@@ -37,6 +37,7 @@ function drawChart() {
stackedColumnChart('chart/budget/frontpage', 'budgets-chart', {beforeDraw: beforeDrawIsEmpty});
columnChart('chart/category/frontpage', 'categories-chart', {beforeDraw: beforeDrawIsEmpty});
columnChart('chart/account/expense', 'expense-accounts-chart', {beforeDraw: beforeDrawIsEmpty});
+ columnChart('chart/account/revenue', 'revenue-accounts-chart', {beforeDraw: beforeDrawIsEmpty});
getBoxAmounts();
diff --git a/public/js/ff/split/journal/from-store.js b/public/js/ff/split/journal/from-store.js
index 5c8b0ca708..77856a0156 100644
--- a/public/js/ff/split/journal/from-store.js
+++ b/public/js/ff/split/journal/from-store.js
@@ -14,57 +14,167 @@ var categories = {};
$(function () {
"use strict";
$('.btn-do-split').click(cloneRow);
+ $('.remove-current-split').click(removeRow);
$.getJSON('json/expense-accounts').done(function (data) {
destAccounts = data;
console.log('destAccounts length is now ' + destAccounts.length);
+ $('input[name$="destination_account_name]"]').typeahead({source: destAccounts});
});
$.getJSON('json/revenue-accounts').done(function (data) {
srcAccounts = data;
console.log('srcAccounts length is now ' + srcAccounts.length);
+ $('input[name$="source_account_name]"]').typeahead({source: srcAccounts});
});
$.getJSON('json/categories').done(function (data) {
categories = data;
console.log('categories length is now ' + categories.length);
+ $('input[name$="category]"]').typeahead({source: categories});
});
- $('input[name="amount[]"]').on('input', calculateSum)
+ $('input[name$="][amount]"]').on('input', calculateSum);
+
+ // add auto complete:
+
+
});
+function removeRow(e) {
+ "use strict";
+ var rows = $('table.split-table tbody tr');
+ if (rows.length === 1) {
+ console.log('Will not remove last split');
+ return false;
+ }
+ var row = $(e.target);
+ var index = row.data('split');
+ console.log('Trying to remove row with split ' + index);
+ $('table.split-table tbody tr[data-split="' + index + '"]').remove();
+
+
+
+ resetSplits();
+
+ return false;
+
+}
+
function cloneRow() {
"use strict";
- var source = $('.initial-row').clone();
+ var source = $('.table.split-table tbody tr').last().clone();
var count = $('.split-table tbody tr').length + 1;
+ var index = count - 1;
source.removeClass('initial-row');
source.find('.count').text('#' + count);
- source.find('input[name="amount[]"]').val("").on('input', calculateSum);
+
+ // // get each input, change the name?
+ // $.each(source.find('input, select'), function (i, v) {
+ // var obj = $(v);
+ // var name = obj.attr('name').replace('[0]', '[' + index + ']');
+ // obj.attr('name', name);
+ // });
+
+ source.find('input[name$="][amount]"]').val("").on('input', calculateSum);
if (destAccounts.length > 0) {
console.log('Will be able to extend dest-accounts.');
- source.find('input[name="destination_account_name[]"]').typeahead({source: destAccounts});
+ source.find('input[name$="destination_account_name]"]').typeahead({source: destAccounts});
}
if (destAccounts.length > 0) {
console.log('Will be able to extend src-accounts.');
- source.find('input[name="source_account_name[]"]').typeahead({source: srcAccounts});
+ source.find('input[name$="source_account_name]"]').typeahead({source: srcAccounts});
}
- if(categories.length > 0) {
+ if (categories.length > 0) {
console.log('Will be able to extend categories.');
- source.find('input[name="category[]"]').typeahead({source: categories});
+ source.find('input[name$="category]"]').typeahead({source: categories});
}
$('.split-table tbody').append(source);
+ // remove original click things, add them again:
+ $('.remove-current-split').unbind('click').click(removeRow);
+
+
calculateSum();
+ resetSplits();
return false;
}
+function resetSplits() {
+ "use strict";
+ // loop rows, reset numbers:
+
+ // update the row split number:
+ $.each($('table.split-table tbody tr'), function (i, v) {
+ var row = $(v);
+ row.attr('data-split', i);
+ console.log('Row is now ' + row.data('split'));
+ });
+
+ // loop each remove button, update the index
+ $.each($('.remove-current-split'), function (i, v) {
+ var button = $(v);
+ button.attr('data-split', i);
+ button.find('i').attr('data-split', i);
+ console.log('Remove button index is now ' + button.data('split'));
+
+ });
+
+ // loop each indicator (#) and update it:
+ $.each($('td.count'), function (i, v) {
+ var cell = $(v);
+ var index = i + 1;
+ cell.text('#' + index);
+ console.log('Cell is now ' + cell.text());
+ });
+
+ // loop each possible field.
+
+ // ends with ][description]
+ $.each($('input[name$="][description]"]'), function (i, v) {
+ var input = $(v);
+ input.attr('name', 'transaction[' + i + '][description]');
+ console.log('description is now ' + input.attr('name'));
+ });
+ // ends with ][destination_account_name]
+ $.each($('input[name$="][destination_account_name]"]'), function (i, v) {
+ var input = $(v);
+ input.attr('name', 'transaction[' + i + '][destination_account_name]');
+ console.log('destination_account_name is now ' + input.attr('name'));
+ });
+ // ends with ][source_account_name]
+ $.each($('input[name$="][source_account_name]"]'), function (i, v) {
+ var input = $(v);
+ input.attr('name', 'transaction[' + i + '][source_account_name]');
+ console.log('source_account_name is now ' + input.attr('name'));
+ });
+ // ends with ][amount]
+ $.each($('input[name$="][amount]"]'), function (i, v) {
+ var input = $(v);
+ input.attr('name', 'transaction[' + i + '][amount]');
+ console.log('amount is now ' + input.attr('name'));
+ });
+ // ends with ][budget_id]
+ $.each($('input[name$="][budget_id]"]'), function (i, v) {
+ var input = $(v);
+ input.attr('name', 'transaction[' + i + '][budget_id]');
+ console.log('budget_id is now ' + input.attr('name'));
+ });
+ // ends with ][category]
+ $.each($('input[name$="][category]"]'), function (i, v) {
+ var input = $(v);
+ input.attr('name', 'transaction[' + i + '][category]');
+ console.log('category is now ' + input.attr('name'));
+ });
+}
+
function calculateSum() {
"use strict";
var sum = 0;
- var set = $('input[name="amount[]"]');
+ var set = $('input[name$="][amount]"]');
for (var i = 0; i < set.length; i++) {
var current = $(set[i]);
sum += (current.val() == "" ? 0 : parseFloat(current.val()));
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index 4530d8b6cd..773bef3979 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -69,6 +69,7 @@ return [
'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.',
'source_accounts' => 'Source account(s)',
'destination_accounts' => 'Destination account(s)',
+ 'user_id_is' => 'Your user id is :user',
// repeat frequencies:
'repeat_freq_monthly' => 'monthly',
@@ -273,6 +274,14 @@ return [
'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)',
'pref_save_settings' => 'Save settings',
'saved_preferences' => 'Preferences saved!',
+ 'preferences_general' => 'General',
+ 'preferences_frontpage' => 'Home screen',
+ 'preferences_security' => 'Security',
+ '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_do_show_deposits' => 'Yes, show them',
+ 'successful_count' => 'of which :count successful',
'transaction_page_size_title' => 'Page size',
'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions',
'transaction_page_size_label' => 'Page size',
@@ -661,146 +670,158 @@ return [
'add_money_to_piggy_title' => 'Add money to piggy bank ":name"',
'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"',
'add' => 'Add',
- 'remove' => 'Remove',
- 'max_amount_add' => 'The maximum amount you can add is',
- 'max_amount_remove' => 'The maximum amount you can remove is',
- 'update_piggy_button' => 'Update piggy bank',
- 'update_piggy_title' => 'Update piggy bank ":name"',
- 'updated_piggy_bank' => 'Updated piggy bank ":name"',
- 'details' => 'Details',
- 'events' => 'Events',
- 'target_amount' => 'Target amount',
- 'start_date' => 'Start date',
- 'target_date' => 'Target date',
- 'no_target_date' => 'No target date',
- 'todo' => 'to do',
- 'table' => 'Table',
- 'piggy_bank_not_exists' => 'Piggy bank no longer exists.',
- 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.',
- 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date',
- 'delete_piggy_bank' => 'Delete piggy bank ":name"',
- 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".',
- 'deleted_piggy_bank' => 'Deleted piggy bank ":name"',
- 'added_amount_to_piggy' => 'Added :amount to ":name"',
- 'removed_amount_from_piggy' => 'Removed :amount from ":name"',
- 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".',
+
+ 'remove' => 'Remove',
+ 'max_amount_add' => 'The maximum amount you can add is',
+ 'max_amount_remove' => 'The maximum amount you can remove is',
+ 'update_piggy_button' => 'Update piggy bank',
+ 'update_piggy_title' => 'Update piggy bank ":name"',
+ 'updated_piggy_bank' => 'Updated piggy bank ":name"',
+ 'details' => 'Details',
+ 'events' => 'Events',
+ 'target_amount' => 'Target amount',
+ 'start_date' => 'Start date',
+ 'target_date' => 'Target date',
+ 'no_target_date' => 'No target date',
+ 'todo' => 'to do',
+ 'table' => 'Table',
+ 'piggy_bank_not_exists' => 'Piggy bank no longer exists.',
+ 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.',
+ 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date',
+ 'delete_piggy_bank' => 'Delete piggy bank ":name"',
+ 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".',
+ 'deleted_piggy_bank' => 'Deleted piggy bank ":name"',
+ 'added_amount_to_piggy' => 'Added :amount to ":name"',
+ 'removed_amount_from_piggy' => 'Removed :amount from ":name"',
+ 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".',
// tags
- 'regular_tag' => 'Just a regular tag.',
- 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.',
- '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!',
- 'no_year' => 'No year set',
- 'no_month' => 'No month set',
- 'tag_title_nothing' => 'Default tags',
- 'tag_title_balancingAct' => 'Balancing act tags',
- 'tag_title_advancePayment' => 'Advance payment tags',
- 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.',
- 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money 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.',
+ 'regular_tag' => 'Just a regular tag.',
+ 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.',
+ '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!',
+ 'no_year' => 'No year set',
+ 'no_month' => 'No month set',
+ 'tag_title_nothing' => 'Default tags',
+ 'tag_title_balancingAct' => 'Balancing act tags',
+ 'tag_title_advancePayment' => 'Advance payment tags',
+ 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.',
+ 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money 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_meta' => 'Meta information',
+ 'total_amount' => 'Total amount',
// administration
- 'administration' => 'Administration',
- 'user_administration' => 'User administration',
- 'list_all_users' => 'All users',
- 'all_users' => 'All users',
- 'all_blocked_domains' => 'All blocked domains',
- 'blocked_domains' => 'Blocked domains',
- 'no_domains_banned' => 'No domains blocked',
- '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',
- 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.',
+ 'administration' => 'Administration',
+ 'user_administration' => 'User administration',
+ 'list_all_users' => 'All users',
+ 'all_users' => 'All users',
+ 'all_blocked_domains' => 'All blocked domains',
+ 'blocked_domains' => 'Blocked domains',
+ 'no_domains_banned' => 'No domains blocked',
+ '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',
+ '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',
+ 'total_size' => 'total size',
+ 'budget_or_budgets' => 'budget(s)',
+ 'budgets_with_limits' => 'budget(s) with configured amount',
+ 'rule_or_rules' => 'rule(s)',
+ 'rulegroup_or_groups' => 'rule group(s)',
// split a transaction:
- 'transaction_meta_data' => 'Transaction meta-data',
- 'transaction_dates' => 'Transaction dates',
- 'splits' => 'Splits',
- 'split_title_withdrawal' => 'Split your new withdrawal',
- 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
- 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
- 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
- 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
- '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_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.',
- 'store_splitted_deposit' => 'Store splitted deposit',
- 'split_title_transfer' => 'Split your new transfer',
- 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
- 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
- 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
- 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
- 'store_splitted_transfer' => 'Store splitted transfer',
- 'add_another_split' => 'Add another split',
- 'split-transactions' => 'Split transactions',
- 'split-new-transaction' => 'Split a new transaction',
- 'do_split' => 'Do a split',
- 'split_this_withdrawal' => 'Split this withdrawal',
- 'split_this_deposit' => 'Split this deposit',
- 'split_this_transfer' => 'Split this transfer',
- 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.',
- 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.',
- 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.',
+ 'transaction_meta_data' => 'Transaction meta-data',
+ 'transaction_dates' => 'Transaction dates',
+ 'splits' => 'Splits',
+ 'split_title_withdrawal' => 'Split your new withdrawal',
+ 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
+ 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
+ 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
+ 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ '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_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.',
+ 'store_splitted_deposit' => 'Store splitted deposit',
+ 'split_title_transfer' => 'Split your new transfer',
+ 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
+ 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
+ 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
+ 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_transfer' => 'Store splitted transfer',
+ 'add_another_split' => 'Add another split',
+ 'split-transactions' => 'Split transactions',
+ 'split-new-transaction' => 'Split a new transaction',
+ 'do_split' => 'Do a split',
+ 'split_this_withdrawal' => 'Split this withdrawal',
+ 'split_this_deposit' => 'Split this deposit',
+ 'split_this_transfer' => 'Split this transfer',
+ 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.',
+ 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.',
+ 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.',
// import
- 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.',
- 'import_data_index' => 'Index',
- 'import_file_type_csv' => 'CSV (comma separated values)',
- 'import_file_type_help' => 'Select the type of file you will upload',
- 'import_start' => 'Start the import',
- 'configure_import' => 'Further configure your import',
- 'import_finish_configuration' => 'Finish configuration',
- 'settings_for_import' => 'Settings',
- 'import_status' => 'Import status',
- 'import_status_text' => 'The import is currently running, or will start momentarily.',
- 'import_complete' => 'Import configuration complete!',
- 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.',
- 'import_download_config' => 'Download configuration',
- 'import_start_import' => 'Start import',
- 'import_intro_beta' => 'The import function of Firefly III is in beta. Many users of Firefly III have tried many different files. Although each individual compontent of this import routine works (really), the combination might break. If your file cannot be imported by Firefly, please read this wiki page so I can fix the problem you have run into.',
- 'import_data' => 'Import data',
- 'import_data_full' => 'Import data into Firefly III',
- 'import' => 'Import',
- 'import_intro_what_it_does' => 'This page allows you to import data into Firefly III. To do so, export data from your bank, or from another financial management system. Upload that file here. Firefly III will convert the data. You need to give it some directions. Please select a file and follow the instructions.',
- 'import_intro_import_conf_title' => 'Import "configuration"',
- 'import_intro_beta_warning' => 'Warning',
- 'import_intro_import_conf_text' => 'As you will discover over the next few pages, this import routine has a lot of settings. These settings are mainly dependent on the bank (or financial management software) your file comes from. There is a good chance somebody else already imported such a file and has shared their configuration file. Please visit the import configuration center to see if there already is a configuration available for your bank or system. If there is, you should download this configuration file and upload it here as well. It will save you a lot of time!',
- 'import_file_help' => 'Select your file',
- 'import_status_settings_complete' => 'The import is ready to start.',
- 'import_status_import_complete' => 'The import has completed.',
- '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_finished' => 'Import has finished',
- 'import_error_single' => 'An error has occured during the import.',
- 'import_error_multi' => 'Some errors occured during the import.',
- 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:',
- 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.',
- 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.',
- 'import_finished_all' => 'The import has finished. Please check out the results below.',
- 'import_with_key' => 'Import with key \':key\'',
+ 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.',
+ 'import_data_index' => 'Index',
+ 'import_file_type_csv' => 'CSV (comma separated values)',
+ 'import_file_type_help' => 'Select the type of file you will upload',
+ 'import_start' => 'Start the import',
+ 'configure_import' => 'Further configure your import',
+ 'import_finish_configuration' => 'Finish configuration',
+ 'settings_for_import' => 'Settings',
+ 'import_status' => 'Import status',
+ 'import_status_text' => 'The import is currently running, or will start momentarily.',
+ 'import_complete' => 'Import configuration complete!',
+ 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.',
+ 'import_download_config' => 'Download configuration',
+ 'import_start_import' => 'Start import',
+ 'import_intro_beta' => 'The import function of Firefly III is in beta. Many users of Firefly III have tried many different files. Although each individual compontent of this import routine works (really), the combination might break. If your file cannot be imported by Firefly, please read this wiki page so I can fix the problem you have run into.',
+ 'import_data' => 'Import data',
+ 'import_data_full' => 'Import data into Firefly III',
+ 'import' => 'Import',
+ 'import_intro_what_it_does' => 'This page allows you to import data into Firefly III. To do so, export data from your bank, or from another financial management system. Upload that file here. Firefly III will convert the data. You need to give it some directions. Please select a file and follow the instructions.',
+ 'import_intro_import_conf_title' => 'Import "configuration"',
+ 'import_intro_beta_warning' => 'Warning',
+ 'import_intro_import_conf_text' => 'As you will discover over the next few pages, this import routine has a lot of settings. These settings are mainly dependent on the bank (or financial management software) your file comes from. There is a good chance somebody else already imported such a file and has shared their configuration file. Please visit the import configuration center to see if there already is a configuration available for your bank or system. If there is, you should download this configuration file and upload it here as well. It will save you a lot of time!',
+ 'import_file_help' => 'Select your file',
+ 'import_status_settings_complete' => 'The import is ready to start.',
+ 'import_status_import_complete' => 'The import has completed.',
+ '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_finished' => 'Import has finished',
+ 'import_error_single' => 'An error has occured during the import.',
+ 'import_error_multi' => 'Some errors occured during the import.',
+ 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:',
+ 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.',
+ 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.',
+ 'import_finished_all' => 'The import has finished. Please check out the results below.',
+ 'import_with_key' => 'Import with key \':key\'',
'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.',
diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php
index 1892c99f76..ba44232dd4 100644
--- a/resources/lang/en_US/list.php
+++ b/resources/lang/en_US/list.php
@@ -25,6 +25,7 @@ return [
'matchedOn' => 'Matched on',
'matchesOn' => 'Matched on',
'account_type' => 'Account type',
+ 'created_at' => 'Created at',
'new_balance' => 'New balance',
'account' => 'Account',
'matchingAmount' => 'Amount',
@@ -71,4 +72,17 @@ return [
'blocked_code' => 'Block code',
'domain' => 'Domain',
'registration_attempts' => 'Registration attempts',
+ 'source_account' => 'Source account',
+ 'destination_account' => 'Destination account',
+
+ 'accounts_count' => 'Number of accounts',
+ 'journals_count' => 'Number of journals',
+ 'attachments_count' => 'Number of attachments',
+ 'bills_count' => 'Number of bills',
+ 'categories_count' => 'Number of categories',
+ 'export_jobs_count' => 'Number of export jobs',
+ 'import_jobs_count' => 'Number of import jobs',
+ 'budget_count' => 'Number of budgets',
+ 'rule_and_groups_count' => 'Number of rules and rule groups',
+ 'tags_count' => 'Number of tags',
];
diff --git a/resources/views/admin/users/index.twig b/resources/views/admin/users/index.twig
index 9d17bee41b..9b47a66dd8 100644
--- a/resources/views/admin/users/index.twig
+++ b/resources/views/admin/users/index.twig
@@ -35,7 +35,8 @@
#{{ user.id }} |
- {{ user.email }} |
+
+ {{ user.email }} |
{{ user.created_at.formatLocalized(monthAndDayFormat) }}
{{ user.created_at.format('H:i') }}
diff --git a/resources/views/admin/users/show.twig b/resources/views/admin/users/show.twig
new file mode 100644
index 0000000000..e008708de1
--- /dev/null
+++ b/resources/views/admin/users/show.twig
@@ -0,0 +1,151 @@
+{% extends "./layout/default.twig" %}
+
+{% block breadcrumbs %}
+ {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, user) }}
+{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+ {{ trans('list.id') }} |
+ #{{ user.id }} |
+
+
+ {{ trans('list.email') }} |
+ {{ user.email }}
+ |
+ |
+
+ {{ trans('list.created_at') }} |
+
+ {{ user.created_at.formatLocalized(monthAndDayFormat) }}
+ {{ user.created_at.format('H:i') }} |
+
+
+ {{ trans('list.registered_from') }} |
+ {{ registration }} ({{ registrationHost }}) |
+
+
+ {{ trans('list.confirmed_from') }} |
+ {{ confirmation }} ({{ confirmationHost }}) |
+
+
+ {{ trans('list.is_admin') }} |
+
+ {% if information.is_admin %}
+ Yes
+ {% else %}
+ No
+ {% endif %}
+ |
+
+
+ {{ trans('list.has_two_factor') }} |
+
+ {% if information.has_2fa %}
+ Yes
+ {% else %}
+ No
+ {% endif %}
+ |
+
+
+ {{ trans('list.is_activated') }} |
+
+ {% if information.is_activated %}
+ Yes
+ {% else %}
+ No
+ {% endif %}
+ |
+
+
+ {{ trans('list.is_blocked') }} |
+
+ {% if information.blocked %}
+ Yes:
+
+ {% if information.blocked_code == "" %}
+ ~
+ {% else %}
+ {{ information.blocked_code }}
+ {% endif %}
+
+ {% else %}
+ No
+ {% endif %}
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ trans('list.accounts_count') }} |
+ {{ information.accounts }} |
+
+
+ {{ trans('list.journals_count') }} |
+ {{ information.journals }} ({{ information.transactions }} {{ trans('firefly.transactions') }}) |
+
+
+ {{ trans('list.attachments_count') }} |
+ {{ information.attachments }} ({{ trans('firefly.total_size') }}: {{ information.attachments_size|filesize }}) |
+
+
+ {{ trans('list.bills_count') }} |
+ {{ information.bills }} |
+
+
+ {{ trans('list.categories_count') }} |
+ {{ information.categories }} |
+
+
+ {{ trans('list.export_jobs_count') }} |
+ {{ information.export_jobs }}, {{ trans('firefly.successful_count', {count: information.export_jobs_success}) }}
+ |
+
+
+ {{ trans('list.import_jobs_count') }} |
+ {{ information.import_jobs }}, {{ trans('firefly.successful_count', {count: information.import_jobs_success}) }}
+ |
+
+
+ {{ trans('list.budget_count') }} |
+ {{ information.budgets }} {{ trans('firefly.budget_or_budgets') }},
+ {{ information.budgets_with_limits }} {{ trans('firefly.budgets_with_limits') }} |
+
+
+ {{ trans('list.rule_and_groups_count') }} |
+
+ {{ information.rules }} {{ 'rule_or_rules'|_ }} {{ 'in'|_ }} {{ information.rule_groups }}
+
+ {{ 'rulegroup_or_groups'|_ }}
+ |
+
+
+ {{ trans('list.tags_count') }} |
+ {{ information.tags }} tags |
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/resources/views/form/date.twig b/resources/views/form/date.twig
index f14289d1fe..3f08f20e3b 100644
--- a/resources/views/form/date.twig
+++ b/resources/views/form/date.twig
@@ -2,7 +2,12 @@
- {{ Form.input('date', name, value, options) }}
+
{% include 'form/help.twig' %}
{% include 'form/feedback.twig' %}
diff --git a/resources/views/index.twig b/resources/views/index.twig
index 88bd28b7e1..14a14c66e3 100644
--- a/resources/views/index.twig
+++ b/resources/views/index.twig
@@ -101,6 +101,18 @@
+
+ {% if showDepositsFrontpage %}
+
+ {% endif %}
diff --git a/resources/views/list/bills.twig b/resources/views/list/bills.twig
index 380878a14e..1f2a8a51f7 100644
--- a/resources/views/list/bills.twig
+++ b/resources/views/list/bills.twig
@@ -6,7 +6,7 @@
| {{ trans('list.matchesOn') }} |
{{ trans('list.matchingAmount') }} |
{{ trans('list.paid_current_period') }} |
- {{ trans('list.expectedMatch') }} |
+ {{ trans('list.next_expected_match') }} |
{{ trans('list.active') }} |
{{ trans('list.automatch') }} |
{{ trans('list.repeat_freq') }} |
diff --git a/resources/views/piggy-banks/create.twig b/resources/views/piggy-banks/create.twig
index 492ff18ae8..f6d055b46e 100644
--- a/resources/views/piggy-banks/create.twig
+++ b/resources/views/piggy-banks/create.twig
@@ -31,6 +31,7 @@
{{ ExpandedForm.date('targetdate') }}
+ {{ ExpandedForm.textarea('note') }}
diff --git a/resources/views/piggy-banks/edit.twig b/resources/views/piggy-banks/edit.twig
index 82bd5aeda7..c12e629504 100644
--- a/resources/views/piggy-banks/edit.twig
+++ b/resources/views/piggy-banks/edit.twig
@@ -33,6 +33,7 @@
{{ ExpandedForm.date('targetdate') }}
+ {{ ExpandedForm.textarea('note') }}
diff --git a/resources/views/piggy-banks/show.twig b/resources/views/piggy-banks/show.twig
index a46f0920c2..0b625d7680 100644
--- a/resources/views/piggy-banks/show.twig
+++ b/resources/views/piggy-banks/show.twig
@@ -35,7 +35,7 @@
+
+ {% if note %}
+
+
+
+
+ {{ note.text|nl2br }}
+
+
+
+ {% endif %}
+