diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 6df27b4876..cc97bc569c 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -4,20 +4,20 @@
## Feature requests
-If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/).
+I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/).
## Bugs
-If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary.
+First of all: thank you for reporting a bug instead of ditching the tool altogether. If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary. Bugs have a lot of priority!
## Installation problems
-Take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them!
+Please take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! If not, open an issue and I will help where I can.
## Pull requests
-I can only accept pull requests against the `develop` branch, never the `master` branch.
+When contributing to Firefly III, please first discuss the change you wish to make via issue, email, or any other method. I can only accept pull requests against the `develop` branch, never the `master` branch.
## Translations :us: :fr: :de:
-If you see a spelling error, grammatical error or a weird translation in your language, please join [our CrowdIn](https://crowdin.com/project/firefly-iii) project. There, you can submit your translations and fixes. The GitHub repository will download these automatically and they will be included in the next release.
\ No newline at end of file
+If you see a spelling error, grammatical error or a weird translation in your language, please join [our CrowdIn](https://crowdin.com/project/firefly-iii) project. There, you can submit your translations and fixes. The GitHub repository will download these automatically and they will be included in the next release.
diff --git a/.gitignore b/.gitignore
index b1e27bfc74..d4cfc0d4fb 100755
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ Homestead.json
Homestead.yaml
.env
public/google*.html
+report.html
diff --git a/.travis.yml b/.travis.yml
index 9e633c9bb1..930e42cb60 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,7 +16,7 @@ install:
- php artisan optimize
- php artisan env
- cp .env.testing .env
- - mv storage/database/databasecopy.sqlite storage/database/database.sqlite
+ - wget -q https://github.com/firefly-iii/test-data/raw/master/storage/database.sqlite -O storage/database/database.sqlite
- mkdir -p build/logs
script:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a64c684432..f68ff4250a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,44 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## [4.6.2] - 2017-07-08
+### Added
+- Links added to boxes, idea by @simonsmiley
+
+### Fixed
+- Various bugs in import routine
+
+## [4.6.1] - 2017-07-02
+### Fixed
+- Fixed several small issues all around.
+
+## [4.6.0] - 2017-06-28
+
+### Changed
+- Revamped import routine. Will be buggy.
+
+### Fixed
+- Issue #667, postgresql reported by @skibbipl.
+- Issue #680 by @Xeli
+- Fixed #660
+- Fixes #672, reported by @dzaikos
+- Translation error fixed by
+- Fix a bug where the balance routine forgot to account for accounts without a currency preference.
+- Various other bugfixes.
+
+## [4.5.0] - 2017-06-07
+
+### Added
+- Better support for multi-currency transactions and display of transactions, accounts and everything. This requires a database overhaul (moving the currency information to specific transactions) so be careful when upgrading.
+- Translations for Spanish and Slovenian.
+- New interface for budget page, ~~stolen from~~ inspired by YNAB.
+- Expanded Docker to work with postgresql as well, thanks to @kressh
+
+### Fixed
+- PostgreSQL support in database upgrade routine (#644, reported by @skibbipl)
+- Frontpage budget chart was off, fix by @nhaarman
+- Was not possible to remove opening balance.
+
## [4.4.3] - 2017-05-03
### Added
- Added support for Slovenian
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..9e6b350466
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at thegrumpydictator@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/Dockerfile b/Dockerfile
index 7d00a3724b..794634fdb8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,13 +11,14 @@ RUN apt-get update -y && \
libtidy-dev \
libxml2-dev \
libsqlite3-dev \
+ libpq-dev \
libbz2-dev \
gettext-base \
locales && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
-RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2
+RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2 pdo_pgsql
# Generate locales supported by firefly
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
diff --git a/README.md b/README.md
index 069c7cbee8..92467107fd 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,9 @@
[](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
-Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance.
+Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
-There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
-
-## Installation
+## Getting started
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html).
@@ -26,7 +24,7 @@ Personal financial management is pretty difficult, and everybody has their own a
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
-#### Some advantages of using Firefly
+### Some advantages of using Firefly
- Firefly can import any CSV file, so migrating from other systems is easy.
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", it’s just somebody else’s computer!
@@ -35,6 +33,25 @@ Firefly works on the principle that if you know where you're money is going, you
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
+### Contributing
+
+Please read [CONTRIBUTING.md](https://github.com/firefly-iii/firefly-iii/blob/master/.github/CONTRIBUTING.md) for details on contributing, and the process for submitting pull requests. Please check out the [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/master/CODE_OF_CONDUCT.md) as well.
+
+### Versioning
+
+We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository.
+
+### Authors
+
+* James Cole
+* Over time, [many people have contributed to Firefly III](https://github.com/firefly-iii/firefly-iii/graphs/contributors).
+
+### License
+
+This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
+
+### Other stuff
+
If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php
index 3c0d14fb18..adec1162db 100644
--- a/app/Console/Commands/CreateImport.php
+++ b/app/Console/Commands/CreateImport.php
@@ -14,10 +14,14 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use Artisan;
+use FireflyIII\Import\Logging\CommandHandler;
+use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Console\Command;
+use Illuminate\Support\MessageBag;
use Log;
+use Monolog\Formatter\LineFormatter;
/**
* Class CreateImport
@@ -73,30 +77,55 @@ class CreateImport extends Command
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));
+ $this->line(sprintf('Going to create a job to import file: %s', $file));
+ $this->line(sprintf('Using configuration file: %s', $configuration));
+ $this->line(sprintf('Import into user: #%d (%s)', $user->id, $user->email));
+ $this->line(sprintf('Type of import: %s', $type));
+
/** @var ImportJobRepositoryInterface $jobRepository */
$jobRepository = app(ImportJobRepositoryInterface::class);
$jobRepository->setUser($user);
$job = $jobRepository->create($type);
- $this->line(sprintf('Created job "%s"...', $job->key));
+ $this->line(sprintf('Created job "%s"', $job->key));
+
Artisan::call('firefly:encrypt-file', ['file' => $file, 'key' => $job->key]);
$this->line('Stored import data...');
+
$job->configuration = $configurationData;
- $job->status = 'settings_complete';
+ $job->status = 'configured';
$job->save();
$this->line('Stored configuration...');
+
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!');
+
+ // normally would refer to other firefly:start-import but that doesn't seem to work all to well...
+ $monolog = Log::getMonolog();
+ $handler = new CommandHandler($this);
+ $formatter = new LineFormatter(null, null, false, true);
+ $handler->setFormatter($formatter);
+ $monolog->pushHandler($handler);
+
+
+ // start the actual routine:
+ /** @var ImportRoutine $routine */
+ $routine = app(ImportRoutine::class);
+ $routine->setJob($job);
+ $routine->run();
+
+ // give feedback.
+ /** @var MessageBag $error */
+ foreach ($routine->errors as $index => $error) {
+ $this->error(sprintf('Error importing line #%d: %s', $index, $error));
+ }
+ $this->line(
+ sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->journals->count(), $routine->lines)
+ );
}
return;
diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php
index d2ff9b8997..f37203cc41 100644
--- a/app/Console/Commands/Import.php
+++ b/app/Console/Commands/Import.php
@@ -13,12 +13,11 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands;
-use FireflyIII\Import\ImportProcedure;
use FireflyIII\Import\Logging\CommandHandler;
+use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Models\ImportJob;
-use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
-use Illuminate\Support\Collection;
+use Illuminate\Support\MessageBag;
use Log;
/**
@@ -75,15 +74,18 @@ class Import extends Command
$monolog = Log::getMonolog();
$handler = new CommandHandler($this);
$monolog->pushHandler($handler);
- $importProcedure = new ImportProcedure;
- $result = $importProcedure->runImport($job);
- // display result to user:
- $this->presentResults($result);
- $this->line('The import has completed.');
+ /** @var ImportRoutine $routine */
+ $routine = app(ImportRoutine::class);
+ $routine->setJob($job);
+ $routine->run();
- // get any errors from the importer:
- $this->presentErrors($job);
+ /** @var MessageBag $error */
+ foreach ($routine->errors as $index => $error) {
+ $this->error(sprintf('Error importing line #%d: %s', $index, $error));
+ }
+
+ $this->line(sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->journals->count(), $routine->lines));
return;
}
@@ -96,12 +98,14 @@ class Import extends Command
private function isValid(ImportJob $job): bool
{
if (is_null($job)) {
+ Log::error('This job does not seem to exist.');
$this->error('This job does not seem to exist.');
return false;
}
- if ($job->status != 'settings_complete') {
+ if ($job->status !== 'configured') {
+ Log::error(sprintf('This job is not ready to be imported (status is %s).', $job->status));
$this->error('This job is not ready to be imported.');
return false;
@@ -109,36 +113,4 @@ class Import extends Command
return true;
}
-
- /**
- * @param ImportJob $job
- */
- private function presentErrors(ImportJob $job)
- {
- $extendedStatus = $job->extended_status;
- if (isset($extendedStatus['errors']) && count($extendedStatus['errors']) > 0) {
- $this->line(sprintf('The following %d error(s) occured during the import:', count($extendedStatus['errors'])));
- foreach ($extendedStatus['errors'] as $error) {
- $this->error($error);
- }
- }
- }
-
- /**
- * @param Collection $result
- */
- private function presentResults(Collection $result)
- {
- /**
- * @var int $index
- * @var TransactionJournal $journal
- */
- foreach ($result as $index => $journal) {
- if (!is_null($journal->id)) {
- $this->line(sprintf('Line #%d has been imported as transaction #%d.', $index, $journal->id));
- continue;
- }
- $this->error(sprintf('Could not store line #%d', $index));
- }
- }
}
diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php
index d313c55ec6..42566a6cee 100644
--- a/app/Console/Commands/UpgradeDatabase.php
+++ b/app/Console/Commands/UpgradeDatabase.php
@@ -32,6 +32,7 @@ use Illuminate\Database\QueryException;
use Log;
use Preferences;
use Schema;
+use Steam;
/**
* Class UpgradeDatabase
@@ -72,9 +73,54 @@ class UpgradeDatabase extends Command
$this->repairPiggyBanks();
$this->updateAccountCurrencies();
$this->updateJournalCurrencies();
+ $this->currencyInfoToTransactions();
+ $this->verifyCurrencyInfo();
$this->info('Firefly III database is up to date.');
}
+ /**
+ * Moves the currency id info to the transaction instead of the journal.
+ */
+ private function currencyInfoToTransactions()
+ {
+ $count = 0;
+ $set = TransactionJournal::with('transactions')->get();
+ /** @var TransactionJournal $journal */
+ foreach ($set as $journal) {
+ /** @var Transaction $transaction */
+ foreach ($journal->transactions as $transaction) {
+ if (is_null($transaction->transaction_currency_id)) {
+ $transaction->transaction_currency_id = $journal->transaction_currency_id;
+ $transaction->save();
+ $count++;
+ }
+ }
+
+
+ // read and use the foreign amounts when present.
+ if ($journal->hasMeta('foreign_amount')) {
+ $amount = Steam::positive($journal->getMeta('foreign_amount'));
+
+ // update both transactions:
+ foreach ($journal->transactions as $transaction) {
+ $transaction->foreign_amount = $amount;
+ if (bccomp($transaction->amount, '0') === -1) {
+ // update with negative amount:
+ $transaction->foreign_amount = bcmul($amount, '-1');
+ }
+ // set foreign currency id:
+ $transaction->foreign_currency_id = intval($journal->getMeta('foreign_currency_id'));
+ $transaction->save();
+ }
+ $journal->deleteMeta('foreign_amount');
+ $journal->deleteMeta('foreign_currency_id');
+ }
+
+ }
+
+ $this->line(sprintf('Updated currency information for %d transactions', $count));
+ }
+
/**
* Migrate budget repetitions to new format.
*/
@@ -269,9 +315,11 @@ class UpgradeDatabase extends Command
$repository = app(CurrencyRepositoryInterface::class);
$notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.';
$transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.';
+ $driver = DB::connection()->getDriverName();
+ $pgsql = ['pgsql', 'postgresql'];
foreach ($types as $type => $operator) {
- $set = TransactionJournal
+ $query = TransactionJournal
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin(
'transactions', function (JoinClause $join) use ($operator) {
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0');
@@ -280,9 +328,15 @@ class UpgradeDatabase extends Command
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
->where('transaction_types.type', $type)
- ->where('account_meta.name', 'currency_id')
- ->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'))
- ->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']);
+ ->where('account_meta.name', 'currency_id');
+ if (in_array($driver, $pgsql)) {
+ $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)'));
+ }
+ if (!in_array($driver, $pgsql)) {
+ $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'));
+ }
+
+ $set = $query->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']);
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
$expectedCurrency = $repository->find(intval($journal->expected_currency_id));
@@ -334,4 +388,25 @@ class UpgradeDatabase extends Command
}
}
}
+
+ /**
+ *
+ */
+ private function verifyCurrencyInfo()
+ {
+ $count = 0;
+ $transactions = Transaction::get();
+ /** @var Transaction $transaction */
+ foreach ($transactions as $transaction) {
+ $currencyId = intval($transaction->transaction_currency_id);
+ $foreignId = intval($transaction->foreign_currency_id);
+ if ($currencyId === $foreignId) {
+ $transaction->foreign_currency_id = null;
+ $transaction->foreign_amount = null;
+ $transaction->save();
+ $count++;
+ }
+ }
+ $this->line(sprintf('Updated currency information for %d transactions', $count));
+ }
}
diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php
index f98e84f86a..363e5089f7 100644
--- a/app/Console/Commands/UpgradeFireflyInstructions.php
+++ b/app/Console/Commands/UpgradeFireflyInstructions.php
@@ -50,10 +50,10 @@ class UpgradeFireflyInstructions extends Command
public function handle()
{
- if ($this->argument('task') == 'update') {
+ if ($this->argument('task') === 'update') {
$this->updateInstructions();
}
- if ($this->argument('task') == 'install') {
+ if ($this->argument('task') === 'install') {
$this->installInstructions();
}
}
diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php
index 52cf80c265..6e128e7f53 100644
--- a/app/Console/Commands/VerifyDatabase.php
+++ b/app/Console/Commands/VerifyDatabase.php
@@ -259,7 +259,7 @@ class VerifyDatabase extends Command
{
$plural = str_plural($name);
$class = sprintf('FireflyIII\Models\%s', ucfirst($name));
- $field = $name == 'tag' ? 'tag' : 'name';
+ $field = $name === 'tag' ? 'tag' : 'name';
$set = $class::leftJoin($name . '_transaction_journal', $plural . '.id', '=', $name . '_transaction_journal.' . $name . '_id')
->leftJoin('users', $plural . '.user_id', '=', 'users.id')
->distinct()
diff --git a/app/Events/StoredTransactionJournal.php b/app/Events/StoredTransactionJournal.php
index d1d6805cd2..e67cfbe2cc 100644
--- a/app/Events/StoredTransactionJournal.php
+++ b/app/Events/StoredTransactionJournal.php
@@ -26,9 +26,9 @@ class StoredTransactionJournal extends Event
use SerializesModels;
- /** @var TransactionJournal */
+ /** @var TransactionJournal */
public $journal;
- /** @var int */
+ /** @var int */
public $piggyBankId;
/**
diff --git a/app/Events/UpdatedTransactionJournal.php b/app/Events/UpdatedTransactionJournal.php
index ce21810bc2..2390a13d75 100644
--- a/app/Events/UpdatedTransactionJournal.php
+++ b/app/Events/UpdatedTransactionJournal.php
@@ -26,7 +26,7 @@ class UpdatedTransactionJournal extends Event
use SerializesModels;
- /** @var TransactionJournal */
+ /** @var TransactionJournal */
public $journal;
/**
diff --git a/app/Export/Collector/JournalExportCollector.php b/app/Export/Collector/JournalExportCollector.php
index 175b405f79..b0e04e115e 100644
--- a/app/Export/Collector/JournalExportCollector.php
+++ b/app/Export/Collector/JournalExportCollector.php
@@ -303,7 +303,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
- ->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id')
+ ->leftJoin('transaction_currencies', 'transactions.transaction_currency_id', '=', 'transaction_currencies.id')
->whereIn('transactions.account_id', $accountIds)
->where('transaction_journals.user_id', $this->job->user_id)
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
@@ -338,7 +338,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac
'transaction_journals.encrypted as journal_encrypted',
'transaction_journals.transaction_type_id',
'transaction_types.type as transaction_type',
- 'transaction_journals.transaction_currency_id',
+ 'transactions.transaction_currency_id',
'transaction_currencies.code AS transaction_currency_code',
]
diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php
index f4061f703b..13be79458e 100644
--- a/app/Export/Entry/Entry.php
+++ b/app/Export/Entry/Entry.php
@@ -88,7 +88,7 @@ final class Entry
$entry->budget_name = $object->budget_name ?? '';
// update description when transaction description is different:
- if (!is_null($object->description) && $object->description != $entry->description) {
+ if (!is_null($object->description) && $object->description !== $entry->description) {
$entry->description = $entry->description . ' (' . $object->description . ')';
}
diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php
index b63f9e9c01..b8086880f4 100644
--- a/app/Generator/Chart/Basic/ChartJsGenerator.php
+++ b/app/Generator/Chart/Basic/ChartJsGenerator.php
@@ -110,9 +110,16 @@ class ChartJsGenerator implements GeneratorInterface
];
// sort by value, keep keys.
+ // different sort when values are positive and when they're negative.
asort($data);
+ $next = next($data);
+ if (!is_bool($next) && bccomp($next, '0') === 1) {
+ // next is positive, sort other way around.
+ arsort($data);
+ }
+ unset($next);
- $index = 0;
+ $index = 0;
foreach ($data as $key => $value) {
// make larger than 0
diff --git a/app/Generator/Chart/Basic/GeneratorInterface.php b/app/Generator/Chart/Basic/GeneratorInterface.php
index 92b1b29d93..31deeb0a30 100644
--- a/app/Generator/Chart/Basic/GeneratorInterface.php
+++ b/app/Generator/Chart/Basic/GeneratorInterface.php
@@ -22,10 +22,15 @@ interface GeneratorInterface
{
/**
- * Will generate a (ChartJS) compatible array from the given input. Expects this format:
+ * Will generate a Chart JS compatible array from the given input. Expects this format
+ *
+ * Will take labels for all from first set.
*
* 0: [
* 'label' => 'label of set',
+ * 'type' => bar or line, optional
+ * 'yAxisID' => ID of yAxis, optional, will not be included when unused.
+ * 'fill' => if to fill a line? optional, will not be included when unused.
* 'entries' =>
* [
* 'label-of-entry' => 'value'
@@ -33,12 +38,16 @@ interface GeneratorInterface
* ]
* 1: [
* 'label' => 'label of another set',
+ * 'type' => bar or line, optional
+ * 'yAxisID' => ID of yAxis, optional, will not be included when unused.
+ * 'fill' => if to fill a line? optional, will not be included when unused.
* 'entries' =>
* [
* 'label-of-entry' => 'value'
* ]
* ]
*
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five.
*
* @param array $data
*
diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php
index 0b12e60fe0..43e23ad1a4 100644
--- a/app/Generator/Report/Audit/MonthReportGenerator.php
+++ b/app/Generator/Report/Audit/MonthReportGenerator.php
@@ -19,6 +19,7 @@ use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
+use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Support\Collection;
use Steam;
@@ -147,6 +148,8 @@ class MonthReportGenerator implements ReportGeneratorInterface
*/
private function getAuditReport(Account $account, Carbon $date): array
{
+ /** @var CurrencyRepositoryInterface $currencyRepos */
+ $currencyRepos = app(CurrencyRepositoryInterface::class);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
@@ -155,15 +158,21 @@ class MonthReportGenerator implements ReportGeneratorInterface
$journals = $journals->reverse();
$dayBeforeBalance = Steam::balance($account, $date);
$startBalance = $dayBeforeBalance;
-
+ $currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
/** @var Transaction $journal */
foreach ($journals as $transaction) {
$transaction->before = $startBalance;
$transactionAmount = $transaction->transaction_amount;
- $newBalance = bcadd($startBalance, $transactionAmount);
- $transaction->after = $newBalance;
- $startBalance = $newBalance;
+
+ if ($currency->id === $transaction->foreign_currency_id) {
+ $transactionAmount = $transaction->transaction_foreign_amount;
+ }
+
+ $newBalance = bcadd($startBalance, $transactionAmount);
+ $transaction->after = $newBalance;
+ $startBalance = $newBalance;
+ $transaction->currency = $currency;
}
/*
diff --git a/app/Generator/Report/Support.php b/app/Generator/Report/Support.php
index 563e72f12b..674b31f7d6 100644
--- a/app/Generator/Report/Support.php
+++ b/app/Generator/Report/Support.php
@@ -15,7 +15,6 @@ namespace FireflyIII\Generator\Report;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
-use Log;
/**
diff --git a/app/Generator/Report/Tag/MonthReportGenerator.php b/app/Generator/Report/Tag/MonthReportGenerator.php
index f8a253defd..3f247f4467 100644
--- a/app/Generator/Report/Tag/MonthReportGenerator.php
+++ b/app/Generator/Report/Tag/MonthReportGenerator.php
@@ -14,6 +14,7 @@ namespace FireflyIII\Generator\Report\Tag;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
+use FireflyIII\Generator\Report\Support;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
@@ -30,7 +31,7 @@ use Log;
*
* @package FireflyIII\Generator\Report\Tag
*/
-class MonthReportGenerator implements ReportGeneratorInterface
+class MonthReportGenerator extends Support implements ReportGeneratorInterface
{
/** @var Collection */
diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php
index 7a880fc699..6d266d0dda 100644
--- a/app/Handlers/Events/StoredJournalEventHandler.php
+++ b/app/Handlers/Events/StoredJournalEventHandler.php
@@ -42,6 +42,10 @@ class StoredJournalEventHandler
/**
* StoredJournalEventHandler constructor.
+ *
+ * @param PRI $repository
+ * @param JRI $journalRepository
+ * @param RGRI $ruleGroupRepository
*/
public function __construct(PRI $repository, JRI $journalRepository, RGRI $ruleGroupRepository)
{
diff --git a/app/Handlers/Events/UpdatedJournalEventHandler.php b/app/Handlers/Events/UpdatedJournalEventHandler.php
index 1cdadd6da8..17303c9134 100644
--- a/app/Handlers/Events/UpdatedJournalEventHandler.php
+++ b/app/Handlers/Events/UpdatedJournalEventHandler.php
@@ -23,7 +23,7 @@ use FireflyIII\Support\Events\BillScanner;
/**
* @codeCoverageIgnore
- *
+ *
* Class UpdatedJournalEventHandler
*
* @package FireflyIII\Handlers\Events
@@ -35,6 +35,8 @@ class UpdatedJournalEventHandler
/**
* StoredJournalEventHandler constructor.
+ *
+ * @param RuleGroupRepositoryInterface $ruleGroupRepository
*/
public function __construct(RuleGroupRepositoryInterface $ruleGroupRepository)
{
@@ -52,7 +54,7 @@ class UpdatedJournalEventHandler
{
// get all the user's rule groups, with the rules, order by 'order'.
$journal = $updatedJournalEvent->journal;
- $groups = $this->repository->getActiveGroups($journal->user);
+ $groups = $this->repository->getActiveGroups($journal->user);
/** @var RuleGroup $group */
foreach ($groups as $group) {
diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php
index 4b02a2a455..8c7eb4a90c 100644
--- a/app/Handlers/Events/UserEventHandler.php
+++ b/app/Handlers/Events/UserEventHandler.php
@@ -74,6 +74,7 @@ class UserEventHandler
} catch (Swift_TransportException $e) {
Log::error($e->getMessage());
}
+
// @codeCoverageIgnoreEnd
return true;
@@ -96,16 +97,17 @@ class UserEventHandler
}
// get the email address
$email = $event->user->email;
- $address = route('index');
+ $uri = route('index');
$ipAddress = $event->ipAddress;
// send email.
try {
- Mail::to($email)->send(new RegisteredUserMail($address, $ipAddress));
+ Mail::to($email)->send(new RegisteredUserMail($uri, $ipAddress));
// @codeCoverageIgnoreStart
} catch (Swift_TransportException $e) {
Log::error($e->getMessage());
}
+
// @codeCoverageIgnoreEnd
return true;
diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php
index 3189055bb2..af83f729a7 100644
--- a/app/Helpers/Attachments/AttachmentHelper.php
+++ b/app/Helpers/Attachments/AttachmentHelper.php
@@ -18,9 +18,10 @@ use FireflyIII\Models\Attachment;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
+use Log;
use Storage;
use Symfony\Component\HttpFoundation\File\UploadedFile;
-use Log;
+
/**
* Class AttachmentHelper
*
@@ -157,11 +158,14 @@ class AttachmentHelper implements AttachmentHelperInterface
$attachment->size = $file->getSize();
$attachment->uploaded = 0;
$attachment->save();
+ Log::debug('Created attachment:', $attachment->toArray());
$fileObject = $file->openFile('r');
$fileObject->rewind();
$content = $fileObject->fread($file->getSize());
$encrypted = Crypt::encrypt($content);
+ Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize()));
+ Log::debug(sprintf('Encrypted content is %d', strlen($encrypted)));
// store it:
$this->uploadDisk->put($attachment->fileName(), $encrypted);
@@ -202,6 +206,7 @@ class AttachmentHelper implements AttachmentHelperInterface
/**
* @codeCoverageIgnore
+ *
* @param UploadedFile $file
*
* @return bool
diff --git a/app/Helpers/Collection/BalanceLine.php b/app/Helpers/Collection/BalanceLine.php
index 73b1a0f486..c3d892b214 100644
--- a/app/Helpers/Collection/BalanceLine.php
+++ b/app/Helpers/Collection/BalanceLine.php
@@ -147,13 +147,13 @@ class BalanceLine
if ($this->getBudget() instanceof BudgetModel && !is_null($this->getBudget()->id)) {
return $this->getBudget()->name;
}
- if ($this->getRole() == self::ROLE_DEFAULTROLE) {
+ if ($this->getRole() === self::ROLE_DEFAULTROLE) {
return strval(trans('firefly.no_budget'));
}
- if ($this->getRole() == self::ROLE_TAGROLE) {
+ if ($this->getRole() === self::ROLE_TAGROLE) {
return strval(trans('firefly.coveredWithTags'));
}
- if ($this->getRole() == self::ROLE_DIFFROLE) {
+ if ($this->getRole() === self::ROLE_DIFFROLE) {
return strval(trans('firefly.leftUnbalanced'));
}
diff --git a/app/Helpers/Collection/Bill.php b/app/Helpers/Collection/Bill.php
index e78e31631b..0df4a5fd5b 100644
--- a/app/Helpers/Collection/Bill.php
+++ b/app/Helpers/Collection/Bill.php
@@ -96,7 +96,7 @@ class Bill
{
$set = $this->bills->sortBy(
function (BillLine $bill) {
- $active = intval($bill->getBill()->active) == 0 ? 1 : 0;
+ $active = intval($bill->getBill()->active) === 0 ? 1 : 0;
$name = $bill->getBill()->name;
return $active . $name;
diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php
index 56d53d7446..43522ef149 100644
--- a/app/Helpers/Collector/JournalCollector.php
+++ b/app/Helpers/Collector/JournalCollector.php
@@ -60,17 +60,30 @@ class JournalCollector implements JournalCollectorInterface
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.encrypted',
- 'transaction_currencies.code as transaction_currency_code',
'transaction_types.type as transaction_type_type',
'transaction_journals.bill_id',
'bills.name as bill_name',
'bills.name_encrypted as bill_name_encrypted',
'transactions.id as id',
- 'transactions.amount as transaction_amount',
+
'transactions.description as transaction_description',
'transactions.account_id',
'transactions.identifier',
'transactions.transaction_journal_id',
+
+ 'transactions.amount as transaction_amount',
+
+ 'transactions.transaction_currency_id as transaction_currency_id',
+ 'transaction_currencies.code as transaction_currency_code',
+ 'transaction_currencies.symbol as transaction_currency_symbol',
+ 'transaction_currencies.decimal_places as transaction_currency_dp',
+
+ 'transactions.foreign_amount as transaction_foreign_amount',
+ 'transactions.foreign_currency_id as foreign_currency_id',
+ 'foreign_currencies.code as foreign_currency_code',
+ 'foreign_currencies.symbol as foreign_currency_symbol',
+ 'foreign_currencies.decimal_places as foreign_currency_dp',
+
'accounts.name as account_name',
'accounts.encrypted as account_encrypted',
'account_types.type as account_type',
@@ -484,17 +497,19 @@ class JournalCollector implements JournalCollectorInterface
Log::debug('journalCollector::startQuery');
/** @var EloquentBuilder $query */
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
+ ->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transactions.transaction_currency_id')
+ ->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', 'transactions.foreign_currency_id')
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC');
+ ->orderBy('transaction_journals.id', 'DESC')
+ ->orderBy('transaction_journals.description', 'DESC');
$this->query = $query;
diff --git a/app/Helpers/Filter/AmountFilter.php b/app/Helpers/Filter/AmountFilter.php
index eb4ab55662..7fdd9eb966 100644
--- a/app/Helpers/Filter/AmountFilter.php
+++ b/app/Helpers/Filter/AmountFilter.php
@@ -53,4 +53,4 @@ class AmountFilter implements FilterInterface
}
);
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Filter/EmptyFilter.php b/app/Helpers/Filter/EmptyFilter.php
index 23a4172e88..5fa9f64521 100644
--- a/app/Helpers/Filter/EmptyFilter.php
+++ b/app/Helpers/Filter/EmptyFilter.php
@@ -31,4 +31,4 @@ class EmptyFilter implements FilterInterface
{
return $set;
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Filter/FilterInterface.php b/app/Helpers/Filter/FilterInterface.php
index d2dcb9eda4..f189eb10ff 100644
--- a/app/Helpers/Filter/FilterInterface.php
+++ b/app/Helpers/Filter/FilterInterface.php
@@ -23,4 +23,4 @@ interface FilterInterface
*/
public function filter(Collection $set): Collection;
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Filter/InternalTransferFilter.php b/app/Helpers/Filter/InternalTransferFilter.php
index 7e8d71588e..7ab06618aa 100644
--- a/app/Helpers/Filter/InternalTransferFilter.php
+++ b/app/Helpers/Filter/InternalTransferFilter.php
@@ -26,7 +26,7 @@ use Log;
*/
class InternalTransferFilter implements FilterInterface
{
- /** @var array */
+ /** @var array */
private $accounts = [];
/**
@@ -70,4 +70,4 @@ class InternalTransferFilter implements FilterInterface
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Filter/NegativeAmountFilter.php b/app/Helpers/Filter/NegativeAmountFilter.php
index f2ef34bcc4..e1e494127e 100644
--- a/app/Helpers/Filter/NegativeAmountFilter.php
+++ b/app/Helpers/Filter/NegativeAmountFilter.php
@@ -44,4 +44,4 @@ class NegativeAmountFilter implements FilterInterface
}
);
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Filter/OpposingAccountFilter.php b/app/Helpers/Filter/OpposingAccountFilter.php
index 69a1567943..663f9a35ce 100644
--- a/app/Helpers/Filter/OpposingAccountFilter.php
+++ b/app/Helpers/Filter/OpposingAccountFilter.php
@@ -60,4 +60,4 @@ class OpposingAccountFilter implements FilterInterface
}
);
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Filter/PositiveAmountFilter.php b/app/Helpers/Filter/PositiveAmountFilter.php
index ddb71841da..e3d4065942 100644
--- a/app/Helpers/Filter/PositiveAmountFilter.php
+++ b/app/Helpers/Filter/PositiveAmountFilter.php
@@ -47,4 +47,4 @@ class PositiveAmountFilter implements FilterInterface
}
);
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Filter/TransferFilter.php b/app/Helpers/Filter/TransferFilter.php
index d33d5e41ed..1aa0b5bd43 100644
--- a/app/Helpers/Filter/TransferFilter.php
+++ b/app/Helpers/Filter/TransferFilter.php
@@ -55,4 +55,4 @@ class TransferFilter implements FilterInterface
return $new;
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php
index 9c62ea1ebc..dadef8fe41 100644
--- a/app/Helpers/Report/BalanceReportHelper.php
+++ b/app/Helpers/Report/BalanceReportHelper.php
@@ -244,7 +244,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface
foreach ($accounts as $account) {
$leftEntry = $tagsLeft->filter(
function (Tag $tag) use ($account) {
- return $tag->account_id == $account->id;
+ return $tag->account_id === $account->id;
}
);
$left = '0';
diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php
index 55c6ea64a7..7fecb90c89 100644
--- a/app/Helpers/Report/BudgetReportHelper.php
+++ b/app/Helpers/Report/BudgetReportHelper.php
@@ -56,7 +56,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface
/** @var Budget $budget */
foreach ($set as $budget) {
$budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
- if ($budgetLimits->count() == 0) { // no budget limit(s) for this budget
+ if ($budgetLimits->count() === 0) { // no budget limit(s) for this budget
$spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);// spent for budget in time range
if (bccomp($spent, '0') === -1) {
diff --git a/app/Helpers/Report/PopupReport.php b/app/Helpers/Report/PopupReport.php
index f4d0a07d17..7823eb95b8 100644
--- a/app/Helpers/Report/PopupReport.php
+++ b/app/Helpers/Report/PopupReport.php
@@ -187,7 +187,7 @@ class PopupReport implements PopupReportInterface
$journals = $journals->filter(
function (Transaction $transaction) use ($report) {
// get the destinations:
- $destinations = $transaction->destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray();
+ $destinations = $transaction->transactionJournal->destinationAccountList()->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $destinations));
@@ -196,4 +196,4 @@ class PopupReport implements PopupReportInterface
return $journals;
}
-}
\ No newline at end of file
+}
diff --git a/app/Helpers/Report/PopupReportInterface.php b/app/Helpers/Report/PopupReportInterface.php
index 79ca4be007..388c1a1d91 100644
--- a/app/Helpers/Report/PopupReportInterface.php
+++ b/app/Helpers/Report/PopupReportInterface.php
@@ -80,4 +80,4 @@ interface PopupReportInterface
* @return Collection
*/
public function byIncome(Account $account, array $attributes): Collection;
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
index 8e248a1628..24e04b7f7b 100644
--- a/app/Http/Controllers/AccountController.php
+++ b/app/Http/Controllers/AccountController.php
@@ -219,8 +219,8 @@ class AccountController extends Controller
$start->subDay();
$ids = $accounts->pluck('id')->toArray();
- $startBalances = Steam::balancesById($ids, $start);
- $endBalances = Steam::balancesById($ids, $end);
+ $startBalances = Steam::balancesByAccounts($accounts, $start);
+ $endBalances = Steam::balancesByAccounts($accounts, $end);
$activities = Steam::getLastActivities($ids);
$accounts->each(
@@ -293,8 +293,8 @@ class AccountController extends Controller
$periods = $this->getPeriodOverview($account);
}
- $count = 0;
- $loop = 0;
+ $count = 0;
+ $loop = 0;
// grab journals, but be prepared to jump a period back to get the right ones:
Log::info('Now at loop start.');
while ($count === 0 && $loop < 3) {
@@ -308,7 +308,7 @@ class AccountController extends Controller
$journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id . '/' . $moment);
$count = $journals->getCollection()->count();
- if ($count === 0) {
+ if ($count === 0 && $loop < 3) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
@@ -316,7 +316,7 @@ class AccountController extends Controller
}
}
- if ($moment != 'all' && $loop > 1) {
+ if ($moment !== 'all' && $loop > 1) {
$subTitle = trans(
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat)]
diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php
index cd8de38752..6827d58fe2 100644
--- a/app/Http/Controllers/AttachmentController.php
+++ b/app/Http/Controllers/AttachmentController.php
@@ -98,10 +98,13 @@ class AttachmentController extends Controller
*/
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
{
+
+
if ($repository->exists($attachment)) {
$content = $repository->getContent($attachment);
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
+
/** @var LaravelResponse $response */
$response = response($content, 200);
$response
@@ -149,7 +152,8 @@ class AttachmentController extends Controller
{
$image = 'images/page_green.png';
- if ($attachment->mime == 'application/pdf') {
+
+ if ($attachment->mime === 'application/pdf') {
$image = 'images/page_white_acrobat.png';
}
$file = public_path($image);
diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php
index 2418bde1b0..8a963cd57f 100644
--- a/app/Http/Controllers/BillController.php
+++ b/app/Http/Controllers/BillController.php
@@ -175,7 +175,7 @@ class BillController extends Controller
*/
public function rescan(Request $request, BillRepositoryInterface $repository, Bill $bill)
{
- if (intval($bill->active) == 0) {
+ if (intval($bill->active) === 0) {
$request->session()->flash('warning', strval(trans('firefly.cannot_scan_inactive_bill')));
return redirect(URL::previous());
@@ -206,7 +206,7 @@ class BillController extends Controller
/** @var Carbon $date */
$date = session('start');
$year = $date->year;
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$yearAverage = $repository->getYearAverage($bill, $date);
$overallAverage = $repository->getOverallAverage($bill);
diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php
index 05188a146e..b223c477dc 100644
--- a/app/Http/Controllers/BudgetController.php
+++ b/app/Http/Controllers/BudgetController.php
@@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers;
use Amount;
use Carbon\Carbon;
+use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\BudgetFormRequest;
@@ -80,7 +81,7 @@ class BudgetController extends Controller
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
- if ($amount == 0) {
+ if ($amount === 0) {
$budgetLimit = null;
}
Preferences::mark();
@@ -166,16 +167,38 @@ class BudgetController extends Controller
}
/**
+ * @param string|null $moment
+ *
* @return View
*/
- public function index()
+ public function index(string $moment = null)
{
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = session('start', new Carbon);
+ $end = session('end', new Carbon);
+
+ // make date if present:
+ if (!is_null($moment) || strlen(strval($moment)) !== 0) {
+ try {
+ $start = new Carbon($moment);
+ $end = Navigation::endOfPeriod($start, $range);
+ } catch (Exception $e) {
+ // start and end are already defined.
+
+ }
+ }
+ $next = clone $end;
+ $next->addDay();
+ $prev = clone $start;
+ $prev->subDay();
+ $prev = Navigation::startOfPeriod($prev, $range);
+
+
$this->repository->cleanupBudgets();
+
$budgets = $this->repository->getActiveBudgets();
$inactive = $this->repository->getInactiveBudgets();
- $start = session('start', new Carbon);
- $end = session('end', new Carbon);
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
@@ -184,9 +207,44 @@ class BudgetController extends Controller
$spent = array_sum(array_column($budgetInformation, 'spent'));
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
+ // select thing for last 12 periods:
+ $previousLoop = [];
+ $previousDate = clone $start;
+ $count = 0;
+ while ($count < 12) {
+ $previousDate->subDay();
+ $previousDate = Navigation::startOfPeriod($previousDate, $range);
+ $format = $previousDate->format('Y-m-d');
+ $previousLoop[$format] = Navigation::periodShow($previousDate, $range);
+ $count++;
+ }
+
+ // select thing for next 12 periods:
+ $nextLoop = [];
+ $nextDate = clone $end;
+ $nextDate->addDay();
+ $count = 0;
+
+ while ($count < 12) {
+ $format = $nextDate->format('Y-m-d');
+ $nextLoop[$format] = Navigation::periodShow($nextDate, $range);
+ $nextDate = Navigation::endOfPeriod($nextDate, $range);
+ $count++;
+ $nextDate->addDay();
+ }
+
+ // display info
+ $currentMonth = Navigation::periodShow($start, $range);
+ $nextText = Navigation::periodShow($next, $range);
+ $prevText = Navigation::periodShow($prev, $range);
+
return view(
'budgets.index',
- compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', 'spent', 'budgeted')
+ compact(
+ 'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText',
+ 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets',
+ 'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start'
+ )
);
}
@@ -235,7 +293,7 @@ class BudgetController extends Controller
);
}
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$count = 0;
@@ -244,7 +302,7 @@ class BudgetController extends Controller
Log::info('Now at no-budget loop start.');
while ($count === 0 && $loop < 3) {
$loop++;
- Log::info('Count is zero, search for journals.');
+ Log::info(sprintf('Count is zero, search for journals between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
@@ -252,7 +310,7 @@ class BudgetController extends Controller
$journals = $collector->getPaginatedJournals();
$journals->setPath('/budgets/list/no-budget');
$count = $journals->getCollection()->count();
- if ($count === 0) {
+ if ($count === 0 && $loop < 3) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
@@ -260,7 +318,7 @@ class BudgetController extends Controller
}
}
- if ($moment != 'all' && $loop > 1) {
+ if ($moment !== 'all' && $loop > 1) {
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -299,7 +357,7 @@ class BudgetController extends Controller
/** @var Carbon $start */
$start = session('first', Carbon::create()->startOfYear());
$end = new Carbon;
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$limits = $this->getLimits($budget, $start, $end);
$repetition = null;
@@ -326,11 +384,11 @@ class BudgetController extends Controller
*/
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
{
- if ($budgetLimit->budget->id != $budget->id) {
+ if ($budgetLimit->budget->id !== $budget->id) {
throw new FireflyException('This budget limit is not part of this budget.');
}
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitle = trans(
'firefly.budget_in_period', [
diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php
index a760a0733d..4cf4389a2a 100644
--- a/app/Http/Controllers/CategoryController.php
+++ b/app/Http/Controllers/CategoryController.php
@@ -199,7 +199,7 @@ class CategoryController extends Controller
);
}
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$count = 0;
@@ -216,7 +216,7 @@ class CategoryController extends Controller
$journals = $collector->getPaginatedJournals();
$journals->setPath('/categories/list/no-category');
$count = $journals->getCollection()->count();
- if ($count === 0) {
+ if ($count === 0 && $loop < 3) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
@@ -224,7 +224,7 @@ class CategoryController extends Controller
}
}
- if ($moment != 'all' && $loop > 1) {
+ if ($moment !== 'all' && $loop > 1) {
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -247,7 +247,7 @@ class CategoryController extends Controller
// default values:
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$count = 0;
$loop = 0;
@@ -300,7 +300,7 @@ class CategoryController extends Controller
$journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id);
$count = $journals->getCollection()->count();
- if ($count === 0) {
+ if ($count === 0 && $loop < 3) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
@@ -308,7 +308,7 @@ class CategoryController extends Controller
}
}
- if ($moment != 'all' && $loop > 1) {
+ if ($moment !== 'all' && $loop > 1) {
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
@@ -463,7 +463,7 @@ class CategoryController extends Controller
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$first = $repository->firstUseDate($category);
- if ($first->year == 1900) {
+ if ($first->year === 1900) {
$first = new Carbon;
}
$range = Preferences::get('viewRange', '1M')->data;
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index 38bd1f3345..61194faff9 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -14,7 +14,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
-use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
@@ -115,9 +114,8 @@ class AccountController extends Controller
$start->subDay();
$accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
- $ids = $accounts->pluck('id')->toArray();
- $startBalances = Steam::balancesById($ids, $start);
- $endBalances = Steam::balancesById($ids, $end);
+ $startBalances = Steam::balancesByAccounts($accounts, $start);
+ $endBalances = Steam::balancesByAccounts($accounts, $end);
$chartData = [];
foreach ($accounts as $account) {
@@ -129,6 +127,7 @@ class AccountController extends Controller
$chartData[$account->name] = $diff;
}
}
+
arsort($chartData);
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data);
@@ -336,7 +335,7 @@ class AccountController extends Controller
/**
* @param Account $account
- * @param Carbon $start
+ * @param Carbon $start
*
* @return \Illuminate\Http\JsonResponse
* @throws FireflyException
@@ -411,9 +410,8 @@ class AccountController extends Controller
$accounts = $repository->getAccountsByType([AccountType::REVENUE]);
$start->subDay();
- $ids = $accounts->pluck('id')->toArray();
- $startBalances = Steam::balancesById($ids, $start);
- $endBalances = Steam::balancesById($ids, $end);
+ $startBalances = Steam::balancesByAccounts($accounts, $start);
+ $endBalances = Steam::balancesByAccounts($accounts, $end);
foreach ($accounts as $account) {
$id = $account->id;
@@ -427,7 +425,7 @@ class AccountController extends Controller
}
arsort($chartData);
- $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
+ $data = $this->generator->singleSet(strval(trans('firefly.earned')), $chartData);
$cache->store($data);
return Response::json($data);
diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php
index 189c1e2dea..4b74cfbf38 100644
--- a/app/Http/Controllers/Chart/BudgetController.php
+++ b/app/Http/Controllers/Chart/BudgetController.php
@@ -31,6 +31,7 @@ use Illuminate\Support\Collection;
use Navigation;
use Preferences;
use Response;
+use Steam;
/**
* Class BudgetController
@@ -123,7 +124,7 @@ class BudgetController extends Controller
*/
public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit)
{
- if ($budgetLimit->budget->id != $budget->id) {
+ if ($budgetLimit->budget->id !== $budget->id) {
throw new FireflyException('This budget limit is not part of this budget.');
}
@@ -320,12 +321,12 @@ class BudgetController extends Controller
['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',],
];
-
/** @var Budget $budget */
foreach ($budgets as $budget) {
// get relevant repetitions:
$limits = $this->repository->getBudgetLimits($budget, $start, $end);
$expenses = $this->getExpensesForBudget($limits, $budget, $start, $end);
+
foreach ($expenses as $name => $row) {
$chartData[0]['entries'][$name] = $row['spent'];
$chartData[1]['entries'][$name] = $row['left'];
@@ -529,9 +530,7 @@ class BudgetController extends Controller
$rows = $this->spentInPeriodMulti($budget, $limits);
foreach ($rows as $name => $row) {
if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) {
- $return[$name]['spent'] = bcmul($row['spent'], '-1');
- $return[$name]['left'] = $row['left'];
- $return[$name]['overspent'] = bcmul($row['overspent'], '-1');
+ $return[$name] = $row;
}
}
unset($rows, $row);
@@ -563,6 +562,7 @@ class BudgetController extends Controller
/** @var BudgetLimit $budgetLimit */
foreach ($limits as $budgetLimit) {
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
+ $expenses = Steam::positive($expenses);
if ($limits->count() > 1) {
$name = $budget->name . ' ' . trans(
@@ -578,10 +578,14 @@ class BudgetController extends Controller
* left: amount of budget limit min spent, or 0 when < 0.
* spent: spent, or amount of budget limit when > amount
*/
- $amount = $budgetLimit->amount;
- $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses);
- $spent = bccomp($expenses, $amount) === 1 ? $expenses : bcmul($amount, '-1');
- $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';
+ $amount = $budgetLimit->amount;
+ $leftInLimit = bcsub($amount, $expenses);
+ $hasOverspent = bccomp($leftInLimit, '0') === -1;
+
+ $left = $hasOverspent ? '0' : bcsub($amount, $expenses);
+ $spent = $hasOverspent ? $amount : $expenses;
+ $overspent = $hasOverspent ? Steam::positive($leftInLimit) : '0';
+
$return[$name] = [
'left' => $left,
'overspent' => $overspent,
diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php
index d330a9b363..b6ac5e0973 100644
--- a/app/Http/Controllers/Chart/CategoryController.php
+++ b/app/Http/Controllers/Chart/CategoryController.php
@@ -67,7 +67,7 @@ class CategoryController extends Controller
$start = $repository->firstUseDate($category);
- if ($start->year == 1900) {
+ if ($start->year === 1900) {
$start = new Carbon;
}
@@ -277,10 +277,10 @@ class CategoryController extends Controller
*/
public function specificPeriod(CategoryRepositoryInterface $repository, Category $category, Carbon $date)
{
- $range = Preferences::get('viewRange', '1M')->data;
- $start = Navigation::startOfPeriod($date, $range);
- $end = Navigation::endOfPeriod($date, $range);
- $data = $this->makePeriodChart($repository, $category, $start, $end);
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = Navigation::startOfPeriod($date, $range);
+ $end = Navigation::endOfPeriod($date, $range);
+ $data = $this->makePeriodChart($repository, $category, $start, $end);
return Response::json($data);
}
@@ -336,9 +336,9 @@ class CategoryController extends Controller
$sum = bcadd($spent, $earned);
$label = trim(Navigation::periodShow($start, '1D'));
- $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'),12);
- $chartData[1]['entries'][$label] = round($earned,12);
- $chartData[2]['entries'][$label] = round($sum,12);
+ $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
+ $chartData[1]['entries'][$label] = round($earned, 12);
+ $chartData[2]['entries'][$label] = round($sum, 12);
$start->addDay();
diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php
index 572a8a3432..a79073c863 100644
--- a/app/Http/Controllers/Chart/CategoryReportController.php
+++ b/app/Http/Controllers/Chart/CategoryReportController.php
@@ -16,7 +16,6 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
-use FireflyIII\Generator\Report\Category\MonthReportGenerator;
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
@@ -248,7 +247,7 @@ class CategoryReportController extends Controller
// remove all empty entries to prevent cluttering:
$newSet = [];
foreach ($chartData as $key => $entry) {
- if (!array_sum($entry['entries']) == 0) {
+ if (!array_sum($entry['entries']) === 0) {
$newSet[$key] = $chartData[$key];
}
}
diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php
index 1452192fe3..7df9b25e58 100644
--- a/app/Http/Controllers/Chart/ReportController.php
+++ b/app/Http/Controllers/Chart/ReportController.php
@@ -67,11 +67,10 @@ class ReportController extends Controller
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
- $ids = $accounts->pluck('id')->toArray();
$current = clone $start;
$chartData = [];
while ($current < $end) {
- $balances = Steam::balancesById($ids, $current);
+ $balances = Steam::balancesByAccounts($accounts, $current);
$sum = $this->arraySum($balances);
$label = $current->formatLocalized(strval(trans('config.month_and_day')));
$chartData[$label] = $sum;
@@ -104,7 +103,7 @@ class ReportController extends Controller
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
- //return Response::json($cache->get()); // @codeCoverageIgnore
+ return Response::json($cache->get()); // @codeCoverageIgnore
}
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
@@ -250,7 +249,7 @@ class ReportController extends Controller
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
- // return $cache->get(); // @codeCoverageIgnore
+ return $cache->get(); // @codeCoverageIgnore
}
$currentStart = clone $start;
diff --git a/app/Http/Controllers/Chart/TagReportController.php b/app/Http/Controllers/Chart/TagReportController.php
index 2aca50bc77..d7e86825b6 100644
--- a/app/Http/Controllers/Chart/TagReportController.php
+++ b/app/Http/Controllers/Chart/TagReportController.php
@@ -231,7 +231,7 @@ class TagReportController extends Controller
// remove all empty entries to prevent cluttering:
$newSet = [];
foreach ($chartData as $key => $entry) {
- if (!array_sum($entry['entries']) == 0) {
+ if (!array_sum($entry['entries']) === 0) {
$newSet[$key] = $chartData[$key];
}
}
@@ -364,4 +364,4 @@ class TagReportController extends Controller
return $grouped;
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 63cbe9c975..d46c485086 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -18,6 +18,7 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
+use FireflyIII\Support\Facades\Preferences;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
@@ -25,6 +26,7 @@ use Illuminate\Routing\Controller as BaseController;
use Session;
use URL;
use View;
+use Route;
/**
* Class Controller
@@ -63,6 +65,12 @@ class Controller extends BaseController
$this->monthAndDayFormat = (string)trans('config.month_and_day');
$this->dateTimeFormat = (string)trans('config.date_time');
+ // get shown-intro-preference:
+ $key = 'shown_demo_' . Route::currentRouteName();
+ $shownDemo = Preferences::get($key, false)->data;
+ View::share('shownDemo', $shownDemo);
+ View::share('current_route_name', Route::currentRouteName());
+
return $next($request);
}
);
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index 7e4cca9547..6ea0eb2119 100644
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -21,9 +21,11 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Http\Request;
+use Illuminate\Routing\Route;
use Illuminate\Support\Collection;
use Log;
use Preferences;
+use Route as RouteFacade;
use Session;
use View;
@@ -107,7 +109,7 @@ class HomeController extends Controller
$types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->count($types);
- if ($count == 0) {
+ if ($count === 0) {
return redirect(route('new-user.index'));
}
@@ -120,7 +122,6 @@ class HomeController extends Controller
$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);
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
@@ -137,10 +138,34 @@ class HomeController extends Controller
}
return view(
- 'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage', 'billCount')
+ 'index', compact('count', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage', 'billCount')
);
}
+ public function routes()
+ {
+ $set = RouteFacade::getRoutes();
+ $ignore = ['chart.','javascript.','json.','report-data.','popup.','debugbar.'];
+ /** @var Route $route */
+ foreach ($set as $route) {
+ $name = $route->getName();
+ if (!is_null($name) && in_array('GET', $route->methods()) && strlen($name) > 0) {
+ $found = false;
+ foreach ($ignore as $string) {
+ if (strpos($name, $string) !== false) {
+ $found = true;
+ }
+ }
+ if (!$found) {
+ echo 'touch '.$route->getName() . '.md;';
+ }
+
+ }
+ }
+
+ return ' ';
+ }
+
/**
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php
index 325698f720..c4b0bd1342 100644
--- a/app/Http/Controllers/ImportController.php
+++ b/app/Http/Controllers/ImportController.php
@@ -12,32 +12,29 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
-use Crypt;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\ImportUploadRequest;
-use FireflyIII\Import\ImportProcedureInterface;
-use FireflyIII\Import\Setup\SetupInterface;
+use FireflyIII\Import\Configurator\ConfiguratorInterface;
+use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
-use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse;
use Log;
use Response;
-use Session;
-use SplFileObject;
-use Storage;
-use Symfony\Component\HttpFoundation\File\UploadedFile;
use View;
/**
- * Class ImportController
+ * Class ImportController.
*
* @package FireflyIII\Http\Controllers
*/
class ImportController extends Controller
{
+ /** @var ImportJobRepositoryInterface */
+ public $repository;
+
/**
*
*/
@@ -48,7 +45,8 @@ class ImportController extends Controller
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-archive');
- View::share('title', trans('firefly.import_data_full'));
+ View::share('title', trans('firefly.import_index_title'));
+ $this->repository = app(ImportJobRepositoryInterface::class);
return $next($request);
}
@@ -56,28 +54,7 @@ class ImportController extends Controller
}
/**
- * This is the last step before the import starts.
- *
- * @param ImportJob $job
- *
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
- */
- public function complete(ImportJob $job)
- {
- Log::debug('Now in complete()', ['job' => $job->key]);
- if (!$this->jobInCorrectStep($job, 'complete')) {
- return $this->redirectToCorrectStep($job);
- }
- $subTitle = trans('firefly.import_complete');
- $subTitleIcon = 'fa-star';
-
- return view('import.complete', compact('job', 'subTitle', 'subTitleIcon'));
- }
-
- /**
- * This is step 3.
- * This is the first step in configuring the job. It can only be executed
- * when the job is set to "import_status_never_started".
+ * This is step 3. This repeats until the job is configured.
*
* @param ImportJob $job
*
@@ -86,37 +63,43 @@ class ImportController extends Controller
*/
public function configure(ImportJob $job)
{
- Log::debug('Now at start of configure()');
- if (!$this->jobInCorrectStep($job, 'configure')) {
- return $this->redirectToCorrectStep($job);
- }
+ // create configuration class:
+ $configurator = $this->makeConfigurator($job);
- // actual code
- $importer = $this->makeImporter($job);
- $importer->configure();
- $data = $importer->getConfigurationData();
- $subTitle = trans('firefly.configure_import');
+ // is the job already configured?
+ if ($configurator->isJobConfigured()) {
+ $this->repository->updateStatus($job, 'configured');
+
+ return redirect(route('import.status', [$job->key]));
+ }
+ $view = $configurator->getNextView();
+ $data = $configurator->getNextData();
+ $subTitle = trans('firefly.import_config_bread_crumb');
$subTitleIcon = 'fa-wrench';
- return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon'));
+ return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
}
/**
- * Generate a JSON file of the job's config and send it to the user.
+ * Generate a JSON file of the job's configuration and send it to the user.
*
* @param ImportJob $job
*
- * @return mixed
+ * @return LaravelResponse
*/
public function download(ImportJob $job)
{
Log::debug('Now in download()', ['job' => $job->key]);
- $config = $job->configuration;
+ $config = $job->configuration;
+
+ // TODO this is CSV import specific:
$config['column-roles-complete'] = false;
$config['column-mapping-complete'] = false;
+ $config['initial-config-complete'] = false;
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
- $result = json_encode($config, JSON_PRETTY_PRINT);
- $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
+
+ $result = json_encode($config, JSON_PRETTY_PRINT);
+ $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
/** @var LaravelResponse $response */
$response = response($result, 200);
@@ -134,26 +117,6 @@ class ImportController extends Controller
}
- /**
- * @param ImportJob $job
- *
- * @return View
- */
- public function finished(ImportJob $job)
- {
- if (!$this->jobInCorrectStep($job, 'finished')) {
- return $this->redirectToCorrectStep($job);
- }
-
- // if there is a tag (there might not be), we can link to it:
- $tagId = $job->extended_status['importTag'] ?? 0;
-
- $subTitle = trans('firefly.import_finished');
- $subTitleIcon = 'fa-star';
-
- return view('import.finished', compact('job', 'subTitle', 'subTitleIcon', 'tagId'));
- }
-
/**
* This is step 1. Upload a file.
*
@@ -161,8 +124,7 @@ class ImportController extends Controller
*/
public function index()
{
- Log::debug('Now at index');
- $subTitle = trans('firefly.import_data_index');
+ $subTitle = trans('firefly.import_index_sub_title');
$subTitleIcon = 'fa-home';
$importFileTypes = [];
$defaultImportType = config('firefly.default_import_format');
@@ -175,42 +137,75 @@ class ImportController extends Controller
}
/**
+ * This is step 2. It creates an Import Job. Stores the import.
+ *
+ * @param ImportUploadRequest $request
+ *
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ */
+ public function initialize(ImportUploadRequest $request)
+ {
+ Log::debug('Now in initialize()');
+
+ // create import job:
+ $type = $request->get('import_file_type');
+ $job = $this->repository->create($type);
+ Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
+
+ // process file:
+ $this->repository->processFile($job, $request->files->get('import_file'));
+
+ // process config, if present:
+ if ($request->files->has('configuration_file')) {
+ $this->repository->processConfiguration($job, $request->files->get('configuration_file'));
+ }
+
+ $this->repository->updateStatus($job, 'initialized');
+
+ return redirect(route('import.configure', [$job->key]));
+ }
+
+ /**
+ *
+ * Show status of import job in JSON.
+ *
* @param ImportJob $job
*
* @return \Illuminate\Http\JsonResponse
*/
public function json(ImportJob $job)
{
- $result = [
- 'showPercentage' => false,
- 'started' => false,
- 'finished' => false,
- 'running' => false,
- 'errors' => $job->extended_status['errors'],
- 'percentage' => 0,
- 'steps' => $job->extended_status['total_steps'],
- 'stepsDone' => $job->extended_status['steps_done'],
- 'statusText' => trans('firefly.import_status_' . $job->status),
- 'finishedText' => '',
+ $result = [
+ 'started' => false,
+ 'finished' => false,
+ 'running' => false,
+ 'errors' => array_values($job->extended_status['errors']),
+ 'percentage' => 0,
+ 'show_percentage' => false,
+ 'steps' => $job->extended_status['steps'],
+ 'done' => $job->extended_status['done'],
+ 'statusText' => trans('firefly.import_status_job_' . $job->status),
+ 'status' => $job->status,
+ 'finishedText' => '',
];
- $percentage = 0;
- if ($job->extended_status['total_steps'] !== 0) {
- $percentage = round(($job->extended_status['steps_done'] / $job->extended_status['total_steps']) * 100, 0);
+
+ if ($job->extended_status['steps'] !== 0) {
+ $result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
+ $result['show_percentage'] = true;
}
- if ($job->status === 'import_complete') {
- $tagId = $job->extended_status['importTag'];
+
+ if ($job->status === 'finished') {
+ $tagId = $job->extended_status['tag'];
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId);
$result['finished'] = true;
- $result['finishedText'] = trans('firefly.import_finished_link', ['link' => route('tags.show', [$tag->id]), 'tag' => $tag->tag]);
+ $result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
}
- if ($job->status === 'import_running') {
- $result['started'] = true;
- $result['running'] = true;
- $result['showPercentage'] = true;
- $result['percentage'] = $percentage;
+ if ($job->status === 'running') {
+ $result['started'] = true;
+ $result['running'] = true;
}
return Response::json($result);
@@ -219,286 +214,82 @@ class ImportController extends Controller
/**
* Step 4. Save the configuration.
*
- * @param Request $request
- * @param ImportJobRepositoryInterface $repository
- * @param ImportJob $job
- *
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
- */
- public function postConfigure(Request $request, ImportJobRepositoryInterface $repository, ImportJob $job)
- {
- Log::debug('Now in postConfigure()', ['job' => $job->key]);
- if (!$this->jobInCorrectStep($job, 'process')) {
- return $this->redirectToCorrectStep($job);
- }
- Log::debug('Continue postConfigure()', ['job' => $job->key]);
-
- // actual code
- $importer = $this->makeImporter($job);
- $data = $request->all();
- $files = $request->files;
- $importer->saveImportConfiguration($data, $files);
-
- // update job:
- $repository->updateStatus($job, 'import_configuration_saved');
-
- // return redirect to settings.
- // this could loop until the user is done.
- return redirect(route('import.settings', [$job->key]));
- }
-
- /**
- * This step 6. Depending on the importer, this will process the
- * settings given and store them.
- *
* @param Request $request
* @param ImportJob $job
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
- * @throws FireflyException
*/
- public function postSettings(Request $request, ImportJob $job)
+ public function postConfigure(Request $request, ImportJob $job)
{
- Log::debug('Now in postSettings()', ['job' => $job->key]);
- if (!$this->jobInCorrectStep($job, 'store-settings')) {
- return $this->redirectToCorrectStep($job);
- }
- $importer = $this->makeImporter($job);
- $importer->storeSettings($request);
-
- // return redirect to settings (for more settings perhaps)
- return redirect(route('import.settings', [$job->key]));
- }
-
- /**
- * Step 5. Depending on the importer, this will show the user settings to
- * fill in.
- *
- * @param ImportJobRepositoryInterface $repository
- * @param ImportJob $job
- *
- * @return View
- */
- public function settings(ImportJobRepositoryInterface $repository, ImportJob $job)
- {
- Log::debug('Now in settings()', ['job' => $job->key]);
- if (!$this->jobInCorrectStep($job, 'settings')) {
- return $this->redirectToCorrectStep($job);
- }
- Log::debug('Continue in settings()');
- $importer = $this->makeImporter($job);
- $subTitle = trans('firefly.settings_for_import');
- $subTitleIcon = 'fa-wrench';
-
- // now show settings screen to user.
- if ($importer->requireUserSettings()) {
- Log::debug('Job requires user config.');
- $data = $importer->getDataForSettings();
- $view = $importer->getViewForSettings();
-
- return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
- }
- Log::debug('Job does NOT require user config.');
-
- $repository->updateStatus($job, 'settings_complete');
-
- // if no more settings, save job and continue to process thing.
- return redirect(route('import.complete', [$job->key]));
-
- // ask the importer for the requested action.
- // for example pick columns or map data.
- // depends of course on the data in the job.
- }
-
- /**
- * @param ImportProcedureInterface $importProcedure
- * @param ImportJob $job
- */
- public function start(ImportProcedureInterface $importProcedure, ImportJob $job)
- {
- set_time_limit(0);
- if ($job->status == 'settings_complete') {
- $importProcedure->runImport($job);
- }
- }
-
- /**
- * This is the last step before the import starts.
- *
- * @param ImportJob $job
- *
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
- */
- public function status(ImportJob $job)
- { //
- Log::debug('Now in status()', ['job' => $job->key]);
- if (!$this->jobInCorrectStep($job, 'status')) {
- return $this->redirectToCorrectStep($job);
- }
- $subTitle = trans('firefly.import_status');
- $subTitleIcon = 'fa-star';
-
- return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
- }
-
-
- /**
- * This is step 2. It creates an Import Job. Stores the import.
- *
- * @param ImportUploadRequest $request
- * @param ImportJobRepositoryInterface $repository
- * @param UserRepositoryInterface $userRepository
- *
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
- */
- public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository, UserRepositoryInterface $userRepository)
- {
- Log::debug('Now in upload()');
- // create import job:
- $type = $request->get('import_file_type');
- $job = $repository->create($type);
- Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
-
- /** @var UploadedFile $upload */
- $upload = $request->files->get('import_file');
- $newName = $job->key . '.upload';
- $uploaded = new SplFileObject($upload->getRealPath());
- $content = $uploaded->fread($uploaded->getSize());
- $contentEncrypted = Crypt::encrypt($content);
- $disk = Storage::disk('upload');
-
- // user is demo user, replace upload with prepared file.
- if ($userRepository->hasRole(auth()->user(), 'demo')) {
- $stubsDisk = Storage::disk('stubs');
- $content = $stubsDisk->get('demo-import.csv');
- $contentEncrypted = Crypt::encrypt($content);
- $disk->put($newName, $contentEncrypted);
- Log::debug('Replaced upload with demo file.');
-
- // also set up prepared configuration.
- $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true);
- $repository->setConfiguration($job, $configuration);
- Log::debug('Set configuration for demo user', $configuration);
-
- // also flash info
- Session::flash('info', trans('demo.import-configure-security'));
- }
- if (!$userRepository->hasRole(auth()->user(), 'demo')) {
- // user is not demo, process original upload:
- $disk->put($newName, $contentEncrypted);
- Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]);
- }
-
- // store configuration file's content into the job's configuration thing. Otherwise, leave it empty.
- // demo user's configuration upload is ignored completely.
- if ($request->files->has('configuration_file') && !auth()->user()->hasRole('demo')) {
- /** @var UploadedFile $configFile */
- $configFile = $request->files->get('configuration_file');
- Log::debug(
- 'Uploaded configuration file',
- ['name' => $configFile->getClientOriginalName(), 'size' => $configFile->getSize(), 'mime' => $configFile->getClientMimeType()]
- );
-
- $configFileObject = new SplFileObject($configFile->getRealPath());
- $configRaw = $configFileObject->fread($configFileObject->getSize());
- $configuration = json_decode($configRaw, true);
-
- // @codeCoverageIgnoreStart
- if (!is_null($configuration) && is_array($configuration)) {
- Log::debug('Found configuration', $configuration);
- $repository->setConfiguration($job, $configuration);
- }
- // @codeCoverageIgnoreEnd
+ Log::debug('Now in postConfigure()', ['job' => $job->key]);
+ $configurator = $this->makeConfigurator($job);
+
+ // is the job already configured?
+ if ($configurator->isJobConfigured()) {
+ return redirect(route('import.status', [$job->key]));
}
+ $data = $request->all();
+ $configurator->configureJob($data);
+ // return to configure
return redirect(route('import.configure', [$job->key]));
}
/**
* @param ImportJob $job
- * @param string $method
*
- * @return bool
+ * @return \Illuminate\Http\JsonResponse
+ * @throws FireflyException
*/
- private function jobInCorrectStep(ImportJob $job, string $method): bool
+ public function start(ImportJob $job)
{
- Log::debug('Now in jobInCorrectStep()', ['job' => $job->key, 'method' => $method]);
- switch ($method) {
- case 'configure':
- case 'process':
- return $job->status === 'import_status_never_started';
- case 'settings':
- case 'store-settings':
- Log::debug(sprintf('Job %d with key %s has status %s', $job->id, $job->key, $job->status));
-
- return $job->status === 'import_configuration_saved';
- case 'finished':
- return $job->status === 'import_complete';
- case 'complete':
- return $job->status === 'settings_complete';
- case 'status':
- return ($job->status === 'settings_complete') || ($job->status === 'import_running');
+ /** @var ImportRoutine $routine */
+ $routine = app(ImportRoutine::class);
+ $routine->setJob($job);
+ $result = $routine->run();
+ if ($result) {
+ return Response::json(['run' => 'ok']);
}
- return false; // @codeCoverageIgnore
-
+ throw new FireflyException('Job did not complete succesfully.');
}
/**
* @param ImportJob $job
*
- * @return SetupInterface
- * @throws FireflyException
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
- private function makeImporter(ImportJob $job): SetupInterface
+ public function status(ImportJob $job)
{
- // create proper importer (depends on job)
- $type = strtolower($job->file_type);
-
- // validate type:
- $validTypes = array_keys(config('firefly.import_formats'));
-
-
- if (in_array($type, $validTypes)) {
- /** @var SetupInterface $importer */
- $importer = app('FireflyIII\Import\Setup\\' . ucfirst($type) . 'Setup');
- $importer->setJob($job);
-
- return $importer;
+ $statuses = ['configured', 'running', 'finished'];
+ if (!in_array($job->status, $statuses)) {
+ return redirect(route('import.configure', [$job->key]));
}
- throw new FireflyException(sprintf('"%s" is not a valid file type', $type)); // @codeCoverageIgnore
+ $subTitle = trans('firefly.import_status_sub_title');
+ $subTitleIcon = 'fa-star';
+ return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
}
/**
* @param ImportJob $job
*
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ * @return ConfiguratorInterface
* @throws FireflyException
*/
- private function redirectToCorrectStep(ImportJob $job)
+ private function makeConfigurator(ImportJob $job): ConfiguratorInterface
{
- Log::debug('Now in redirectToCorrectStep()', ['job' => $job->key]);
- switch ($job->status) {
- case 'import_status_never_started':
- Log::debug('Will redirect to configure()');
-
- return redirect(route('import.configure', [$job->key]));
- case 'import_configuration_saved':
- Log::debug('Will redirect to settings()');
-
- return redirect(route('import.settings', [$job->key]));
- case 'settings_complete':
- Log::debug('Will redirect to complete()');
-
- return redirect(route('import.complete', [$job->key]));
- case 'import_complete':
- Log::debug('Will redirect to finished()');
-
- return redirect(route('import.finished', [$job->key]));
+ $type = $job->file_type;
+ $key = sprintf('firefly.import_configurators.%s', $type);
+ $className = config($key);
+ if (is_null($className)) {
+ throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
}
+ /** @var ConfiguratorInterface $configurator */
+ $configurator = app($className);
+ $configurator->setJob($job);
- throw new FireflyException('Cannot redirect for job state ' . $job->status); // @codeCoverageIgnore
+ return $configurator;
}
}
diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php
index e5e7fb0651..1ad10422c2 100644
--- a/app/Http/Controllers/JavascriptController.php
+++ b/app/Http/Controllers/JavascriptController.php
@@ -34,7 +34,7 @@ class JavascriptController extends Controller
* @param AccountRepositoryInterface $repository
* @param CurrencyRepositoryInterface $currencyRepository
*
- * @return $this
+ * @return \Illuminate\Http\Response
*/
public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository)
{
@@ -63,7 +63,7 @@ class JavascriptController extends Controller
/**
* @param CurrencyRepositoryInterface $repository
*
- * @return $this
+ * @return \Illuminate\Http\Response
*/
public function currencies(CurrencyRepositoryInterface $repository)
{
@@ -71,8 +71,8 @@ class JavascriptController extends Controller
$data = ['currencies' => [],];
/** @var TransactionCurrency $currency */
foreach ($currencies as $currency) {
- $currencyId = $currency->id;
- $entry = ['name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol];
+ $currencyId = $currency->id;
+ $entry = ['name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol];
$data['currencies'][$currencyId] = $entry;
}
diff --git a/app/Http/Controllers/Json/ExchangeController.php b/app/Http/Controllers/Json/ExchangeController.php
index e96cae05e8..b1e3b5a60d 100644
--- a/app/Http/Controllers/Json/ExchangeController.php
+++ b/app/Http/Controllers/Json/ExchangeController.php
@@ -41,7 +41,6 @@ class ExchangeController extends Controller
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date);
- $amount = null;
if (is_null($rate->id)) {
Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
$preferred = env('EXCHANGE_RATE_SERVICE', config('firefly.preferred_exchange_service'));
@@ -63,4 +62,4 @@ class ExchangeController extends Controller
return Response::json($return);
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php
index c8e916e231..378c5ebac2 100644
--- a/app/Http/Controllers/JsonController.php
+++ b/app/Http/Controllers/JsonController.php
@@ -20,7 +20,6 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
-use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
@@ -116,10 +115,11 @@ class JsonController extends Controller
* Since both this method and the chart use the exact same data, we can suffice
* with calling the one method in the bill repository that will get this amount.
*/
- $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
- $amount = bcmul($amount, '-1');
+ $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
+ $amount = bcmul($amount, '-1');
+ $currency = Amount::getDefaultCurrency();
- $data = ['box' => 'bills-paid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
+ $data = ['box' => 'bills-paid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data);
}
@@ -131,19 +131,19 @@ class JsonController extends Controller
*/
public function boxBillsUnpaid(BillRepositoryInterface $repository)
{
- $start = session('start', Carbon::now()->startOfMonth());
- $end = session('end', Carbon::now()->endOfMonth());
- $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
- $data = ['box' => 'bills-unpaid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
+ $start = session('start', Carbon::now()->startOfMonth());
+ $end = session('end', Carbon::now()->endOfMonth());
+ $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
+ $currency = Amount::getDefaultCurrency();
+ $data = ['box' => 'bills-unpaid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data);
}
/**
- * @param AccountTaskerInterface $accountTasker
- * @param AccountRepositoryInterface $repository
- *
* @return \Illuminate\Http\JsonResponse
+ * @internal param AccountTaskerInterface $accountTasker
+ * @internal param AccountRepositoryInterface $repository
*
*/
public function boxIn()
@@ -167,18 +167,19 @@ class JsonController extends Controller
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
- $amount = strval($collector->getJournals()->sum('transaction_amount'));
- $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
+ $amount = strval($collector->getJournals()->sum('transaction_amount'));
+ $currency = Amount::getDefaultCurrency();
+ $data = ['box' => 'in', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data);
return Response::json($data);
}
/**
- * @param AccountTaskerInterface $accountTasker
- * @param AccountRepositoryInterface $repository
- *
* @return \Symfony\Component\HttpFoundation\Response
+ * @internal param AccountTaskerInterface $accountTasker
+ * @internal param AccountRepositoryInterface $repository
+ *
*/
public function boxOut()
{
@@ -200,9 +201,9 @@ class JsonController extends Controller
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
- $amount = strval($collector->getJournals()->sum('transaction_amount'));
-
- $data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
+ $amount = strval($collector->getJournals()->sum('transaction_amount'));
+ $currency = Amount::getDefaultCurrency();
+ $data = ['box' => 'out', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data);
return Response::json($data);
@@ -236,16 +237,6 @@ class JsonController extends Controller
return Response::json($return);
}
- /**
- * @return \Illuminate\Http\JsonResponse
- */
- public function endTour()
- {
- Preferences::set('tour', false);
-
- return Response::json('true');
- }
-
/**
* Returns a JSON list of all beneficiaries.
*
@@ -292,34 +283,6 @@ class JsonController extends Controller
}
- /**
- *
- */
- public function tour()
- {
- $pref = Preferences::get('tour', true);
- if (!$pref) {
- throw new FireflyException('Cannot find preference for tour. Exit.'); // @codeCoverageIgnore
- }
- $headers = ['main-content', 'sidebar-toggle', 'account-menu', 'budget-menu', 'report-menu', 'transaction-menu', 'option-menu', 'main-content-end'];
- $steps = [];
- foreach ($headers as $header) {
- $steps[] = [
- 'element' => '#' . $header,
- 'title' => trans('help.' . $header . '-title'),
- 'content' => trans('help.' . $header . '-text'),
- ];
- }
- $steps[0]['orphan'] = true;// orphan and backdrop for first element.
- $steps[0]['backdrop'] = true;
- $steps[1]['placement'] = 'left';// sidebar position left:
- $steps[7]['orphan'] = true; // final in the center again.
- $steps[7]['backdrop'] = true;
- $template = view('json.tour')->render();
-
- return Response::json(['steps' => $steps, 'template' => $template]);
- }
-
/**
* @param JournalCollectorInterface $collector
* @param string $what
@@ -364,7 +327,7 @@ class JsonController extends Controller
$keys = array_keys(config('firefly.rule-triggers'));
$triggers = [];
foreach ($keys as $key) {
- if ($key != 'user_action') {
+ if ($key !== 'user_action') {
$triggers[$key] = trans('firefly.rule_trigger_' . $key . '_choice');
}
}
diff --git a/app/Http/Controllers/NewUserController.php b/app/Http/Controllers/NewUserController.php
index 478e5830d6..8e47d8a13b 100644
--- a/app/Http/Controllers/NewUserController.php
+++ b/app/Http/Controllers/NewUserController.php
@@ -54,7 +54,6 @@ class NewUserController extends Controller
View::share('title', trans('firefly.welcome'));
View::share('mainTitleIcon', 'fa-fire');
-
$types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->count($types);
@@ -74,30 +73,13 @@ class NewUserController extends Controller
*/
public function submit(NewUserFormRequest $request, AccountRepositoryInterface $repository)
{
- $count = 1;
// create normal asset account:
$this->createAssetAccount($request, $repository);
// create savings account
- $savingBalance = strval($request->get('savings_balance')) === '' ? '0' : strval($request->get('savings_balance'));
- if (bccomp($savingBalance, '0') !== 0) {
- $this->createSavingsAccount($request, $repository);
- $count++;
- }
+ $this->createSavingsAccount($request, $repository);
-
- // create credit card.
- $limit = strval($request->get('credit_card_limit')) === '' ? '0' : strval($request->get('credit_card_limit'));
- if (bccomp($limit, '0') !== 0) {
- $this->storeCreditCard($request, $repository);
- $count++;
- }
- $message = strval(trans('firefly.stored_new_accounts_new_user'));
- if ($count == 1) {
- $message = strval(trans('firefly.stored_new_account_new_user'));
- }
-
- Session::flash('success', $message);
+ Session::flash('success', strval(trans('firefly.stored_new_accounts_new_user')));
Preferences::mark();
return redirect(route('index'));
@@ -152,29 +134,4 @@ class NewUserController extends Controller
return true;
}
- /**
- * @param NewUserFormRequest $request
- * @param AccountRepositoryInterface $repository
- *
- * @return bool
- */
- private function storeCreditCard(NewUserFormRequest $request, AccountRepositoryInterface $repository): bool
- {
- $creditAccount = [
- 'name' => 'Credit card',
- 'iban' => null,
- 'accountType' => 'asset',
- 'virtualBalance' => round($request->get('credit_card_limit'), 12),
- 'active' => true,
- 'accountRole' => 'ccAsset',
- 'openingBalance' => null,
- 'openingBalanceDate' => null,
- 'openingBalanceCurrency' => intval($request->input('amount_currency_id_credit_card_limit')),
- 'ccType' => 'monthlyFull',
- 'ccMonthlyPaymentDate' => Carbon::now()->year . '-01-01',
- ];
- $repository->store($creditAccount);
-
- return true;
- }
}
diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php
index 052900cba5..207f98cd6f 100644
--- a/app/Http/Controllers/PiggyBankController.php
+++ b/app/Http/Controllers/PiggyBankController.php
@@ -276,18 +276,29 @@ class PiggyBankController extends Controller
*/
public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
{
- $amount = $request->get('amount');
-
+ $amount = $request->get('amount');
+ $currency = Amount::getDefaultCurrency();
if ($repository->canAddAmount($piggyBank, $amount)) {
$repository->addAmount($piggyBank, $amount);
- Session::flash('success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name])));
+ Session::flash(
+ 'success', strval(
+ trans(
+ 'firefly.added_amount_to_piggy',
+ ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name]
+ )
+ )
+ );
Preferences::mark();
return redirect(route('piggy-banks.index'));
}
Log::error('Cannot add ' . $amount . ' because canAddAmount returned false.');
- Session::flash('error', strval(trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
+ Session::flash(
+ 'error', strval(
+ trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)])
+ )
+ );
return redirect(route('piggy-banks.index'));
}
@@ -301,11 +312,13 @@ class PiggyBankController extends Controller
*/
public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
{
- $amount = $request->get('amount');
+ $amount = $request->get('amount');
+ $currency = Amount::getDefaultCurrency();
if ($repository->canRemoveAmount($piggyBank, $amount)) {
$repository->removeAmount($piggyBank, $amount);
Session::flash(
- 'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name]))
+ 'success',
+ strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name]))
);
Preferences::mark();
@@ -314,7 +327,11 @@ class PiggyBankController extends Controller
$amount = strval(round($request->get('amount'), 12));
- Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
+ Session::flash(
+ 'error', strval(
+ trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)])
+ )
+ );
return redirect(route('piggy-banks.index'));
}
@@ -380,7 +397,7 @@ class PiggyBankController extends Controller
// @codeCoverageIgnoreEnd
}
- return redirect($this->getPreviousUri('piggy-banks.edit.uri'));
+ return redirect($this->getPreviousUri('piggy-banks.create.uri'));
}
/**
diff --git a/app/Http/Controllers/Report/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php
index 1528f44460..250e54dde9 100644
--- a/app/Http/Controllers/Report/CategoryController.php
+++ b/app/Http/Controllers/Report/CategoryController.php
@@ -120,7 +120,7 @@ class CategoryController extends Controller
foreach ($categories as $category) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
if (bccomp($spent, '0') !== 0) {
- $report[$category->id] = ['name' => $category->name, 'spent' => $spent];
+ $report[$category->id] = ['name' => $category->name, 'spent' => $spent, 'id' => $category->id];
}
}
diff --git a/app/Http/Controllers/Report/OperationsController.php b/app/Http/Controllers/Report/OperationsController.php
index 5081ba6554..59643b578d 100644
--- a/app/Http/Controllers/Report/OperationsController.php
+++ b/app/Http/Controllers/Report/OperationsController.php
@@ -15,10 +15,7 @@ namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
-use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
-use FireflyIII\Models\Transaction;
-use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php
index a7247b7705..5ab2f5e4ae 100644
--- a/app/Http/Controllers/RuleController.php
+++ b/app/Http/Controllers/RuleController.php
@@ -282,7 +282,7 @@ class RuleController extends Controller
// build trigger array from response
$triggers = $this->getValidTriggerList($request);
- if (count($triggers) == 0) {
+ if (count($triggers) === 0) {
return Response::json(['html' => '', 'warning' => trans('firefly.warning_no_valid_triggers')]);
}
@@ -298,15 +298,15 @@ class RuleController extends Controller
// Warn the user if only a subset of transactions is returned
$warning = '';
- if (count($matchingTransactions) == $limit) {
+ if (count($matchingTransactions) === $limit) {
$warning = trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]);
}
- if (count($matchingTransactions) == 0) {
+ if (count($matchingTransactions) === 0) {
$warning = trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]);
}
// Return json response
- $view = view('list.journals-tiny-tasker', ['transactions' => $matchingTransactions])->render();
+ $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render();
return Response::json(['html' => $view, 'warning' => $warning]);
}
@@ -440,7 +440,7 @@ class RuleController extends Controller
/** @var RuleTrigger $entry */
foreach ($rule->ruleTriggers as $entry) {
- if ($entry->trigger_type != 'user_action') {
+ if ($entry->trigger_type !== 'user_action') {
$count = ($index + 1);
$triggers[] = view(
'rules.partials.trigger',
diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php
index 12f9a0bb58..d0f60c08d3 100644
--- a/app/Http/Controllers/RuleGroupController.php
+++ b/app/Http/Controllers/RuleGroupController.php
@@ -253,7 +253,7 @@ class RuleGroupController extends Controller
$data = [
'title' => $request->input('title'),
'description' => $request->input('description'),
- 'active' => intval($request->input('active')) == 1,
+ 'active' => intval($request->input('active')) === 1,
];
$repository->update($ruleGroup, $data);
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
index f014f8325a..a0acba5a7e 100644
--- a/app/Http/Controllers/SearchController.php
+++ b/app/Http/Controllers/SearchController.php
@@ -16,6 +16,7 @@ namespace FireflyIII\Http\Controllers;
use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
+use Response;
use View;
/**
@@ -51,6 +52,15 @@ class SearchController extends Controller
*/
public function index(Request $request, SearchInterface $searcher)
{
+ $fullQuery = $request->get('q');
+
+ // parse search terms:
+ $searcher->parseQuery($fullQuery);
+ $query = $searcher->getWordsAsString();
+ $subTitle = trans('breadcrumbs.search_result', ['query' => $query]);
+
+ return view('search.index', compact('query', 'fullQuery', 'subTitle'));
+
// yes, hard coded values:
$minSearchLen = 1;
$limit = 20;
@@ -94,4 +104,19 @@ class SearchController extends Controller
return view('search.index', compact('rawQuery', 'hasModifiers', 'modifiers', 'subTitle', 'limit', 'query', 'result'));
}
+ public function search(Request $request, SearchInterface $searcher)
+ {
+ $fullQuery = $request->get('query');
+
+ // parse search terms:
+ $searcher->parseQuery($fullQuery);
+ $searcher->setLimit(20);
+ $transactions = $searcher->searchTransactions();
+ $html = view('search.search', compact('transactions'))->render();
+
+ return Response::json(['count' => $transactions->count(), 'html' => $html]);
+
+
+ }
+
}
diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php
index fa94a1b612..f373fa7dff 100644
--- a/app/Http/Controllers/TagController.php
+++ b/app/Http/Controllers/TagController.php
@@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
+use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Http\Requests\TagFormRequest;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
@@ -235,7 +236,7 @@ class TagController extends Controller
// default values:
$subTitle = $tag->tag;
$subTitleIcon = 'fa-tag';
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$count = 0;
$loop = 0;
@@ -245,6 +246,7 @@ class TagController extends Controller
$periods = new Collection;
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
$sum = '0';
+ $path = 'tags/show/' . $tag->id;
// prep for "all" view.
@@ -253,6 +255,7 @@ class TagController extends Controller
$start = $repository->firstUseDate($tag);
$end = new Carbon;
$sum = $repository->sumOfTag($tag);
+ $path = 'tags/show/' . $tag->id . '/all';
}
// prep for "specific date" view.
@@ -282,15 +285,15 @@ class TagController extends Controller
Log::info('Now at tag loop start.');
while ($count === 0 && $loop < 3) {
$loop++;
- Log::info('Count is zero, search for journals.');
+ Log::info(sprintf('Count is zero, search for journals between %s and %s (pagesize %d, page %d).', $start, $end, $pageSize, $page));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
- ->setTag($tag)->withBudgetInformation()->withCategoryInformation();
+ ->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class);
$journals = $collector->getPaginatedJournals();
- $journals->setPath('tags/show/' . $tag->id);
+ $journals->setPath($path);
$count = $journals->getCollection()->count();
- if ($count === 0) {
+ if ($count === 0 && $loop < 3) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
@@ -298,7 +301,7 @@ class TagController extends Controller
}
}
- if ($moment != 'all' && $loop > 1) {
+ if ($moment !== 'all' && $loop > 1) {
$subTitle = trans(
'firefly.journals_in_period_for_tag',
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php
index 8853a3dbaa..b01335dce7 100644
--- a/app/Http/Controllers/Transaction/ConvertController.php
+++ b/app/Http/Controllers/Transaction/ConvertController.php
@@ -174,14 +174,17 @@ class ConvertController extends Controller
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore
- case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one
+ case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
+ // one
$destination = $sourceAccount;
break;
- case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: // two
+ case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER:
+ // two
$destination = $accountRepository->find(intval($data['destination_account_asset']));
break;
- case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three
- case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five
+ case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
+ case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
+ // three and five
if ($data['destination_account_expense'] === '') {
// destination is a cash account.
$destination = $accountRepository->getCashAccount();
@@ -197,8 +200,9 @@ class ConvertController extends Controller
];
$destination = $accountRepository->store($data);
break;
- case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: // four
- case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six
+ case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER:
+ case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
+ // four and six
$destination = $destinationAccount;
break;
}
@@ -225,8 +229,8 @@ class ConvertController extends Controller
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore
- case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one
- case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six
+ case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
+ case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
if ($data['source_account_revenue'] === '') {
// destination is a cash account.
@@ -244,14 +248,14 @@ class ConvertController extends Controller
];
$source = $accountRepository->store($data);
break;
- case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: // two
- case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five
+ case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER:
+ case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
$source = $sourceAccount;
break;
- case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three
+ case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
$source = $destinationAccount;
break;
- case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: // four
+ case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER:
$source = $accountRepository->find(intval($data['source_account_asset']));
break;
}
diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php
index 285a73f3e9..fe5c99c4b3 100644
--- a/app/Http/Controllers/Transaction/MassController.php
+++ b/app/Http/Controllers/Transaction/MassController.php
@@ -19,6 +19,7 @@ use FireflyIII\Http\Requests\MassDeleteJournalRequest;
use FireflyIII\Http\Requests\MassEditJournalRequest;
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\Journal\JournalRepositoryInterface;
@@ -85,7 +86,7 @@ class MassController extends Controller
foreach ($ids as $journalId) {
/** @var TransactionJournal $journal */
$journal = $repository->find(intval($journalId));
- if (!is_null($journal->id) && $journalId == $journal->id) {
+ if (!is_null($journal->id) && $journalId === $journal->id) {
$set->push($journal);
}
}
@@ -126,8 +127,7 @@ class MassController extends Controller
$budgetRepository = app(BudgetRepositoryInterface::class);
$budgets = $budgetRepository->getBudgets();
- // skip transactions that have multiple destinations
- // or multiple sources:
+ // skip transactions that have multiple destinations, multiple sources or are an opening balance.
$filtered = new Collection;
$messages = [];
/**
@@ -146,6 +146,10 @@ class MassController extends Controller
$messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
+ if ($journal->transactionType->type === TransactionType::OPENING_BALANCE) {
+ $messages[] = trans('firefly.cannot_edit_opening_balance');
+ continue;
+ }
$filtered->push($journal);
}
@@ -158,13 +162,21 @@ class MassController extends Controller
Session::flash('gaEventCategory', 'transactions');
Session::flash('gaEventAction', 'mass-edit');
- // set some values to be used in the edit routine:
+ // collect some useful meta data for the mass edit:
$filtered->each(
function (TransactionJournal $journal) {
- $journal->amount = $journal->amountPositive();
- $sources = $journal->sourceAccountList();
- $destinations = $journal->destinationAccountList();
- $journal->transaction_count = $journal->transactions()->count();
+ $transaction = $journal->positiveTransaction();
+ $currency = $transaction->transactionCurrency;
+ $journal->amount = floatval($transaction->amount);
+ $sources = $journal->sourceAccountList();
+ $destinations = $journal->destinationAccountList();
+ $journal->transaction_count = $journal->transactions()->count();
+ $journal->currency_symbol = $currency->symbol;
+ $journal->transaction_type_type = $journal->transactionType->type;
+
+ $journal->foreign_amount = floatval($transaction->foreign_amount);
+ $journal->foreign_currency = $transaction->foreignCurrency;
+
if (!is_null($sources->first())) {
$journal->source_account_id = $sources->first()->id;
$journal->source_account_name = $sources->first()->editname;
@@ -208,6 +220,10 @@ class MassController extends Controller
$budgetId = $request->get('budget_id')[$journal->id] ?? 0;
$category = $request->get('category')[$journal->id];
$tags = $journal->tags->pluck('tag')->toArray();
+ $amount = round($request->get('amount')[$journal->id], 12);
+ $foreignAmount = isset($request->get('foreign_amount')[$journal->id]) ? round($request->get('foreign_amount')[$journal->id], 12) : null;
+ $foreignCurrencyId = isset($request->get('foreign_currency_id')[$journal->id]) ?
+ intval($request->get('foreign_currency_id')[$journal->id]) : null;
// build data array
$data = [
@@ -218,16 +234,19 @@ class MassController extends Controller
'source_account_name' => $sourceAccountName,
'destination_account_id' => intval($destAccountId),
'destination_account_name' => $destAccountName,
- 'amount' => round($request->get('amount')[$journal->id], 12),
- 'currency_id' => $journal->transaction_currency_id,
+ 'amount' => $foreignAmount,
+ 'native_amount' => $amount,
+ 'source_amount' => $amount,
'date' => new Carbon($request->get('date')[$journal->id]),
'interest_date' => $journal->interest_date,
'book_date' => $journal->book_date,
'process_date' => $journal->process_date,
'budget_id' => intval($budgetId),
+ 'currency_id' => $foreignCurrencyId,
+ 'foreign_amount' => $foreignAmount,
+ 'destination_amount' => $foreignAmount,
'category' => $category,
'tags' => $tags,
-
];
// call repository update function.
$repository->update($journal, $data);
@@ -235,6 +254,7 @@ class MassController extends Controller
$count++;
}
}
+
}
Preferences::mark();
Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php
index 27c7cdba77..6b1a138ee5 100644
--- a/app/Http/Controllers/Transaction/SingleController.php
+++ b/app/Http/Controllers/Transaction/SingleController.php
@@ -21,6 +21,7 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\JournalFormRequest;
use FireflyIII\Models\AccountType;
+use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -93,27 +94,35 @@ class SingleController extends Controller
$category = $journal->categories()->first();
$categoryName = is_null($category) ? '' : $category->name;
$tags = join(',', $journal->tags()->get()->pluck('tag')->toArray());
-
+ /** @var Transaction $transaction */
+ $transaction = $journal->transactions()->first();
+ $amount = Steam::positive($transaction->amount);
+ $foreignAmount = is_null($transaction->foreign_amount) ? null : Steam::positive($transaction->foreign_amount);
$preFilled = [
- 'description' => $journal->description,
- 'source_account_id' => $source->id,
- 'source_account_name' => $source->name,
- 'destination_account_id' => $destination->id,
- 'destination_account_name' => $destination->name,
- 'amount' => $journal->amountPositive(),
- 'date' => $journal->date->format('Y-m-d'),
- 'budget_id' => $budgetId,
- 'category' => $categoryName,
- 'tags' => $tags,
- 'interest_date' => $journal->getMeta('interest_date'),
- 'book_date' => $journal->getMeta('book_date'),
- 'process_date' => $journal->getMeta('process_date'),
- 'due_date' => $journal->getMeta('due_date'),
- 'payment_date' => $journal->getMeta('payment_date'),
- 'invoice_date' => $journal->getMeta('invoice_date'),
- 'internal_reference' => $journal->getMeta('internal_reference'),
- 'notes' => $journal->getMeta('notes'),
+ 'description' => $journal->description,
+ 'source_account_id' => $source->id,
+ 'source_account_name' => $source->name,
+ 'destination_account_id' => $destination->id,
+ 'destination_account_name' => $destination->name,
+ 'amount' => $amount,
+ 'source_amount' => $amount,
+ 'destination_amount' => $foreignAmount,
+ 'foreign_amount' => $foreignAmount,
+ 'native_amount' => $foreignAmount,
+ 'amount_currency_id_amount' => $transaction->foreign_currency_id ?? 0,
+ 'date' => $journal->date->format('Y-m-d'),
+ 'budget_id' => $budgetId,
+ 'category' => $categoryName,
+ 'tags' => $tags,
+ 'interest_date' => $journal->getMeta('interest_date'),
+ 'book_date' => $journal->getMeta('book_date'),
+ 'process_date' => $journal->getMeta('process_date'),
+ 'due_date' => $journal->getMeta('due_date'),
+ 'payment_date' => $journal->getMeta('payment_date'),
+ 'invoice_date' => $journal->getMeta('invoice_date'),
+ 'internal_reference' => $journal->getMeta('internal_reference'),
+ 'notes' => $journal->getMeta('notes'),
];
Session::flash('preFilled', $preFilled);
@@ -238,6 +247,7 @@ class SingleController extends Controller
$sourceAccounts = $journal->sourceAccountList();
$destinationAccounts = $journal->destinationAccountList();
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
+ $pTransaction = $journal->positiveTransaction();
$preFilled = [
'date' => $journal->dateAsString(),
'interest_date' => $journal->dateAsString('interest_date'),
@@ -250,8 +260,6 @@ class SingleController extends Controller
'source_account_name' => $sourceAccounts->first()->edit_name,
'destination_account_id' => $destinationAccounts->first()->id,
'destination_account_name' => $destinationAccounts->first()->edit_name,
- 'amount' => $journal->amountPositive(),
- 'currency' => $journal->transactionCurrency,
// new custom fields:
'due_date' => $journal->dateAsString('due_date'),
@@ -260,26 +268,36 @@ class SingleController extends Controller
'interal_reference' => $journal->getMeta('internal_reference'),
'notes' => $journal->getMeta('notes'),
- // exchange rate fields
- 'native_amount' => $journal->amountPositive(),
- 'native_currency' => $journal->transactionCurrency,
+ // amount fields
+ 'amount' => $pTransaction->amount,
+ 'source_amount' => $pTransaction->amount,
+ 'native_amount' => $pTransaction->amount,
+ 'destination_amount' => $pTransaction->foreign_amount,
+ 'currency' => $pTransaction->transactionCurrency,
+ 'source_currency' => $pTransaction->transactionCurrency,
+ 'native_currency' => $pTransaction->transactionCurrency,
+ 'foreign_currency' => !is_null($pTransaction->foreignCurrency) ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency,
+ 'destination_currency' => !is_null($pTransaction->foreignCurrency) ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency,
];
- // if user has entered a foreign currency, update some fields
- $foreignCurrencyId = intval($journal->getMeta('foreign_currency_id'));
- if ($foreignCurrencyId > 0) {
- // update some fields in pre-filled.
- // @codeCoverageIgnoreStart
- $preFilled['amount'] = $journal->getMeta('foreign_amount');
- $preFilled['currency'] = $this->currency->find(intval($journal->getMeta('foreign_currency_id')));
- // @codeCoverageIgnoreEnd
+ // amounts for withdrawals and deposits:
+ // amount, native_amount, source_amount, destination_amount
+ if (($journal->isWithdrawal() || $journal->isDeposit()) && !is_null($pTransaction->foreign_amount)) {
+ $preFilled['amount'] = $pTransaction->foreign_amount;
+ $preFilled['currency'] = $pTransaction->foreignCurrency;
}
- if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
+ if ($journal->isTransfer() && !is_null($pTransaction->foreign_amount)) {
+ $preFilled['destination_amount'] = $pTransaction->foreign_amount;
+ $preFilled['destination_currency'] = $pTransaction->foreignCurrency;
+ }
+
+ // fixes for cash accounts:
+ if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type === AccountType::CASH) {
$preFilled['destination_account_name'] = '';
}
- if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) {
+ if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type === AccountType::CASH) {
$preFilled['source_account_name'] = '';
}
@@ -319,6 +337,7 @@ class SingleController extends Controller
return redirect(route('transactions.create', [$request->input('what')]))->withInput();
}
+
/** @var array $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
$this->attachments->saveAttachmentsForModel($journal, $files);
diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php
index c587129811..32f0807c3b 100644
--- a/app/Http/Controllers/Transaction/SplitController.php
+++ b/app/Http/Controllers/Transaction/SplitController.php
@@ -93,7 +93,7 @@ class SplitController extends Controller
}
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
- $currencies = ExpandedForm::makeSelectList($this->currencies->get());
+ $currencies = $this->currencies->get();
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
@@ -130,7 +130,6 @@ class SplitController extends Controller
*/
public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
{
-
if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal);
}
@@ -179,7 +178,6 @@ class SplitController extends Controller
'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:
@@ -218,10 +216,9 @@ class SplitController extends Controller
'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($journal->transactionTypeStr()),
- 'date' => $request->old('date', $journal->date),
+ 'date' => $request->old('date', $journal->date->format('Y-m-d')),
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
// all custom fields:
@@ -253,14 +250,22 @@ class SplitController extends Controller
/** @var array $transaction */
foreach ($transactions as $index => $transaction) {
$set = [
- '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'], 12),
- 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
- 'category' => $transaction['category'],
+ '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'], 12),
+ 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
+ 'category' => $transaction['category'],
+ 'transaction_currency_id' => $transaction['transaction_currency_id'],
+ 'transaction_currency_code' => $transaction['transaction_currency_code'],
+ 'transaction_currency_symbol' => $transaction['transaction_currency_symbol'],
+ 'foreign_amount' => round($transaction['foreign_destination_amount'], 12),
+ 'foreign_currency_id' => $transaction['foreign_currency_id'],
+ 'foreign_currency_code' => $transaction['foreign_currency_code'],
+ 'foreign_currency_symbol' => $transaction['foreign_currency_symbol'],
+
];
// set initial category and/or budget:
@@ -294,8 +299,12 @@ class SplitController extends Controller
'destination_account_id' => $transaction['destination_account_id'] ?? 0,
'destination_account_name' => $transaction['destination_account_name'] ?? '',
'amount' => round($transaction['amount'] ?? 0, 12),
+ 'foreign_amount' => !isset($transaction['foreign_amount']) ? null : round($transaction['foreign_amount'] ?? 0, 12),
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
'category' => $transaction['category'] ?? '',
+ 'transaction_currency_id' => intval($transaction['transaction_currency_id']),
+ 'foreign_currency_id' => $transaction['foreign_currency_id'] ?? null,
+
];
}
Log::debug(sprintf('Found %d splits in request data.', count($return)));
diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php
index 1e838c5dec..e506a6fa57 100644
--- a/app/Http/Controllers/TransactionController.php
+++ b/app/Http/Controllers/TransactionController.php
@@ -18,7 +18,6 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Models\TransactionJournal;
-use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Support\CacheProperties;
@@ -71,7 +70,7 @@ class TransactionController extends Controller
// default values:
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
$types = config('firefly.transactionTypesByWhat.' . $what);
- $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
+ $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$count = 0;
$loop = 0;
@@ -79,6 +78,7 @@ class TransactionController extends Controller
$start = null;
$end = null;
$periods = new Collection;
+ $path = '/transactions/' . $what;
// prep for "all" view.
if ($moment === 'all') {
@@ -86,12 +86,14 @@ class TransactionController extends Controller
$first = $repository->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
+ $path = '/transactions/' . $what . '/all/';
}
// prep for "specific date" view.
if (strlen($moment) > 0 && $moment !== 'all') {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
+ $path = '/transactions/' . $what . '/' . $moment;
$subTitle = trans(
'firefly.title_' . $what . '_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -119,9 +121,9 @@ class TransactionController extends Controller
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount();
$collector->removeFilter(InternalTransferFilter::class);
$journals = $collector->getPaginatedJournals();
- $journals->setPath('/transactions/' . $what);
+ $journals->setPath($path);
$count = $journals->getCollection()->count();
- if ($count === 0) {
+ if ($count === 0 && $loop < 3) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
@@ -129,7 +131,7 @@ class TransactionController extends Controller
}
}
- if ($moment != 'all' && $loop > 1) {
+ if ($moment !== 'all' && $loop > 1) {
$subTitle = trans(
'firefly.title_' . $what . '_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -179,21 +181,12 @@ class TransactionController extends Controller
return $this->redirectToAccount($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) . '"';
- $foreignCurrency = null;
+ $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 ($journal->hasMeta('foreign_currency_id')) {
- // @codeCoverageIgnoreStart
- /** @var CurrencyRepositoryInterface $repository */
- $repository = app(CurrencyRepositoryInterface::class);
- $foreignCurrency = $repository->find(intval($journal->getMeta('foreign_currency_id')));
- // @codeCoverageIgnoreEnd
- }
-
- return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'foreignCurrency'));
+ return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
}
diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php
index 90b8eb760d..bf24b65418 100644
--- a/app/Http/Requests/AccountFormRequest.php
+++ b/app/Http/Requests/AccountFormRequest.php
@@ -38,19 +38,19 @@ class AccountFormRequest extends Request
public function getAccountData(): array
{
return [
- 'name' => $this->string('name'),
- 'active' => $this->boolean('active'),
- 'accountType' => $this->string('what'),
- 'currency_id' => $this->integer('currency_id'),
- 'virtualBalance' => $this->float('virtualBalance'),
- 'iban' => $this->string('iban'),
- 'BIC' => $this->string('BIC'),
- 'accountNumber' => $this->string('accountNumber'),
- 'accountRole' => $this->string('accountRole'),
- 'openingBalance' => $this->float('openingBalance'),
- 'openingBalanceDate' => $this->date('openingBalanceDate'),
- 'ccType' => $this->string('ccType'),
- 'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'),
+ 'name' => $this->string('name'),
+ 'active' => $this->boolean('active'),
+ 'accountType' => $this->string('what'),
+ 'currency_id' => $this->integer('currency_id'),
+ 'virtualBalance' => $this->float('virtualBalance'),
+ 'iban' => $this->string('iban'),
+ 'BIC' => $this->string('BIC'),
+ 'accountNumber' => $this->string('accountNumber'),
+ 'accountRole' => $this->string('accountRole'),
+ 'openingBalance' => $this->float('openingBalance'),
+ 'openingBalanceDate' => $this->date('openingBalanceDate'),
+ 'ccType' => $this->string('ccType'),
+ 'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'),
];
}
diff --git a/app/Http/Requests/ImportUploadRequest.php b/app/Http/Requests/ImportUploadRequest.php
index 93065bcada..758ee6164a 100644
--- a/app/Http/Requests/ImportUploadRequest.php
+++ b/app/Http/Requests/ImportUploadRequest.php
@@ -38,8 +38,9 @@ class ImportUploadRequest extends Request
$types = array_keys(config('firefly.import_formats'));
return [
- 'import_file' => 'required|file',
- 'import_file_type' => 'required|in:' . join(',', $types),
+ 'import_file' => 'required|file',
+ 'import_file_type' => 'required|in:' . join(',', $types),
+ 'configuration_file' => 'file',
];
}
diff --git a/app/Http/Requests/TagFormRequest.php b/app/Http/Requests/TagFormRequest.php
index d61e5cb8d9..1348a0d767 100644
--- a/app/Http/Requests/TagFormRequest.php
+++ b/app/Http/Requests/TagFormRequest.php
@@ -37,7 +37,7 @@ class TagFormRequest extends Request
*/
public function collectTagData(): array
{
- if ($this->get('setTag') == 'true') {
+ if ($this->get('setTag') === 'true') {
$latitude = $this->string('latitude');
$longitude = $this->string('longitude');
$zoomLevel = $this->integer('zoomLevel');
diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php
index 1b98afd9b4..df835e940a 100644
--- a/app/Http/breadcrumbs.php
+++ b/app/Http/breadcrumbs.php
@@ -78,10 +78,10 @@ Breadcrumbs::register(
$breadcrumbs->push(trans('firefly.everything'), route('accounts.show', [$account->id, 'all']));
}
// when is specific period or when empty:
- if ($moment !== 'all') {
+ if ($moment !== 'all' && $moment !== '(nothing)') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
- 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
+ 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
);
$breadcrumbs->push($title, route('accounts.show', [$account->id, $moment, $start, $end]));
}
@@ -91,7 +91,7 @@ Breadcrumbs::register(
Breadcrumbs::register(
'accounts.delete', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
- $breadcrumbs->parent('accounts.show', $account, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('accounts.show', $account, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.delete_account', ['name' => e($account->name)]), route('accounts.delete', [$account->id]));
}
);
@@ -99,7 +99,7 @@ Breadcrumbs::register(
Breadcrumbs::register(
'accounts.edit', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
- $breadcrumbs->parent('accounts.show', $account, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('accounts.show', $account, '(nothing)', new Carbon, new Carbon);
$what = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => e($account->name)]), route('accounts.edit', [$account->id]));
@@ -257,8 +257,8 @@ Breadcrumbs::register(
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget', ['all']));
}
- // when is specific period:
- if ($moment !== 'all') {
+ // when is specific period or when empty:
+ if ($moment !== 'all' && $moment !== '(nothing)') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
@@ -312,13 +312,13 @@ Breadcrumbs::register(
Breadcrumbs::register(
'categories.edit', function (BreadCrumbGenerator $breadcrumbs, Category $category) {
- $breadcrumbs->parent('categories.show', $category, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.edit_category', ['name' => e($category->name)]), route('categories.edit', [$category->id]));
}
);
Breadcrumbs::register(
'categories.delete', function (BreadCrumbGenerator $breadcrumbs, Category $category) {
- $breadcrumbs->parent('categories.show', $category, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.delete_category', ['name' => e($category->name)]), route('categories.delete', [$category->id]));
}
);
@@ -333,8 +333,8 @@ Breadcrumbs::register(
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all']));
}
- // when is specific period:
- if ($moment !== 'all') {
+ // when is specific period or when empty:
+ if ($moment !== 'all' && $moment !== '(nothing)') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
@@ -354,8 +354,8 @@ Breadcrumbs::register(
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('categories.no-category', ['all']));
}
- // when is specific period:
- if ($moment !== 'all') {
+ // when is specific period or when empty:
+ if ($moment !== 'all' && $moment !== '(nothing)') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
@@ -469,26 +469,20 @@ Breadcrumbs::register(
$breadcrumbs->push(trans('firefly.import'), route('import.index'));
}
);
-Breadcrumbs::register(
- 'import.complete', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
- $breadcrumbs->parent('import.index');
- $breadcrumbs->push(trans('firefly.bread_crumb_import_complete', ['key' => $job->key]), route('import.complete', [$job->key]));
-}
-);
+
Breadcrumbs::register(
'import.configure', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
$breadcrumbs->parent('import.index');
- $breadcrumbs->push(trans('firefly.bread_crumb_configure_import', ['key' => $job->key]), route('import.configure', [$job->key]));
+ $breadcrumbs->push(trans('firefly.import_config_sub_title', ['key' => $job->key]), route('import.configure', [$job->key]));
}
);
Breadcrumbs::register(
- 'import.finished', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
+ 'import.status', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
$breadcrumbs->parent('import.index');
- $breadcrumbs->push(trans('firefly.bread_crumb_import_finished', ['key' => $job->key]), route('import.finished', [$job->key]));
+ $breadcrumbs->push(trans('firefly.import_status_bread_crumb', ['key' => $job->key]), route('import.status', [$job->key]));
}
);
-
/**
* PREFERENCES
*/
@@ -686,7 +680,7 @@ Breadcrumbs::register(
Breadcrumbs::register(
'search.index', function (BreadCrumbGenerator $breadcrumbs, $query) {
$breadcrumbs->parent('home');
- $breadcrumbs->push(trans('breadcrumbs.searchResult', ['query' => e($query)]), route('search.index'));
+ $breadcrumbs->push(trans('breadcrumbs.search_result', ['query' => e($query)]), route('search.index'));
}
);
@@ -710,14 +704,14 @@ Breadcrumbs::register(
Breadcrumbs::register(
'tags.edit', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) {
- $breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => e($tag->tag)]), route('tags.edit', [$tag->id]));
}
);
Breadcrumbs::register(
'tags.delete', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) {
- $breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => e($tag->tag)]), route('tags.delete', [$tag->id]));
}
);
@@ -726,16 +720,17 @@ Breadcrumbs::register(
Breadcrumbs::register(
'tags.show', function (BreadCrumbGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) {
$breadcrumbs->parent('tags.index');
- $breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id], $moment));
+ $breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id, $moment]));
if ($moment === 'all') {
- $breadcrumbs->push(trans('firefly.everything'), route('tags.show', [$tag->id], $moment));
+ $breadcrumbs->push(trans('firefly.everything'), route('tags.show', [$tag->id, $moment]));
}
- if ($moment !== 'all') {
+ // when is specific period or when empty:
+ if ($moment !== 'all' && $moment !== '(nothing)') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
);
- $breadcrumbs->push($title, route('tags.show', [$tag->id], $moment));
+ $breadcrumbs->push($title, route('tags.show', [$tag->id, $moment]));
}
}
);
@@ -753,8 +748,8 @@ Breadcrumbs::register(
$breadcrumbs->push(trans('firefly.everything'), route('transactions.index', [$what, 'all']));
}
- // when is specific period:
- if ($moment !== 'all') {
+ // when is specific period or when empty:
+ if ($moment !== 'all' && $moment !== '(nothing)') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
@@ -767,7 +762,7 @@ Breadcrumbs::register(
Breadcrumbs::register(
'transactions.create', function (BreadCrumbGenerator $breadcrumbs, string $what) {
- $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what]));
}
);
@@ -789,7 +784,7 @@ Breadcrumbs::register(
'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
$what = strtolower($journal->transactionType->type);
- $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push($journal->description, route('transactions.show', [$journal->id]));
}
);
@@ -814,7 +809,7 @@ Breadcrumbs::register(
if ($journals->count() > 0) {
$journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type);
- $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds));
return;
@@ -829,7 +824,7 @@ Breadcrumbs::register(
$journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type);
- $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
+ $breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds));
}
);
diff --git a/app/Import/Configurator/ConfiguratorInterface.php b/app/Import/Configurator/ConfiguratorInterface.php
new file mode 100644
index 0000000000..1f120d30d3
--- /dev/null
+++ b/app/Import/Configurator/ConfiguratorInterface.php
@@ -0,0 +1,65 @@
+getConfigurationClass();
+ $job = $this->job;
+ /** @var ConfigurationInterface $object */
+ $object = new $class($this->job);
+ $object->setJob($job);
+
+ return $object->storeConfiguration($data);
+ }
+
+ /**
+ * Return the data required for the next step in the job configuration.
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ public function getNextData(): array
+ {
+ $class = $this->getConfigurationClass();
+ $job = $this->job;
+ /** @var ConfigurationInterface $object */
+ $object = app($class);
+ $object->setJob($job);
+
+ return $object->getData();
+
+ }
+
+ /**
+ * @return string
+ * @throws FireflyException
+ */
+ public function getNextView(): string
+ {
+ if (!$this->job->configuration['initial-config-complete']) {
+ return 'import.csv.initial';
+ }
+ if (!$this->job->configuration['column-roles-complete']) {
+ return 'import.csv.roles';
+ }
+ if (!$this->job->configuration['column-mapping-complete']) {
+ return 'import.csv.map';
+ }
+
+ throw new FireflyException('No view for state');
+ }
+
+ /**
+ * @return bool
+ */
+ public function isJobConfigured(): bool
+ {
+ $config = $this->job->configuration;
+ $config['initial-config-complete'] = $config['initial-config-complete'] ?? false;
+ $config['column-roles-complete'] = $config['column-roles-complete'] ?? false;
+ $config['column-mapping-complete'] = $config['column-mapping-complete'] ?? false;
+ $this->job->configuration = $config;
+ $this->job->save();
+
+ if ($this->job->configuration['initial-config-complete']
+ && $this->job->configuration['column-roles-complete']
+ && $this->job->configuration['column-mapping-complete']
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param ImportJob $job
+ */
+ public function setJob(ImportJob $job)
+ {
+ $this->job = $job;
+ if (is_null($this->job->configuration) || count($this->job->configuration) === 0) {
+ Log::debug(sprintf('Gave import job %s initial configuration.', $this->job->key));
+ $this->job->configuration = config('csv.default_config');
+ $this->job->save();
+ }
+ }
+
+ /**
+ * @return string
+ * @throws FireflyException
+ */
+ private function getConfigurationClass(): string
+ {
+ $class = false;
+ switch (true) {
+ case (!$this->job->configuration['initial-config-complete']):
+ $class = Initial::class;
+ break;
+ case (!$this->job->configuration['column-roles-complete']):
+ $class = Roles::class;
+ break;
+ case (!$this->job->configuration['column-mapping-complete']):
+ $class = Map::class;
+ break;
+ default:
+ break;
+ }
+
+ if ($class === false || strlen($class) === 0) {
+ throw new FireflyException('Cannot handle current job state in getConfigurationClass().');
+ }
+ if (!class_exists($class)) {
+ throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class));
+ }
+
+ return $class;
+ }
+}
diff --git a/app/Import/Converter/AccountId.php b/app/Import/Converter/AccountId.php
deleted file mode 100644
index 88161515ab..0000000000
--- a/app/Import/Converter/AccountId.php
+++ /dev/null
@@ -1,69 +0,0 @@
- $value]);
- if ($value === 0) {
- $this->setCertainty(0);
-
- return new Account;
- }
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $account = $repository->find(intval($this->mapping[$value]));
- if (!is_null($account->id)) {
- Log::debug('Found account by ID', ['id' => $account->id]);
-
- $this->setCertainty(100);
-
- return $account;
- }
- }
- $account = $repository->find($value);// not mapped? Still try to find it first:
- if (!is_null($account->id)) {
- $this->setCertainty(90);
- Log::debug('Found account by ID ', ['id' => $account->id]);
-
- return $account;
- }
- $this->setCertainty(0); // should not really happen. If the ID does not match FF, what is FF supposed to do?
-
- return new Account;
-
- }
-}
diff --git a/app/Import/Converter/Amount.php b/app/Import/Converter/Amount.php
index c7840e397c..75f90a534d 100644
--- a/app/Import/Converter/Amount.php
+++ b/app/Import/Converter/Amount.php
@@ -18,7 +18,7 @@ namespace FireflyIII\Import\Converter;
*
* @package FireflyIII\Import\Converter
*/
-class Amount extends BasicConverter implements ConverterInterface
+class Amount implements ConverterInterface
{
/**
@@ -28,18 +28,18 @@ class Amount extends BasicConverter implements ConverterInterface
*
* @param $value
*
- * @return float
+ * @return string
*/
- public function convert($value): float
+ public function convert($value): string
{
$len = strlen($value);
$decimalPosition = $len - 3;
$decimal = null;
- if (($len > 2 && $value{$decimalPosition} == '.') || ($len > 2 && strpos($value, '.') > $decimalPosition)) {
+ if (($len > 2 && $value{$decimalPosition} === '.') || ($len > 2 && strpos($value, '.') > $decimalPosition)) {
$decimal = '.';
}
- if ($len > 2 && $value{$decimalPosition} == ',') {
+ if ($len > 2 && $value{$decimalPosition} === ',') {
$decimal = ',';
}
@@ -59,10 +59,7 @@ class Amount extends BasicConverter implements ConverterInterface
$value = str_replace($search, '', $value);
}
- $this->setCertainty(90);
-
-
- return round(floatval($value), 12);
+ return strval(round(floatval($value), 12));
}
}
diff --git a/app/Import/Converter/AssetAccountIban.php b/app/Import/Converter/AssetAccountIban.php
deleted file mode 100644
index f98bef6110..0000000000
--- a/app/Import/Converter/AssetAccountIban.php
+++ /dev/null
@@ -1,87 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Account;
- }
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $account = $repository->find(intval($this->mapping[$value]));
- if (!is_null($account->id)) {
- $this->setCertainty(100);
- Log::debug('Found account by ID', ['id' => $account->id]);
-
- return $account;
- }
- }
-
- // not mapped? Still try to find it first:
- $account = $repository->findByIban($value, [AccountType::ASSET]);
- if (!is_null($account->id)) {
- Log::debug('Found account by IBAN', ['id' => $account->id]);
- $this->setCertainty(50);
-
- return $account;
- }
-
-
- $account = $repository->store(
- ['name' => 'Asset account with IBAN ' . $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'asset', 'virtualBalance' => 0,
- 'active' => true, 'openingBalance' => 0]
- );
-
- if (is_null($account->id)) {
- $this->setCertainty(0);
- Log::info('Could not store new asset account by IBAN', $account->getErrors()->toArray());
-
- return new Account;
- }
-
- $this->setCertainty(100);
-
- return $account;
- }
-}
diff --git a/app/Import/Converter/AssetAccountName.php b/app/Import/Converter/AssetAccountName.php
deleted file mode 100644
index 8355c3b056..0000000000
--- a/app/Import/Converter/AssetAccountName.php
+++ /dev/null
@@ -1,90 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Account;
- }
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $account = $repository->find(intval($this->mapping[$value]));
- if (!is_null($account->id)) {
- Log::debug('Found account by ID', ['id' => $account->id]);
- $this->setCertainty(100);
-
- return $account;
- }
- }
-
- // not mapped? Still try to find it first:
- $account = $repository->findByName($value, [AccountType::ASSET]);
- if (!is_null($account->id)) {
- Log::debug('Found asset account by name', ['value' => $value, 'id' => $account->id]);
-
- return $account;
- }
-
-
- $account = $repository->store(
- ['name' => $value, 'iban' => null, 'openingBalance' => 0, 'user' => $this->user->id, 'accountType' => 'asset', 'virtualBalance' => 0,
- 'active' => true]
- );
-
- if (is_null($account->id)) {
- $this->setCertainty(0);
- Log::info('Could not store new asset account by name', $account->getErrors()->toArray());
-
- return new Account;
- }
-
- $this->setCertainty(100);
-
- Log::debug('Created new asset account ', ['name' => $account->name, 'id' => $account->id]);
-
- return $account;
-
-
- }
-}
diff --git a/app/Import/Converter/AssetAccountNumber.php b/app/Import/Converter/AssetAccountNumber.php
deleted file mode 100644
index 880b4d3061..0000000000
--- a/app/Import/Converter/AssetAccountNumber.php
+++ /dev/null
@@ -1,96 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- return new Account;
- }
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $account = $repository->find(intval($this->mapping[$value]));
- if (!is_null($account->id)) {
- Log::debug('Found account by ID', ['id' => $account->id]);
-
- return $account;
- }
- }
-
- // not mapped? Still try to find it first:
- $account = $repository->findByAccountNumber($value, [AccountType::ASSET]);
- if (!is_null($account->id)) {
- Log::debug('Found account by name', ['id' => $account->id]);
- $this->setCertainty(50);
-
- return $account;
- }
-
- // try to find by the name we would give it:
- $accountName = 'Asset account with number ' . e($value);
- $account = $repository->findByName($accountName, [AccountType::ASSET]);
- if (!is_null($account->id)) {
- Log::debug('Found account by name', ['id' => $account->id]);
- $this->setCertainty(50);
-
- return $account;
- }
-
-
- $account = $repository->store(
- ['name' => $accountName, 'openingBalance' => 0, 'iban' => null, 'user' => $this->user->id,
- 'accountType' => 'asset',
- 'virtualBalance' => 0, 'accountNumber' => $value, 'active' => true]
- );
-
- if (is_null($account->id)) {
- $this->setCertainty(0);
- Log::info('Could not store new asset account by account number', $account->getErrors()->toArray());
-
- return new Account;
- }
-
- $this->setCertainty(100);
-
- return $account;
-
- }
-}
diff --git a/app/Import/Converter/BasicConverter.php b/app/Import/Converter/BasicConverter.php
deleted file mode 100644
index 49fba30f0d..0000000000
--- a/app/Import/Converter/BasicConverter.php
+++ /dev/null
@@ -1,85 +0,0 @@
-certainty;
- }
-
- /**
- * @param int $certainty
- */
- protected function setCertainty(int $certainty)
- {
- $this->certainty = $certainty;
- }
-
- /**
- * @param array $config
- */
- public function setConfig(array $config)
- {
- $this->config = $config;
- }
-
- /**
- * @param mixed $doMap
- */
- public function setDoMap(bool $doMap)
- {
- $this->doMap = $doMap;
- }
-
- /**
- * @param array $mapping
- *
- */
- public function setMapping(array $mapping)
- {
- $this->mapping = $mapping;
- }
-
- /**
- * @param User $user
- */
- public function setUser(User $user)
- {
- $this->user = $user;
- }
-}
diff --git a/app/Import/Converter/BillId.php b/app/Import/Converter/BillId.php
deleted file mode 100644
index 11c91d6536..0000000000
--- a/app/Import/Converter/BillId.php
+++ /dev/null
@@ -1,76 +0,0 @@
- $value]);
-
- if ($value === 0) {
- $this->setCertainty(0);
-
- return new Bill;
- }
-
- /** @var BillRepositoryInterface $repository */
- $repository = app(BillRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found bill in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $bill = $repository->find(intval($this->mapping[$value]));
- if (!is_null($bill->id)) {
- Log::debug('Found bill by ID', ['id' => $bill->id]);
- $this->setCertainty(100);
-
- return $bill;
- }
- }
-
- // not mapped? Still try to find it first:
- $bill = $repository->find($value);
- if (!is_null($bill->id)) {
- Log::debug('Found bill by ID ', ['id' => $bill->id]);
- $this->setCertainty(100);
-
- return $bill;
- }
-
- // should not really happen. If the ID does not match FF, what is FF supposed to do?
- Log::info(sprintf('Could not find bill with ID %d. Will return NULL', $value));
-
- $this->setCertainty(0);
-
- return new Bill;
-
- }
-}
diff --git a/app/Import/Converter/BillName.php b/app/Import/Converter/BillName.php
deleted file mode 100644
index 2ba4ad2761..0000000000
--- a/app/Import/Converter/BillName.php
+++ /dev/null
@@ -1,99 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Bill;
- }
-
- /** @var BillRepositoryInterface $repository */
- $repository = app(BillRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found bill in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $bill = $repository->find(intval($this->mapping[$value]));
- if (!is_null($bill->id)) {
- Log::debug('Found bill by ID', ['id' => $bill->id]);
- $this->setCertainty(100);
-
- return $bill;
- }
- }
-
- // not mapped? Still try to find it first:
- $bill = $repository->findByName($value);
- if (!is_null($bill->id)) {
- Log::debug('Found bill by name ', ['id' => $bill->id]);
- $this->setCertainty(100);
-
- return $bill;
- }
-
- // create new bill. Use a lot of made up values.
- $bill = $repository->store(
- [
- 'name' => $value,
- 'match' => $value,
- 'amount_min' => 1,
- 'user' => $this->user->id,
- 'amount_max' => 10,
- 'date' => date('Ymd'),
- 'repeat_freq' => 'monthly',
- 'skip' => 0,
- 'automatch' => 0,
- 'active' => 1,
-
- ]
- );
- if (is_null($bill->id)) {
- $this->setCertainty(0);
- Log::info('Could not store new bill by name', $bill->getErrors()->toArray());
-
- return new Bill;
- }
-
- $this->setCertainty(100);
-
- return $bill;
-
-
- }
-}
diff --git a/app/Import/Converter/BudgetId.php b/app/Import/Converter/BudgetId.php
deleted file mode 100644
index ea74b8302f..0000000000
--- a/app/Import/Converter/BudgetId.php
+++ /dev/null
@@ -1,76 +0,0 @@
- $value]);
-
- if ($value === 0) {
- $this->setCertainty(0);
-
- return new Budget;
- }
-
- /** @var BudgetRepositoryInterface $repository */
- $repository = app(BudgetRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found budget in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $budget = $repository->find(intval($this->mapping[$value]));
- if (!is_null($budget->id)) {
- Log::debug('Found budget by ID', ['id' => $budget->id]);
- $this->setCertainty(100);
-
- return $budget;
- }
- }
-
- // not mapped? Still try to find it first:
- $budget = $repository->find($value);
- if (!is_null($budget->id)) {
- Log::debug('Found budget by ID ', ['id' => $budget->id]);
- $this->setCertainty(100);
-
- return $budget;
- }
-
- // should not really happen. If the ID does not match FF, what is FF supposed to do?
- $this->setCertainty(0);
-
- Log::info(sprintf('Could not find budget with ID %d. Will return NULL', $value));
-
- return new Budget;
-
- }
-}
diff --git a/app/Import/Converter/BudgetName.php b/app/Import/Converter/BudgetName.php
deleted file mode 100644
index 5d36a109ac..0000000000
--- a/app/Import/Converter/BudgetName.php
+++ /dev/null
@@ -1,80 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Budget;
- }
-
- /** @var BudgetRepositoryInterface $repository */
- $repository = app(BudgetRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found budget in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $budget = $repository->find(intval($this->mapping[$value]));
- if (!is_null($budget->id)) {
- Log::debug('Found budget by ID', ['id' => $budget->id]);
- $this->setCertainty(100);
-
- return $budget;
- }
- }
-
- // not mapped? Still try to find it first:
- $budget = $repository->findByName($value);
- if (!is_null($budget->id)) {
- Log::debug('Found budget by name ', ['id' => $budget->id]);
- $this->setCertainty(100);
-
- return $budget;
- }
-
- // create new budget. Use a lot of made up values.
- $budget = $repository->store(
- [
- 'name' => $value,
- 'user' => $this->user->id,
- ]
- );
- $this->setCertainty(100);
-
- return $budget;
-
- }
-}
diff --git a/app/Import/Converter/CategoryId.php b/app/Import/Converter/CategoryId.php
deleted file mode 100644
index 4b5cd4e6af..0000000000
--- a/app/Import/Converter/CategoryId.php
+++ /dev/null
@@ -1,76 +0,0 @@
- $value]);
-
- if ($value === 0) {
- $this->setCertainty(0);
-
- return new Category;
- }
-
- /** @var CategoryRepositoryInterface $repository */
- $repository = app(CategoryRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found category in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $category = $repository->find(intval($this->mapping[$value]));
- if (!is_null($category->id)) {
- Log::debug('Found category by ID', ['id' => $category->id]);
- $this->setCertainty(100);
-
- return $category;
- }
- }
-
- // not mapped? Still try to find it first:
- $category = $repository->find($value);
- if (!is_null($category->id)) {
- Log::debug('Found category by ID ', ['id' => $category->id]);
- $this->setCertainty(100);
-
- return $category;
- }
-
- // should not really happen. If the ID does not match FF, what is FF supposed to do?
- $this->setCertainty(0);
-
- Log::info(sprintf('Could not find category with ID %d. Will return NULL', $value));
-
- return new Category;
-
- }
-}
diff --git a/app/Import/Converter/CategoryName.php b/app/Import/Converter/CategoryName.php
deleted file mode 100644
index fcd52413cc..0000000000
--- a/app/Import/Converter/CategoryName.php
+++ /dev/null
@@ -1,80 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Category;
- }
-
- /** @var CategoryRepositoryInterface $repository */
- $repository = app(CategoryRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found category in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $category = $repository->find(intval($this->mapping[$value]));
- if (!is_null($category->id)) {
- Log::debug('Found category by ID', ['id' => $category->id]);
- $this->setCertainty(100);
-
- return $category;
- }
- }
-
- // not mapped? Still try to find it first:
- $category = $repository->findByName($value);
- if (!is_null($category->id)) {
- Log::debug('Found category by name ', ['id' => $category->id]);
- $this->setCertainty(100);
-
- return $category;
- }
-
- // create new category. Use a lot of made up values.
- $category = $repository->store(
- [
- 'name' => $value,
- 'user' => $this->user->id,
- ]
- );
- $this->setCertainty(100);
-
- return $category;
-
- }
-}
diff --git a/app/Import/Converter/ConverterInterface.php b/app/Import/Converter/ConverterInterface.php
index f5c27a2746..010d06e4ca 100644
--- a/app/Import/Converter/ConverterInterface.php
+++ b/app/Import/Converter/ConverterInterface.php
@@ -13,8 +13,6 @@ declare(strict_types=1);
namespace FireflyIII\Import\Converter;
-use FireflyIII\User;
-
/**
* Interface ConverterInterface
*
@@ -27,30 +25,4 @@ interface ConverterInterface
*
*/
public function convert($value);
-
- /**
- * @return int
- */
- public function getCertainty(): int;
-
- /**
- * @param array $config
- */
- public function setConfig(array $config);
-
- /**
- * @param bool $doMap
- */
- public function setDoMap(bool $doMap);
-
- /**
- * @param array $mapping
- *
- */
- public function setMapping(array $mapping);
-
- /**
- * @param User $user
- */
- public function setUser(User $user);
}
diff --git a/app/Import/Converter/CurrencyCode.php b/app/Import/Converter/CurrencyCode.php
deleted file mode 100644
index 1afa778292..0000000000
--- a/app/Import/Converter/CurrencyCode.php
+++ /dev/null
@@ -1,71 +0,0 @@
- $value]);
-
- /** @var CurrencyRepositoryInterface $repository */
- $repository = app(CurrencyRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $currency = $repository->find(intval($this->mapping[$value]));
- if (!is_null($currency->id)) {
- Log::debug('Found currency by ID', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
- }
-
- // not mapped? Still try to find it first:
- $currency = $repository->findByCode($value);
- if (!is_null($currency->id)) {
- Log::debug('Found currency by code', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
- $currency = $repository->store(
- [
- 'name' => $value,
- 'code' => $value,
- 'symbol' => $value,
- ]
- );
- $this->setCertainty(100);
-
- return $currency;
- }
-}
diff --git a/app/Import/Converter/CurrencyId.php b/app/Import/Converter/CurrencyId.php
deleted file mode 100644
index d3b74da000..0000000000
--- a/app/Import/Converter/CurrencyId.php
+++ /dev/null
@@ -1,75 +0,0 @@
- $value]);
-
- if ($value === 0) {
- $this->setCertainty(0);
-
- return new TransactionCurrency;
- }
-
- /** @var CurrencyRepositoryInterface $repository */
- $repository = app(CurrencyRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $currency = $repository->find(intval($this->mapping[$value]));
- if (!is_null($currency->id)) {
- Log::debug('Found currency by ID', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
- }
-
- // not mapped? Still try to find it first:
- $currency = $repository->find($value);
- if (!is_null($currency->id)) {
- Log::debug('Found currency by ID ', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
- $this->setCertainty(0);
- // should not really happen. If the ID does not match FF, what is FF supposed to do?
-
- Log::info(sprintf('Could not find category with ID %d. Will return NULL', $value));
-
- return new TransactionCurrency;
-
- }
-}
diff --git a/app/Import/Converter/CurrencyName.php b/app/Import/Converter/CurrencyName.php
deleted file mode 100644
index f68ec043a1..0000000000
--- a/app/Import/Converter/CurrencyName.php
+++ /dev/null
@@ -1,81 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new TransactionCurrency;
- }
-
- /** @var CurrencyRepositoryInterface $repository */
- $repository = app(CurrencyRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $currency = $repository->find(intval($this->mapping[$value]));
- if (!is_null($currency->id)) {
- Log::debug('Found currency by ID', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
- }
-
- // not mapped? Still try to find it first:
- $currency = $repository->findByName($value);
- if (!is_null($currency->id)) {
- Log::debug('Found currency by name ', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
-
- // create new currency
- $currency = $repository->store(
- [
- 'name' => $value,
- 'code' => strtoupper(substr($value, 0, 3)),
- 'symbol' => strtoupper(substr($value, 0, 1)),
- ]
- );
- $this->setCertainty(100);
-
- return $currency;
-
- }
-}
diff --git a/app/Import/Converter/CurrencySymbol.php b/app/Import/Converter/CurrencySymbol.php
deleted file mode 100644
index a40b06af40..0000000000
--- a/app/Import/Converter/CurrencySymbol.php
+++ /dev/null
@@ -1,81 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new TransactionCurrency;
- }
-
- /** @var CurrencyRepositoryInterface $repository */
- $repository = app(CurrencyRepositoryInterface::class);
- $repository->setUser($this->user);
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $currency = $repository->find(intval($this->mapping[$value]));
- if (!is_null($currency->id)) {
- Log::debug('Found currency by ID', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
- }
-
- // not mapped? Still try to find it first:
- $currency = $repository->findBySymbol($value);
- if (!is_null($currency->id)) {
- Log::debug('Found currency by symbol ', ['id' => $currency->id]);
- $this->setCertainty(100);
-
- return $currency;
- }
-
- // create new currency
- $currency = $repository->store(
- [
- 'name' => 'Currency ' . $value,
- 'code' => $value,
- 'symbol' => $value,
- ]
- );
- $this->setCertainty(100);
-
- return $currency;
-
- }
-}
diff --git a/app/Import/Converter/Date.php b/app/Import/Converter/Date.php
deleted file mode 100644
index b799aed9e7..0000000000
--- a/app/Import/Converter/Date.php
+++ /dev/null
@@ -1,53 +0,0 @@
- $value]);
- Log::debug('Format: ', ['format' => $this->config['date-format']]);
- try {
- $date = Carbon::createFromFormat($this->config['date-format'], $value);
- } catch (InvalidArgumentException $e) {
- Log::info($e->getMessage());
- Log::info('Cannot convert this string using the given format.', ['value' => $value, 'format' => $this->config['date-format']]);
- $this->setCertainty(0);
-
- return new Carbon;
- }
- Log::debug('Converted date', ['converted' => $date->toAtomString()]);
- $this->setCertainty(100);
-
- return $date;
- }
-}
diff --git a/app/Import/Converter/Description.php b/app/Import/Converter/Description.php
deleted file mode 100644
index 9eb507acb8..0000000000
--- a/app/Import/Converter/Description.php
+++ /dev/null
@@ -1,39 +0,0 @@
-setCertainty(100);
-
- return strval($value);
-
- }
-}
diff --git a/app/Import/Converter/ExternalId.php b/app/Import/Converter/ExternalId.php
deleted file mode 100644
index feb2e8c3d1..0000000000
--- a/app/Import/Converter/ExternalId.php
+++ /dev/null
@@ -1,39 +0,0 @@
-setCertainty(100);
-
- return strval(trim($value));
-
- }
-}
diff --git a/app/Import/Converter/INGDebetCredit.php b/app/Import/Converter/INGDebetCredit.php
index 80650257ae..7fa82f2cf6 100644
--- a/app/Import/Converter/INGDebetCredit.php
+++ b/app/Import/Converter/INGDebetCredit.php
@@ -20,7 +20,7 @@ use Log;
*
* @package FireflyIII\Import\Converter
*/
-class INGDebetCredit extends BasicConverter implements ConverterInterface
+class INGDebetCredit implements ConverterInterface
{
/**
@@ -34,12 +34,10 @@ class INGDebetCredit extends BasicConverter implements ConverterInterface
if ($value === 'Af') {
Log::debug('Return -1');
- $this->setCertainty(100);
return -1;
}
- $this->setCertainty(100);
Log::debug('Return 1');
return 1;
diff --git a/app/Import/Converter/Ignore.php b/app/Import/Converter/Ignore.php
deleted file mode 100644
index ac619bac3a..0000000000
--- a/app/Import/Converter/Ignore.php
+++ /dev/null
@@ -1,34 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Account;
- }
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $account = $repository->find(intval($this->mapping[$value]));
- if (!is_null($account->id)) {
- Log::debug('Found account by ID', ['id' => $account->id]);
- $this->setCertainty(100);
-
- return $account;
- }
- }
-
- // not mapped? Still try to find it first:
- $account = $repository->findByIban($value, []);
- if (!is_null($account->id)) {
- Log::debug('Found account by IBAN', ['id' => $account->id]);
- Log::info(
- 'The match between IBAN and account is uncertain because the type of transactions may not have been determined.',
- ['id' => $account->id, 'iban' => $value]
- );
- $this->setCertainty(50);
-
- return $account;
- }
-
- // 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/Converter/OpposingAccountName.php b/app/Import/Converter/OpposingAccountName.php
deleted file mode 100644
index 90a959408c..0000000000
--- a/app/Import/Converter/OpposingAccountName.php
+++ /dev/null
@@ -1,89 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Account;
- }
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $account = $repository->find(intval($this->mapping[$value]));
- if (!is_null($account->id)) {
- Log::debug('Found account by ID', ['id' => $account->id]);
- $this->setCertainty(100);
-
- return $account;
- }
- }
-
- // not mapped? Still try to find it first:
- $account = $repository->findByName($value, []);
- if (!is_null($account->id)) {
- Log::debug('Found opposing account by name', ['id' => $account->id]);
- Log::info(
- 'The match between name and account is uncertain because the type of transactions may not have been determined.',
- ['id' => $account->id, 'name' => $value]
- );
- $this->setCertainty(50);
-
- return $account;
- }
-
- $account = $repository->store(
- ['name' => $value, 'iban' => null, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true,
- 'openingBalance' => 0,
- ]
- );
- if (is_null($account->id)) {
- $this->setCertainty(0);
-
- return new Account;
- }
- $this->setCertainty(100);
-
- Log::debug('Created new opposing account ', ['name' => $account->name, 'id' => $account->id]);
-
- return $account;
- }
-}
diff --git a/app/Import/Converter/OpposingAccountNumber.php b/app/Import/Converter/OpposingAccountNumber.php
deleted file mode 100644
index 8ede15ed85..0000000000
--- a/app/Import/Converter/OpposingAccountNumber.php
+++ /dev/null
@@ -1,91 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Account;
- }
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
-
- if (isset($this->mapping[$value])) {
- Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]);
- $account = $repository->find(intval($this->mapping[$value]));
- if (!is_null($account->id)) {
- Log::debug('Found account by ID', ['id' => $account->id]);
- $this->setCertainty(100);
-
- return $account;
- }
- }
-
- // not mapped? Still try to find it first:
- $account = $repository->findByAccountNumber($value, []);
- if (!is_null($account->id)) {
- Log::debug('Found account by number', ['id' => $account->id]);
- $this->setCertainty(50);
-
- return $account;
- }
-
- // try to find by the name we would give it:
- $accountName = 'Import account with number ' . e($value);
- $account = $repository->findByName($accountName, [AccountType::IMPORT]);
- if (!is_null($account->id)) {
- Log::debug('Found account by name', ['id' => $account->id]);
- $this->setCertainty(50);
-
- return $account;
- }
-
-
- $account = $repository->store(
- ['name' => $accountName, 'openingBalance' => 0, 'iban' => null, 'user' => $this->user->id,
- 'accountType' => 'import',
- 'virtualBalance' => 0, 'accountNumber' => $value, 'active' => true]
- );
- $this->setCertainty(100);
-
- return $account;
-
- }
-}
diff --git a/app/Import/Converter/RabobankDebetCredit.php b/app/Import/Converter/RabobankDebetCredit.php
index 9b3e89314d..cef1a55607 100644
--- a/app/Import/Converter/RabobankDebetCredit.php
+++ b/app/Import/Converter/RabobankDebetCredit.php
@@ -20,7 +20,7 @@ use Log;
*
* @package FireflyIII\Import\Converter
*/
-class RabobankDebetCredit extends BasicConverter implements ConverterInterface
+class RabobankDebetCredit implements ConverterInterface
{
/**
@@ -34,13 +34,11 @@ class RabobankDebetCredit extends BasicConverter implements ConverterInterface
if ($value === 'D') {
Log::debug('Return -1');
- $this->setCertainty(100);
return -1;
}
Log::debug('Return 1');
- $this->setCertainty(100);
return 1;
}
diff --git a/app/Import/Converter/TagSplit.php b/app/Import/Converter/TagSplit.php
deleted file mode 100644
index f5ebd034af..0000000000
--- a/app/Import/Converter/TagSplit.php
+++ /dev/null
@@ -1,86 +0,0 @@
-setUser($user);
-
-
- /** @var string $part */
- foreach ($parts as $part) {
- if (isset($mapping[$part])) {
- Log::debug('Found tag in mapping. Should exist.', ['value' => $part, 'map' => $mapping[$part]]);
- $tag = $repository->find(intval($mapping[$part]));
- if (!is_null($tag->id)) {
- Log::debug('Found tag by ID', ['id' => $tag->id]);
-
- $set->push($tag);
- continue;
- }
- }
- // not mapped? Still try to find it first:
- $tag = $repository->findByTag($part);
- if (!is_null($tag->id)) {
- Log::debug('Found tag by name ', ['id' => $tag->id]);
-
- $set->push($tag);
- }
- if (is_null($tag->id)) {
- // create new tag
- $tag = $repository->store(
- [
- 'tag' => $part,
- 'date' => null,
- 'description' => $part,
- 'latitude' => null,
- 'longitude' => null,
- 'zoomLevel' => null,
- 'tagMode' => 'nothing',
- ]
- );
- Log::debug('Created new tag', ['name' => $part, 'id' => $tag->id]);
- $set->push($tag);
- }
- }
-
- return $set;
- }
-
-}
diff --git a/app/Import/Converter/TagsComma.php b/app/Import/Converter/TagsComma.php
deleted file mode 100644
index 93e13698f0..0000000000
--- a/app/Import/Converter/TagsComma.php
+++ /dev/null
@@ -1,48 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Collection;
- }
- $parts = array_unique(explode(',', $value));
- $set = TagSplit::createSetFromSplits($this->user, $this->mapping, $parts);
- $this->setCertainty(100);
-
- return $set;
- }
-}
diff --git a/app/Import/Converter/TagsSpace.php b/app/Import/Converter/TagsSpace.php
deleted file mode 100644
index ae9635a8b7..0000000000
--- a/app/Import/Converter/TagsSpace.php
+++ /dev/null
@@ -1,49 +0,0 @@
- $value]);
-
- if (strlen($value) === 0) {
- $this->setCertainty(0);
-
- return new Collection;
- }
- $parts = array_unique(explode(' ', $value));
- $set = TagSplit::createSetFromSplits($this->user, $this->mapping, $parts);
- $this->setCertainty(100);
-
- return $set;
-
- }
-}
diff --git a/app/Import/FileProcessor/CsvProcessor.php b/app/Import/FileProcessor/CsvProcessor.php
new file mode 100644
index 0000000000..5be25f5ed1
--- /dev/null
+++ b/app/Import/FileProcessor/CsvProcessor.php
@@ -0,0 +1,235 @@
+objects = new Collection;
+ $this->validSpecifics = array_keys(config('csv.import_specifics'));
+ $this->validConverters = array_keys(config('csv.import_roles'));
+ }
+
+ /**
+ * @return Collection
+ */
+ public function getObjects(): Collection
+ {
+ return $this->objects;
+ }
+
+ /**
+ * Does the actual job:
+ *
+ * @return bool
+ */
+ public function run(): bool
+ {
+ Log::debug('Now in CsvProcessor run(). Job is now running...');
+
+ $entries = $this->getImportArray();
+ $index = 0;
+ Log::notice('Building importable objects from CSV file.');
+ foreach ($entries as $index => $row) {
+ // verify if not exists already:
+ if ($this->rowAlreadyImported($row)) {
+ $message = sprintf('Row #%d has already been imported.', $index);
+ $this->job->addError($index, $message);
+ $this->job->addStepsDone(5); // all steps.
+ Log::info($message);
+ continue;
+ }
+ $this->objects->push($this->importRow($index, $row));
+ $this->job->addStepsDone(1);
+ }
+ // if job has no step count, set it now:
+ $extended = $this->job->extended_status;
+ if ($extended['steps'] === 0) {
+ $extended['steps'] = $index * 5;
+ $this->job->extended_status = $extended;
+ $this->job->save();
+ }
+
+
+ return true;
+ }
+
+ /**
+ * @param ImportJob $job
+ *
+ * @return FileProcessorInterface
+ */
+ public function setJob(ImportJob $job): FileProcessorInterface
+ {
+ $this->job = $job;
+
+ return $this;
+ }
+
+ /**
+ * Add meta data to the individual value and verify that it can be handled in a later stage.
+ *
+ * @param int $index
+ * @param string $value
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ private function annotateValue(int $index, string $value)
+ {
+ $value = trim($value);
+ $config = $this->job->configuration;
+ $role = $config['column-roles'][$index] ?? '_ignore';
+ $mapped = $config['column-mapping-config'][$index][$value] ?? null;
+
+ // throw error when not a valid converter.
+ if (!in_array($role, $this->validConverters)) {
+ throw new FireflyException(sprintf('"%s" is not a valid role.', $role));
+ }
+
+ $entry = [
+ 'role' => $role,
+ 'value' => $value,
+ 'mapped' => $mapped,
+ ];
+
+ return $entry;
+ }
+
+ /**
+ * @return Iterator
+ */
+ private function getImportArray(): Iterator
+ {
+ $content = $this->job->uploadFileContents();
+ $config = $this->job->configuration;
+ $reader = Reader::createFromString($content);
+ $reader->setDelimiter($config['delimiter']);
+ $start = $config['has-headers'] ? 1 : 0;
+ $results = $reader->setOffset($start)->fetch();
+ Log::debug(sprintf('Created a CSV reader starting at offset %d', $start));
+
+ return $results;
+ }
+
+ /**
+ * Take a row, build import journal by annotating each value and storing it in the import journal.
+ *
+ * @param int $index
+ * @param array $row
+ *
+ * @return ImportJournal
+ */
+ private function importRow(int $index, array $row): ImportJournal
+ {
+ Log::debug(sprintf('Now at row %d', $index));
+ $row = $this->specifics($row);
+ $journal = new ImportJournal;
+ $journal->setUser($this->job->user);
+ $journal->setHash(hash('sha256', json_encode($row)));
+
+ foreach ($row as $rowIndex => $value) {
+ $value = trim($value);
+ if (strlen($value) > 0) {
+ $annotated = $this->annotateValue($rowIndex, $value);
+ Log::debug('Annotated value', $annotated);
+ $journal->setValue($annotated);
+ }
+ }
+ Log::debug('ImportJournal complete, returning.');
+
+ return $journal;
+ }
+
+ /**
+ * Checks if the row has not been imported before.
+ *
+ * @param array $array
+ *
+ * @return bool
+ */
+ private function rowAlreadyImported(array $array): bool
+ {
+ $string = json_encode($array);
+ $hash = hash('sha256', json_encode($string));
+ $json = json_encode($hash);
+ $entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
+ ->where('data', $json)
+ ->where('name', 'importHash')
+ ->first();
+ if (!is_null($entry)) {
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * And this is the point where the specifix go to work.
+ *
+ * @param array $row
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ private function specifics(array $row): array
+ {
+ $config = $this->job->configuration;
+ //
+ foreach ($config['specifics'] as $name => $enabled) {
+
+ if (!in_array($name, $this->validSpecifics)) {
+ throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
+ }
+
+ /** @var SpecificInterface $specific */
+ $specific = app('FireflyIII\Import\Specifics\\' . $name);
+
+ // it returns the row, possibly modified:
+ $row = $specific->run($row);
+ }
+
+ return $row;
+
+ }
+}
diff --git a/app/Import/FileProcessor/FileProcessorInterface.php b/app/Import/FileProcessor/FileProcessorInterface.php
new file mode 100644
index 0000000000..0813246750
--- /dev/null
+++ b/app/Import/FileProcessor/FileProcessorInterface.php
@@ -0,0 +1,42 @@
+validFields as $value) {
- $this->fields[$value] = null;
- $this->certain[$value] = 0;
- }
- $this->errors = new Collection;
-
- }
-
- /**
- * @param string $role
- * @param int $certainty
- * @param $convertedValue
- *
- * @throws FireflyException
- */
- public function importValue(string $role, int $certainty, $convertedValue)
- {
- switch ($role) {
- default:
- Log::error('Import entry cannot handle object.', ['role' => $role]);
- throw new FireflyException('Import entry cannot handle object of type "' . $role . '".');
- case 'hash':
- $this->hash = $convertedValue;
-
- return;
- case 'amount':
- /*
- * Easy enough.
- */
- $this->setFloat('amount', $convertedValue, $certainty);
- $this->applyMultiplier('amount'); // if present.
-
- return;
- case 'account-id':
- case 'account-iban':
- case 'account-name':
- case 'account-number':
- $this->setObject('asset-account', $convertedValue, $certainty);
- break;
- case 'opposing-number':
- case 'opposing-iban':
- case 'opposing-id':
- case 'opposing-name':
- $this->setObject('opposing-account', $convertedValue, $certainty);
- break;
- case 'bill-id':
- case 'bill-name':
- $this->setObject('bill', $convertedValue, $certainty);
- break;
- case 'budget-id':
- case 'budget-name':
- $this->setObject('budget', $convertedValue, $certainty);
- break;
- case 'category-id':
- case 'category-name':
- $this->setObject('category', $convertedValue, $certainty);
- break;
- case 'currency-code':
- case 'currency-id':
- case 'currency-name':
- case 'currency-symbol':
- $this->setObject('currency', $convertedValue, $certainty);
- break;
- case 'date-transaction':
- $this->setDate('date-transaction', $convertedValue, $certainty);
- break;
-
- case 'date-interest':
- $this->setDate('date-interest', $convertedValue, $certainty);
- break;
- case 'date-book':
- $this->setDate('date-book', $convertedValue, $certainty);
- break;
- case 'date-process':
- $this->setDate('date-process', $convertedValue, $certainty);
- break;
- case 'sepa-ct-id':
- case 'sepa-db':
- case 'sepa-ct-op':
- case 'description':
- $this->setAppendableString('description', $convertedValue);
- break;
- case '_ignore':
- break;
- case 'ing-debet-credit':
- case 'rabo-debet-credit':
- $this->manipulateFloat('amount', 'multiply', $convertedValue);
- $this->applyMultiplier('amount'); // if present.
- break;
- case 'tags-comma':
- case 'tags-space':
- $this->appendCollection('tags', $convertedValue);
- break;
- case 'external-id':
- $this->externalID = $convertedValue;
- break;
-
- }
- }
-
- /**
- * @param User $user
- */
- public function setUser(User $user)
- {
- $this->user = $user;
- }
-
- /**
- * @param string $field
- * @param Collection $convertedValue
- */
- private function appendCollection(string $field, Collection $convertedValue)
- {
- if (is_null($this->fields[$field])) {
- $this->fields[$field] = new Collection;
- }
- $this->fields[$field] = $this->fields[$field]->merge($convertedValue);
- }
-
- /**
- * @param string $field
- */
- private function applyMultiplier(string $field)
- {
- if ($this->fields[$field] != 0 && $this->amountMultiplier != 0) {
- $this->fields[$field] = $this->fields[$field] * $this->amountMultiplier;
- }
- }
-
- /**
- * @param string $field
- * @param string $action
- * @param $convertedValue
- *
- * @throws FireflyException
- */
- private function manipulateFloat(string $field, string $action, $convertedValue)
- {
- switch ($action) {
- default:
- Log::error('Cannot handle manipulateFloat', ['field' => $field, 'action' => $action]);
- throw new FireflyException('Cannot manipulateFloat with action ' . $action);
- case 'multiply':
- $this->amountMultiplier = $convertedValue;
- break;
- }
- }
-
- /**
- * @param string $field
- * @param string $value
- */
- private function setAppendableString(string $field, string $value)
- {
- $value = trim($value);
- $this->fields[$field] .= ' ' . $value;
- }
-
- /**
- * @param string $field
- * @param Carbon $date
- * @param int $certainty
- */
- private function setDate(string $field, Carbon $date, int $certainty)
- {
- if ($certainty > $this->certain[$field] && !is_null($date)) {
- Log::debug(sprintf('ImportEntry: %s is now %s with certainty %d', $field, $date->format('Y-m-d'), $certainty));
- $this->fields[$field] = $date;
- $this->certain[$field] = $certainty;
-
- return;
- }
- Log::info(sprintf('Will not set %s based on certainty %d (current certainty is %d) or NULL id.', $field, $certainty, $this->certain[$field]));
-
- }
-
- /**
- * @param string $field
- * @param float $value
- * @param int $certainty
- */
- private function setFloat(string $field, float $value, int $certainty)
- {
- if ($certainty > $this->certain[$field]) {
- Log::debug(sprintf('ImportEntry: %s is now %f with certainty %d', $field, $value, $certainty));
- $this->fields[$field] = $value;
- $this->certain[$field] = $certainty;
-
- return;
- }
- Log::info(sprintf('Will not set %s based on certainty %d (current certainty is %d).', $field, $certainty, $this->certain[$field]));
- }
-
- /**
- * @param string $field
- * @param $object
- * @param int $certainty
- */
- private function setObject(string $field, $object, int $certainty)
- {
- if ($certainty > $this->certain[$field] && !is_null($object->id)) {
- Log::debug(sprintf('ImportEntry: %s ID is now %d with certainty %d', $field, $object->id, $certainty));
- $this->fields[$field] = $object;
- $this->certain[$field] = $certainty;
-
- return;
- }
- Log::info(sprintf('Will not set %s based on certainty %d (current certainty is %d) or NULL id.', $field, $certainty, $this->certain[$field]));
-
- }
-}
diff --git a/app/Import/ImportProcedure.php b/app/Import/ImportProcedure.php
deleted file mode 100644
index 9150010dae..0000000000
--- a/app/Import/ImportProcedure.php
+++ /dev/null
@@ -1,85 +0,0 @@
-status = 'import_running';
- $job->save();
-
- // create Importer
- $valid = array_keys(config('firefly.import_formats'));
- $class = 'INVALID';
- if (in_array($job->file_type, $valid)) {
- $class = config('firefly.import_formats.' . $job->file_type);
- }
-
- /** @var ImporterInterface $importer */
- $importer = app($class);
- $importer->setJob($job);
-
- // create import entries
- $collection = $importer->createImportEntries();
-
- // validate / clean collection:
- $validator = new ImportValidator($collection);
- $validator->setUser($job->user);
- $validator->setJob($job);
- if ($job->configuration['import-account'] != 0) {
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($job->user);
- $validator->setDefaultImportAccount($repository->find($job->configuration['import-account']));
- }
-
- $cleaned = $validator->clean();
-
- // then import collection:
- $storage = new ImportStorage($job->user, $cleaned);
- $storage->setJob($job);
-
- // and run store routine:
- $result = $storage->store();
-
- // grab import tag:
- $status = $job->extended_status;
- $status['importTag'] = $storage->importTag->id;
- $job->extended_status = $status;
- $job->status = 'import_complete';
- $job->save();
-
- return $result;
- }
-
-}
diff --git a/app/Import/ImportProcedureInterface.php b/app/Import/ImportProcedureInterface.php
deleted file mode 100644
index ca2c6ff5da..0000000000
--- a/app/Import/ImportProcedureInterface.php
+++ /dev/null
@@ -1,33 +0,0 @@
-entries = $entries;
- $this->user = $user;
- $this->rules = $this->getUserRules();
-
- }
-
- /**
- * @param ImportJob $job
- */
- public function setJob(ImportJob $job)
- {
- $this->job = $job;
- }
-
- /**
- * @param User $user
- */
- public function setUser(User $user)
- {
- $this->user = $user;
- }
-
- /**
- * @return Collection
- */
- public function store(): Collection
- {
- // create a tag to join the transactions.
- $this->importTag = $this->createImportTag();
- $collection = new Collection;
- Log::notice(sprintf('Started storing %d entry(ies).', $this->entries->count()));
- foreach ($this->entries as $index => $entry) {
- Log::debug(sprintf('--- import store start for row %d ---', $index));
-
- // store entry:
- $journal = $this->storeSingle($index, $entry);
- $this->job->addStepsDone(1);
-
- // apply rules:
- $this->applyRules($journal);
- $this->job->addStepsDone(1);
-
- $collection->put($index, $journal);
- }
- Log::notice(sprintf('Finished storing %d entry(ies).', $collection->count()));
-
- return $collection;
- }
-
- /**
- * @param string $hash
- *
- * @return TransactionJournal
- */
- private function alreadyImported(string $hash): TransactionJournal
- {
-
- $meta = TransactionJournalMeta
- ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
- ->where('journal_meta.name', 'originalImportHash')
- ->where('transaction_journals.user_id', $this->user->id)
- ->where('journal_meta.data', json_encode($hash))->first(['journal_meta.*']);
- if (!is_null($meta)) {
- /** @var TransactionJournal $journal */
- $journal = $meta->transactionjournal;
- if (intval($journal->completed) === 1) {
- return $journal;
- }
- }
-
- return new TransactionJournal;
- }
-
- /**
- * @param TransactionJournal $journal
- *
- * @return bool
- */
- private function applyRules(TransactionJournal $journal): bool
- {
- if ($this->rules->count() > 0) {
-
- /** @var Rule $rule */
- foreach ($this->rules as $rule) {
- Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id));
- $processor = Processor::make($rule);
- $processor->handleTransactionJournal($journal);
-
- if ($rule->stop_processing) {
- return true;
- }
- }
- }
-
- return true;
- }
-
- /**
- * @return Tag
- */
- private function createImportTag(): Tag
- {
- /** @var TagRepositoryInterface $repository */
- $repository = app(TagRepositoryInterface::class);
- $repository->setUser($this->user);
- $data = [
- 'tag' => trans('firefly.import_with_key', ['key' => $this->job->key]),
- 'date' => new Carbon,
- 'description' => null,
- 'latitude' => null,
- 'longitude' => null,
- 'zoomLevel' => null,
- 'tagMode' => 'nothing',
- ];
- $tag = $repository->store($data);
-
- return $tag;
- }
-
- /**
- * @return Collection
- */
- private function getUserRules(): Collection
- {
- $set = Rule::distinct()
- ->where('rules.user_id', $this->user->id)
- ->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
- ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
- ->where('rule_groups.active', 1)
- ->where('rule_triggers.trigger_type', 'user_action')
- ->where('rule_triggers.trigger_value', 'store-journal')
- ->where('rules.active', 1)
- ->orderBy('rule_groups.order', 'ASC')
- ->orderBy('rules.order', 'ASC')
- ->get(['rules.*', 'rule_groups.order']);
- Log::debug(sprintf('Found %d user rules.', $set->count()));
-
- return $set;
-
- }
-
- /**
- * @param $entry
- *
- * @return array
- * @throws FireflyException
- */
- private function storeAccounts($entry): array
- {
- // then create transactions. Single ones, unfortunately.
- switch ($entry->fields['transaction-type']->type) {
- default:
- throw new FireflyException('ImportStorage cannot handle ' . $entry->fields['transaction-type']->type);
- case TransactionType::WITHDRAWAL:
- $source = $entry->fields['asset-account'];
- $destination = $entry->fields['opposing-account'];
- // make amount positive, if it is not.
- break;
- case TransactionType::DEPOSIT:
- $source = $entry->fields['opposing-account'];
- $destination = $entry->fields['asset-account'];
- break;
- case TransactionType::TRANSFER:
- // depends on amount:
- if ($entry->fields['amount'] < 0) {
- $source = $entry->fields['asset-account'];
- $destination = $entry->fields['opposing-account'];
- break;
- }
- $destination = $entry->fields['asset-account'];
- $source = $entry->fields['opposing-account'];
- break;
- }
-
- return [
- 'source' => $source,
- 'destination' => $destination,
- ];
- }
-
- /**
- * @param TransactionJournal $journal
- * @param ImportEntry $entry
- */
- private function storeBill(TransactionJournal $journal, ImportEntry $entry)
- {
-
- if (!is_null($entry->fields['bill']) && !is_null($entry->fields['bill']->id)) {
- $journal->bill()->associate($entry->fields['bill']);
- Log::debug('Attached bill', ['id' => $entry->fields['bill']->id, 'name' => $entry->fields['bill']->name]);
- $journal->save();
- }
- }
-
- /**
- * @param TransactionJournal $journal
- * @param ImportEntry $entry
- */
- private function storeBudget(TransactionJournal $journal, ImportEntry $entry)
- {
- if (!is_null($entry->fields['budget']) && !is_null($entry->fields['budget']->id)) {
- $journal->budgets()->save($entry->fields['budget']);
- Log::debug('Attached budget', ['id' => $entry->fields['budget']->id, 'name' => $entry->fields['budget']->name]);
- $journal->save();
- }
-
- }
-
- /**
- * @param TransactionJournal $journal
- * @param ImportEntry $entry
- */
- private function storeCategory(TransactionJournal $journal, ImportEntry $entry)
- {
- if (!is_null($entry->fields['category']) && !is_null($entry->fields['category']->id)) {
- $journal->categories()->save($entry->fields['category']);
- Log::debug('Attached category', ['id' => $entry->fields['category']->id, 'name' => $entry->fields['category']->name]);
- $journal->save();
- }
- }
-
- /**
- * @param $entry
- *
- * @return TransactionJournal
- */
- private function storeJournal($entry): TransactionJournal
- {
-
- $billId = is_null($entry->fields['bill']) || intval($entry->fields['bill']->id) === 0 ? null : intval($entry->fields['bill']->id);
- $journalData = [
- 'user_id' => $entry->user->id,
- 'transaction_type_id' => $entry->fields['transaction-type']->id,
- 'bill_id' => $billId,
- 'transaction_currency_id' => $entry->fields['currency']->id,
- 'description' => $entry->fields['description'],
- 'date' => $entry->fields['date-transaction'],
- 'interest_date' => $entry->fields['date-interest'],
- 'book_date' => $entry->fields['date-book'],
- 'process_date' => $entry->fields['date-process'],
- 'completed' => 0,
- ];
- /** @var TransactionJournal $journal */
- $journal = TransactionJournal::create($journalData);
-
- foreach ($journal->getErrors()->all() as $err) {
- Log::error('Error when storing journal: ' . $err);
- }
- Log::debug('Created journal', ['id' => $journal->id]);
-
- // save hash as meta value:
- $meta = new TransactionJournalMeta;
- $meta->name = 'originalImportHash';
- $meta->data = $entry->hash;
- $meta->transactionJournal()->associate($journal);
- $meta->save();
-
- return $journal;
- }
-
- /**
- * @param int $index
- * @param ImportEntry $entry
- *
- * @return TransactionJournal
- * @throws FireflyException
- */
- private function storeSingle(int $index, ImportEntry $entry): TransactionJournal
- {
- if ($entry->valid === false) {
- Log::warning(sprintf('Cannot import row %d, because the entry is not valid.', $index));
- $errors = join(', ', $entry->errors->all());
- $errorText = sprintf('Row #%d: ' . $errors, $index);
- $extendedStatus = $this->job->extended_status;
- $extendedStatus['errors'][] = $errorText;
- $this->job->extended_status = $extendedStatus;
- $this->job->save();
-
- return new TransactionJournal;
- }
- $alreadyImported = $this->alreadyImported($entry->hash);
- if (!is_null($alreadyImported->id)) {
- Log::warning(sprintf('Cannot import row %d, because it has already been imported (journal #%d).', $index, $alreadyImported->id));
- $errorText = trans(
- 'firefly.import_double',
- ['row' => $index, 'link' => route('transactions.show', [$alreadyImported->id]), 'description' => $alreadyImported->description]
- );
- $extendedStatus = $this->job->extended_status;
- $extendedStatus['errors'][] = $errorText;
- $this->job->extended_status = $extendedStatus;
- $this->job->save();
-
- return new TransactionJournal;
- }
-
- Log::debug(sprintf('Going to store row %d', $index));
-
-
- $journal = $this->storeJournal($entry);
- $amount = Steam::positive($entry->fields['amount']);
- $accounts = $this->storeAccounts($entry);
-
- // create new transactions. This is something that needs a rewrite for multiple/split transactions.
- $sourceData = [
- 'account_id' => $accounts['source']->id,
- 'transaction_journal_id' => $journal->id,
- 'description' => null,
- 'amount' => bcmul($amount, '-1'),
- ];
-
- $destinationData = [
- 'account_id' => $accounts['destination']->id,
- 'transaction_journal_id' => $journal->id,
- 'description' => null,
- 'amount' => $amount,
- ];
-
- $one = Transaction::create($sourceData);
- $two = Transaction::create($destinationData);
- $error = false;
- if (is_null($one->id)) {
- Log::error('Could not create transaction 1.', $one->getErrors()->all());
- $error = true;
- }
-
- if (is_null($two->id)) {
- Log::error('Could not create transaction 1.', $two->getErrors()->all());
- $error = true;
- }
-
- // respond to error
- if ($error === true) {
- $errorText = sprintf('Cannot import row %d, because an error occured when storing data.', $index);
- Log::error($errorText);
- $extendedStatus = $this->job->extended_status;
- $extendedStatus['errors'][] = $errorText;
- $this->job->extended_status = $extendedStatus;
- $this->job->save();
-
- return new TransactionJournal;
- }
-
- Log::debug('Created transaction 1', ['id' => $one->id, 'account' => $one->account_id, 'account_name' => $accounts['source']->name]);
- Log::debug('Created transaction 2', ['id' => $two->id, 'account' => $two->account_id, 'account_name' => $accounts['destination']->name]);
-
- $journal->completed = 1;
- $journal->save();
-
- // attach import tag.
- $journal->tags()->save($this->importTag);
-
- // now attach budget and so on.
- $this->storeBudget($journal, $entry);
- $this->storeCategory($journal, $entry);
- $this->storeBill($journal, $entry);
-
- return $journal;
- }
-}
diff --git a/app/Import/ImportValidator.php b/app/Import/ImportValidator.php
deleted file mode 100644
index 3a3a373b2b..0000000000
--- a/app/Import/ImportValidator.php
+++ /dev/null
@@ -1,438 +0,0 @@
-entries = $entries;
- }
-
- /**
- * Clean collection by filling in all the blanks.
- */
- public function clean(): Collection
- {
- Log::notice(sprintf('Started validating %d entry(ies).', $this->entries->count()));
- $newCollection = new Collection;
- /** @var ImportEntry $entry */
- foreach ($this->entries as $index => $entry) {
- Log::debug(sprintf('--- import validator start for row %d ---', $index));
- /*
- * X Adds the date (today) if no date is present.
- * X Determins the types of accounts involved (asset, expense, revenue).
- * X Determins the type of transaction (withdrawal, deposit, transfer).
- * - Determins the currency of the transaction.
- * X Adds a default description if there isn't one present.
- */
- $entry = $this->checkAmount($entry);
- $entry = $this->setDate($entry);
- $entry = $this->setAssetAccount($entry);
- $entry = $this->setOpposingAccount($entry);
- $entry = $this->cleanDescription($entry);
- $entry = $this->setTransactionType($entry);
- $entry = $this->setTransactionCurrency($entry);
-
- $newCollection->put($index, $entry);
- $this->job->addStepsDone(1);
- }
- Log::notice(sprintf('Finished validating %d entry(ies).', $newCollection->count()));
-
- return $newCollection;
- }
-
- /**
- * @param Account $defaultImportAccount
- */
- public function setDefaultImportAccount(Account $defaultImportAccount)
- {
- $this->defaultImportAccount = $defaultImportAccount;
- }
-
- /**
- * @param ImportJob $job
- */
- public function setJob(ImportJob $job)
- {
- $this->job = $job;
- }
-
- /**
- * @param User $user
- */
- public function setUser(User $user)
- {
- $this->user = $user;
- }
-
- /**
- * @param ImportEntry $entry
- *
- * @return ImportEntry
- */
- private function checkAmount(ImportEntry $entry): ImportEntry
- {
- if ($entry->fields['amount'] == 0) {
- $entry->valid = false;
- $entry->errors->push('Amount of transaction is zero, cannot handle.');
- Log::warning('Amount of transaction is zero, cannot handle.');
-
- return $entry;
- }
- Log::debug('Amount is OK.');
-
- return $entry;
- }
-
- /**
- * @param ImportEntry $entry
- *
- * @return ImportEntry
- */
- private function cleanDescription(ImportEntry $entry): ImportEntry
- {
-
- if (!isset($entry->fields['description'])) {
- Log::debug('Set empty transaction description because field was not set.');
- $entry->fields['description'] = '(empty transaction description)';
-
- return $entry;
- }
- if (is_null($entry->fields['description'])) {
- Log::debug('Set empty transaction description because field was null.');
- $entry->fields['description'] = '(empty transaction description)';
-
- return $entry;
- }
- $entry->fields['description'] = trim($entry->fields['description']);
-
- if (strlen($entry->fields['description']) == 0) {
- Log::debug('Set empty transaction description because field was empty.');
- $entry->fields['description'] = '(empty transaction description)';
-
- return $entry;
- }
- Log::debug('Transaction description is OK.', ['description' => $entry->fields['description']]);
-
- return $entry;
- }
-
- /**
- * @param Account $account
- * @param string $type
- *
- * @return Account
- */
- private function convertAccount(Account $account, string $type): Account
- {
- $accountType = $account->accountType->type;
- if ($accountType === $type) {
- Log::debug(sprintf('Account %s already of type %s', $account->name, $type));
-
- return $account;
- }
- // find it first by new type:
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
- $result = $repository->findByName($account->name, [$type]);
- if (is_null($result->id)) {
- // can convert account:
- Log::debug(sprintf('No account named %s of type %s, create new account.', $account->name, $type));
- $result = $repository->store(
- [
- 'user' => $this->user->id,
- 'accountType' => config('firefly.shortNamesByFullName.' . $type),
- 'name' => $account->name,
- 'virtualBalance' => 0,
- 'active' => true,
- 'iban' => null,
- 'openingBalance' => 0,
- ]
- );
- }
- Log::debug(
- sprintf(
- 'Using another account named %s (#%d) of type %s, will use that one instead of %s (#%d)', $account->name, $result->id, $type, $account->name,
- $account->id
- )
- );
-
- return $result;
-
-
- }
-
- /**
- * @return Account
- */
- private function fallbackExpenseAccount(): Account
- {
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
- $name = 'Unknown expense account';
- $result = $repository->findByName($name, [AccountType::EXPENSE]);
- if (is_null($result->id)) {
- $result = $repository->store(
- ['name' => $name, 'iban' => null, 'openingBalance' => 0, 'user' => $this->user->id, 'accountType' => 'expense', 'virtualBalance' => 0,
- 'active' => true]
- );
- }
-
- return $result;
- }
-
- /**
- * @return Account
- */
- private function fallbackRevenueAccount(): Account
- {
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
-
- $name = 'Unknown revenue account';
- $result = $repository->findByName($name, [AccountType::REVENUE]);
-
-
- if (is_null($result->id)) {
- $result = $repository->store(
- ['name' => $name, 'iban' => null, 'openingBalance' => 0, 'user' => $this->user->id, 'accountType' => 'revenue', 'virtualBalance' => 0,
- 'active' => true]
- );
- }
-
- return $result;
- }
-
- /**
- * @param ImportEntry $entry
- *
- * @return ImportEntry
- */
- private function setAssetAccount(ImportEntry $entry): ImportEntry
- {
- if (is_null($entry->fields['asset-account'])) {
- if (!is_null($this->defaultImportAccount)) {
- Log::debug('Set asset account from default asset account');
- $entry->fields['asset-account'] = $this->defaultImportAccount;
-
- return $entry;
- }
- // default import is null? should not happen. Entry cannot be imported.
- // set error message and block.
- $entry->valid = false;
- Log::warning('Cannot import entry. Asset account is NULL and import account is also NULL.');
-
- return $entry;
- }
- Log::debug('Asset account is OK.', ['id' => $entry->fields['asset-account']->id, 'name' => $entry->fields['asset-account']->name]);
-
- return $entry;
- }
-
-
- /**
- * @param ImportEntry $entry
- *
- * @return ImportEntry
- */
- private function setDate(ImportEntry $entry): ImportEntry
- {
- if (is_null($entry->fields['date-transaction']) || $entry->certain['date-transaction'] == 0) {
- // empty date field? find alternative.
- $alternatives = ['date-book', 'date-interest', 'date-process'];
- foreach ($alternatives as $alternative) {
- if (!is_null($entry->fields[$alternative])) {
- Log::debug(sprintf('Copied date-transaction from %s.', $alternative));
- $entry->fields['date-transaction'] = clone $entry->fields[$alternative];
-
- return $entry;
- }
- }
- // date is still null at this point
- Log::debug('Set date-transaction to today.');
- $entry->fields['date-transaction'] = new Carbon;
-
- return $entry;
- }
-
- // confidence is zero?
-
- Log::debug('Date-transaction is OK');
-
- return $entry;
- }
-
- /**
- * @param ImportEntry $entry
- *
- * @return ImportEntry
- */
- private function setOpposingAccount(ImportEntry $entry): ImportEntry
- {
- // empty opposing account. Create one based on amount.
- if (is_null($entry->fields['opposing-account'])) {
-
- if ($entry->fields['amount'] < 0) {
- // create or find general opposing expense account.
- Log::debug('Created fallback expense account');
- $entry->fields['opposing-account'] = $this->fallbackExpenseAccount();
-
- return $entry;
- }
- Log::debug('Created fallback revenue account');
- $entry->fields['opposing-account'] = $this->fallbackRevenueAccount();
-
- return $entry;
- }
-
- // opposing is of type "import". Convert to correct type (by amount):
- $type = $entry->fields['opposing-account']->accountType->type;
- if ($type == AccountType::IMPORT && $entry->fields['amount'] < 0) {
- $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::EXPENSE);
- $entry->fields['opposing-account'] = $account;
- Log::debug('Converted import account to expense account');
-
- return $entry;
- }
- if ($type == AccountType::IMPORT && $entry->fields['amount'] > 0) {
- $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::REVENUE);
- $entry->fields['opposing-account'] = $account;
- Log::debug('Converted import account to revenue account');
-
- return $entry;
- }
- // amount < 0, but opposing is revenue
- if ($type == AccountType::REVENUE && $entry->fields['amount'] < 0) {
- $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::EXPENSE);
- $entry->fields['opposing-account'] = $account;
- Log::debug('Converted revenue account to expense account');
-
- return $entry;
- }
-
- // amount > 0, but opposing is expense
- if ($type == AccountType::EXPENSE && $entry->fields['amount'] > 0) {
- $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::REVENUE);
- $entry->fields['opposing-account'] = $account;
- Log::debug('Converted expense account to revenue account');
-
- return $entry;
- }
- // account type is OK
- Log::debug('Opposing account is OK.');
-
- return $entry;
-
- }
-
- /**
- * @param ImportEntry $entry
- *
- * @return ImportEntry
- */
- private function setTransactionCurrency(ImportEntry $entry): ImportEntry
- {
- if (is_null($entry->fields['currency'])) {
- /** @var CurrencyRepositoryInterface $repository */
- $repository = app(CurrencyRepositoryInterface::class);
- $repository->setUser($this->user);
- // is the default currency for the user or the system
- $defaultCode = Preferences::getForUser($this->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
-
- $entry->fields['currency'] = $repository->findByCode($defaultCode);
- Log::debug(sprintf('Set currency to %s', $defaultCode));
-
- return $entry;
- }
- Log::debug(sprintf('Currency is OK: %s', $entry->fields['currency']->code));
-
- return $entry;
- }
-
- /**
- * @param ImportEntry $entry
- *
- * @return ImportEntry
- */
- private function setTransactionType(ImportEntry $entry): ImportEntry
- {
- Log::debug(sprintf('Opposing account is of type %s', $entry->fields['opposing-account']->accountType->type));
- $type = $entry->fields['opposing-account']->accountType->type;
- switch ($type) {
- case AccountType::EXPENSE:
- $entry->fields['transaction-type'] = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
- Log::debug('Transaction type is now withdrawal.');
-
- return $entry;
- case AccountType::REVENUE:
- $entry->fields['transaction-type'] = TransactionType::whereType(TransactionType::DEPOSIT)->first();
- Log::debug('Transaction type is now deposit.');
-
- return $entry;
- case AccountType::DEFAULT:
- case AccountType::ASSET:
- $entry->fields['transaction-type'] = TransactionType::whereType(TransactionType::TRANSFER)->first();
- Log::debug('Transaction type is now transfer.');
-
- return $entry;
- }
- Log::warning(sprintf('Opposing account is of type %s, cannot handle this.', $type));
- $entry->valid = false;
- $entry->errors->push(sprintf('Opposing account is of type %s, cannot handle this.', $type));
-
- return $entry;
- }
-
-
-}
diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php
deleted file mode 100644
index 99f8c62a97..0000000000
--- a/app/Import/Importer/CsvImporter.php
+++ /dev/null
@@ -1,175 +0,0 @@
-collection = new Collection;
- $this->validSpecifics = array_keys(config('csv.import_specifics'));
- }
-
- /**
- * Run the actual import
- *
- * @return Collection
- */
- public function createImportEntries(): Collection
- {
- $config = $this->job->configuration;
- $content = $this->job->uploadFileContents();
-
- // create CSV reader.
- $reader = Reader::createFromString($content);
- $reader->setDelimiter($config['delimiter']);
- $start = $config['has-headers'] ? 1 : 0;
- $results = $reader->fetch();
-
- Log::notice('Building importable objects from CSV file.');
-
- foreach ($results as $index => $row) {
- if ($index >= $start) {
- $line = $index + 1;
- Log::debug('----- import entry build start --');
- Log::debug(sprintf('Now going to import row %d.', $index));
- $importEntry = $this->importSingleRow($index, $row);
- $this->collection->put($line, $importEntry);
- /**
- * 1. Build import entry.
- * 2. Validate import entry.
- * 3. Store journal.
- * 4. Run rules.
- */
- $this->job->addTotalSteps(4);
- $this->job->addStepsDone(1);
- }
- }
- Log::debug(sprintf('Import collection contains %d entries', $this->collection->count()));
- Log::notice(sprintf('Built %d importable object(s) from your CSV file.', $this->collection->count()));
-
- return $this->collection;
- }
-
- /**
- * @param ImportJob $job
- */
- public function setJob(ImportJob $job)
- {
- $this->job = $job;
- }
-
- /**
- * @param int $index
- * @param array $row
- *
- * @return ImportEntry
- * @throws FireflyException
- */
- private function importSingleRow(int $index, array $row): ImportEntry
- {
- // create import object. This is where each entry ends up.
- $object = new ImportEntry;
-
- Log::debug(sprintf('Now at row %d', $index));
-
- // 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);
- $object->importValue('hash', 100, $hash);
-
- // and this is the point where the specifix go to work.
- foreach ($config['specifics'] as $name => $enabled) {
-
- if (!in_array($name, $this->validSpecifics)) {
- throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
- }
-
- /** @var SpecificInterface $specific */
- $specific = app('FireflyIII\Import\Specifics\\' . $name);
-
- // it returns the row, possibly modified:
- $row = $specific->run($row);
- }
-
- foreach ($row as $rowIndex => $value) {
- // find the role for this column:
- $role = $config['column-roles'][$rowIndex] ?? '_ignore';
- $doMap = $config['column-do-mapping'][$rowIndex] ?? false;
- $validConverters = array_keys(config('csv.import_roles'));
-
- // throw error when not a valid converter.
- if (!in_array($role, $validConverters)) {
- throw new FireflyException(sprintf('"%s" is not a valid role.', $role));
- }
- $converterClass = config('csv.import_roles.' . $role . '.converter');
- $mapping = $config['column-mapping-config'][$rowIndex] ?? [];
- $className = sprintf('FireflyIII\\Import\\Converter\\%s', $converterClass);
- /** @var ConverterInterface $converter */
- $converter = app($className);
- // set some useful values for the converter:
- $converter->setMapping($mapping);
- $converter->setDoMap($doMap);
- $converter->setUser($this->job->user);
- $converter->setConfig($config);
-
- // run the converter for this value:
- $convertedValue = $converter->convert($value);
- $certainty = $converter->getCertainty();
-
- // log it.
- Log::debug('Value ', ['index' => $rowIndex, 'value' => $value, 'role' => $role]);
-
- // store in import entry:
- Log::debug('Going to import', ['role' => $role, 'value' => $value, 'certainty' => $certainty]);
- $object->importValue($role, $certainty, $convertedValue);
- }
-
-
- return $object;
-
- }
-}
diff --git a/app/Import/Importer/ImporterInterface.php b/app/Import/Importer/ImporterInterface.php
deleted file mode 100644
index 06c18bd793..0000000000
--- a/app/Import/Importer/ImporterInterface.php
+++ /dev/null
@@ -1,38 +0,0 @@
-command = $command;
- $this->changeLevel(env('LOG_LEVEL', 'debug'));
+
+ $this->changeLevel(env('APP_LOG_LEVEL', 'info'));
}
/**
@@ -56,9 +57,10 @@ class CommandHandler extends AbstractProcessingHandler
*/
private function changeLevel(string $level)
{
- $level = strtoupper($level);
- if (defined(sprintf('Logger::%s', $level))) {
- $this->setLevel(constant(sprintf('Logger::%s', $level)));
+ $level = strtoupper($level);
+ $reference = sprintf('\Monolog\Logger::%s', $level);
+ if (defined($reference)) {
+ $this->setLevel(constant($reference));
}
}
}
diff --git a/app/Import/Mapper/AssetAccountIbans.php b/app/Import/Mapper/AssetAccountIbans.php
index e94f502cbb..9d53bf6d8c 100644
--- a/app/Import/Mapper/AssetAccountIbans.php
+++ b/app/Import/Mapper/AssetAccountIbans.php
@@ -42,7 +42,7 @@ class AssetAccountIbans implements MapperInterface
if (strlen($iban) > 0) {
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
}
- if (strlen($iban) == 0) {
+ if (strlen($iban) === 0) {
$list[$account->id] = $account->name;
}
}
@@ -50,7 +50,7 @@ class AssetAccountIbans implements MapperInterface
asort($list);
$list = $topList + $list;
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Mapper/AssetAccounts.php b/app/Import/Mapper/AssetAccounts.php
index 8335bea7e4..2679334fe3 100644
--- a/app/Import/Mapper/AssetAccounts.php
+++ b/app/Import/Mapper/AssetAccounts.php
@@ -47,7 +47,7 @@ class AssetAccounts implements MapperInterface
asort($list);
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Mapper/Bills.php b/app/Import/Mapper/Bills.php
index 57d60b3013..a12ee950ea 100644
--- a/app/Import/Mapper/Bills.php
+++ b/app/Import/Mapper/Bills.php
@@ -40,7 +40,7 @@ class Bills implements MapperInterface
}
asort($list);
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Mapper/Budgets.php b/app/Import/Mapper/Budgets.php
index 37276ddcc0..88ca1e0d5e 100644
--- a/app/Import/Mapper/Budgets.php
+++ b/app/Import/Mapper/Budgets.php
@@ -41,7 +41,7 @@ class Budgets implements MapperInterface
}
asort($list);
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Mapper/Categories.php b/app/Import/Mapper/Categories.php
index 5144a06a41..30c81dfada 100644
--- a/app/Import/Mapper/Categories.php
+++ b/app/Import/Mapper/Categories.php
@@ -41,7 +41,7 @@ class Categories implements MapperInterface
}
asort($list);
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Mapper/OpposingAccountIbans.php b/app/Import/Mapper/OpposingAccountIbans.php
index bcf3372e10..121395cac4 100644
--- a/app/Import/Mapper/OpposingAccountIbans.php
+++ b/app/Import/Mapper/OpposingAccountIbans.php
@@ -48,7 +48,7 @@ class OpposingAccountIbans implements MapperInterface
if (strlen($iban) > 0) {
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
}
- if (strlen($iban) == 0) {
+ if (strlen($iban) === 0) {
$list[$account->id] = $account->name;
}
}
@@ -56,7 +56,7 @@ class OpposingAccountIbans implements MapperInterface
asort($list);
$list = $topList + $list;
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Mapper/OpposingAccounts.php b/app/Import/Mapper/OpposingAccounts.php
index fd2d86a5bb..bd95610504 100644
--- a/app/Import/Mapper/OpposingAccounts.php
+++ b/app/Import/Mapper/OpposingAccounts.php
@@ -53,7 +53,7 @@ class OpposingAccounts implements MapperInterface
asort($list);
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
}
diff --git a/app/Import/Mapper/Tags.php b/app/Import/Mapper/Tags.php
index e549fd90ef..c1b7f324e0 100644
--- a/app/Import/Mapper/Tags.php
+++ b/app/Import/Mapper/Tags.php
@@ -40,7 +40,7 @@ class Tags implements MapperInterface
}
asort($list);
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Mapper/TransactionCurrencies.php b/app/Import/Mapper/TransactionCurrencies.php
index d24f083b0f..286f275214 100644
--- a/app/Import/Mapper/TransactionCurrencies.php
+++ b/app/Import/Mapper/TransactionCurrencies.php
@@ -36,7 +36,7 @@ class TransactionCurrencies implements MapperInterface
asort($list);
- $list = [0 => trans('csv.do_not_map')] + $list;
+ $list = [0 => trans('csv.map_do_not_map')] + $list;
return $list;
diff --git a/app/Import/Object/ImportAccount.php b/app/Import/Object/ImportAccount.php
new file mode 100644
index 0000000000..ca5dba85d5
--- /dev/null
+++ b/app/Import/Object/ImportAccount.php
@@ -0,0 +1,344 @@
+expectedType = AccountType::ASSET;
+ $this->account = new Account;
+ $this->repository = app(AccountRepositoryInterface::class);
+ Log::debug('Created ImportAccount.');
+ }
+
+ /**
+ * @return Account
+ */
+ public function getAccount(): Account
+ {
+ if (is_null($this->account->id)) {
+ $this->store();
+ }
+
+ return $this->account;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpectedType(): string
+ {
+ return $this->expectedType;
+ }
+
+ /**
+ * @param string $expectedType
+ */
+ public function setExpectedType(string $expectedType)
+ {
+ $this->expectedType = $expectedType;
+ }
+
+ /**
+ * @param array $accountIban
+ */
+ public function setAccountIban(array $accountIban)
+ {
+ $this->accountIban = $accountIban;
+ }
+
+ /**
+ * @param array $value
+ */
+ public function setAccountId(array $value)
+ {
+ $this->accountId = $value;
+ }
+
+ /**
+ * @param array $accountName
+ */
+ public function setAccountName(array $accountName)
+ {
+ $this->accountName = $accountName;
+ }
+
+ /**
+ * @param array $accountNumber
+ */
+ public function setAccountNumber(array $accountNumber)
+ {
+ $this->accountNumber = $accountNumber;
+ }
+
+ /**
+ * @param int $defaultAccountId
+ */
+ public function setDefaultAccountId(int $defaultAccountId)
+ {
+ $this->defaultAccountId = $defaultAccountId;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ $this->repository->setUser($user);
+ }
+
+ /**
+ * @return Account
+ */
+ private function findExistingObject(): Account
+ {
+ Log::debug('In findExistingObject() for Account');
+ // 0: determin account type:
+ /** @var AccountType $accountType */
+ $accountType = AccountType::whereType($this->expectedType)->first();
+
+ // 1: find by ID, iban or name (and type)
+ if (count($this->accountId) === 3) {
+ Log::debug(sprintf('Finding account of type %d and ID %d', $accountType->id, $this->accountId['value']));
+ /** @var Account $account */
+ $account = $this->user->accounts()->where('account_type_id', $accountType->id)->where('id', $this->accountId['value'])->first();
+ if (!is_null($account)) {
+ Log::debug(sprintf('Found unmapped %s account by ID (#%d): %s', $this->expectedType, $account->id, $account->name));
+
+ return $account;
+ }
+ Log::debug('Found nothing.');
+ }
+ /** @var Collection $accounts */
+ $accounts = $this->repository->getAccountsByType([$accountType->type]);
+ // Two: find by IBAN (and type):
+ if (count($this->accountIban) === 3) {
+ $iban = $this->accountIban['value'];
+ Log::debug(sprintf('Finding account of type %d and IBAN %s', $accountType->id, $iban));
+ $filtered = $accounts->filter(
+ function (Account $account) use ($iban) {
+ if ($account->iban === $iban) {
+ Log::debug(
+ sprintf('Found unmapped %s account by IBAN (#%d): %s (%s)', $this->expectedType, $account->id, $account->name, $account->iban)
+ );
+
+ return $account;
+ }
+
+ return null;
+ }
+ );
+ if ($filtered->count() === 1) {
+ return $filtered->first();
+ }
+ Log::debug('Found nothing.');
+ }
+
+ // Three: find by name (and type):
+ if (count($this->accountName) === 3) {
+ $name = $this->accountName['value'];
+ Log::debug(sprintf('Finding account of type %d and name %s', $accountType->id, $name));
+ $filtered = $accounts->filter(
+ function (Account $account) use ($name) {
+ if ($account->name === $name) {
+ Log::debug(sprintf('Found unmapped %s account by name (#%d): %s', $this->expectedType, $account->id, $account->name));
+
+ return $account;
+ }
+
+ return null;
+ }
+ );
+
+ if ($filtered->count() === 1) {
+ return $filtered->first();
+ }
+ Log::debug('Found nothing.');
+ }
+
+ // 4: do not search by account number.
+ Log::debug('Found NO existing accounts.');
+
+ return new Account;
+
+ }
+
+ /**
+ * @return Account
+ */
+ private function findMappedObject(): Account
+ {
+ Log::debug('In findMappedObject() for Account');
+ $fields = ['accountId', 'accountIban', 'accountNumber', 'accountName'];
+ foreach ($fields as $field) {
+ $array = $this->$field;
+ Log::debug(sprintf('Find mapped account based on field "%s" with value', $field), $array);
+ // check if a pre-mapped object exists.
+ $mapped = $this->getMappedObject($array);
+ if (!is_null($mapped->id)) {
+ Log::debug(sprintf('Found account #%d!', $mapped->id));
+
+ return $mapped;
+ }
+
+ }
+ Log::debug('Found no account on mapped data or no map present.');
+
+ return new Account;
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return Account
+ */
+ private function getMappedObject(array $array): Account
+ {
+ Log::debug('In getMappedObject() for Account');
+ if (count($array) === 0) {
+ Log::debug('Array is empty, nothing will come of this.');
+
+ return new Account;
+ }
+
+ if (array_key_exists('mapped', $array) && is_null($array['mapped'])) {
+ Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
+
+ return new Account;
+ }
+
+ Log::debug('Finding a mapped account based on', $array);
+
+ $search = intval($array['mapped']);
+ $account = $this->repository->find($search);
+
+ if (is_null($account->id)) {
+ Log::error(sprintf('There is no account with id #%d. Invalid mapping will be ignored!', $search));
+
+ return new Account;
+ }
+ // must be of the same type
+ // except when mapped is an asset, then it's fair game.
+ // which only shows that user must map very carefully.
+ if ($account->accountType->type !== $this->expectedType && $account->accountType->type !== AccountType::ASSET) {
+ Log::error(
+ sprintf(
+ 'Mapped account #%d is of type "%s" but we expect a "%s"-account. Mapping will be ignored.', $account->id, $account->accountType->type,
+ $this->expectedType
+ )
+ );
+
+ return new Account;
+ }
+
+ Log::debug(sprintf('Found account! #%d ("%s"). Return it', $account->id, $account->name));
+
+ return $account;
+ }
+
+ /**
+ * @return bool
+ */
+ private function store(): bool
+ {
+ // 1: find mapped object:
+ $mapped = $this->findMappedObject();
+ if (!is_null($mapped->id)) {
+ $this->account = $mapped;
+
+ return true;
+ }
+ // 2: find existing by given values:
+ $found = $this->findExistingObject();
+ if (!is_null($found->id)) {
+ $this->account = $found;
+
+ return true;
+ }
+
+ // 3: if found nothing, retry the search with an asset account:
+ Log::debug('Will try to find an asset account just in case.');
+ $oldExpectedType = $this->expectedType;
+ $this->expectedType = AccountType::ASSET;
+ $found = $this->findExistingObject();
+ if (!is_null($found->id)) {
+ Log::debug('Found asset account!');
+ $this->account = $found;
+
+ return true;
+ }
+ $this->expectedType = $oldExpectedType;
+
+ // if search for an asset account, fall back to given "default account" (mandatory)
+ if ($this->expectedType === AccountType::ASSET) {
+ $this->account = $this->repository->find($this->defaultAccountId);
+ Log::debug(sprintf('Fall back to default account #%d "%s"', $this->account->id, $this->account->name));
+
+ return true;
+ }
+
+ Log::debug(sprintf('Found no account of type %s so must create one ourselves.', $this->expectedType));
+
+ $data = [
+ 'accountType' => config('firefly.shortNamesByFullName.' . $this->expectedType),
+ 'name' => $this->accountName['value'] ?? '(no name)',
+ 'iban' => $this->accountIban['value'] ?? null,
+ 'active' => true,
+ 'virtualBalance' => null,
+ ];
+
+ $this->account = $this->repository->store($data);
+ Log::debug(sprintf('Successfully stored new account #%d: %s', $this->account->id, $this->account->name));
+
+ return true;
+ }
+
+
+}
diff --git a/app/Import/Object/ImportBill.php b/app/Import/Object/ImportBill.php
new file mode 100644
index 0000000000..b8bbb18161
--- /dev/null
+++ b/app/Import/Object/ImportBill.php
@@ -0,0 +1,235 @@
+bill = new Bill;
+ $this->repository = app(BillRepositoryInterface::class);
+ Log::debug('Created ImportBill.');
+ }
+
+ /**
+ * @return Bill
+ */
+ public function getBill(): Bill
+ {
+ if (is_null($this->bill->id)) {
+ $this->store();
+ }
+
+ return $this->bill;
+ }
+
+ /**
+ * @param array $id
+ */
+ public function setId(array $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @param array $name
+ */
+ public function setName(array $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ $this->repository->setUser($user);
+ }
+
+ /**
+ * @return Bill
+ */
+ private function findExistingObject(): Bill
+ {
+ Log::debug('In findExistingObject() for Bill');
+ // 1: find by ID, or name
+
+ if (count($this->id) === 3) {
+ Log::debug(sprintf('Finding bill with ID #%d', $this->id['value']));
+ /** @var Bill $bill */
+ $bill = $this->repository->find(intval($this->id['value']));
+ if (!is_null($bill->id)) {
+ Log::debug(sprintf('Found unmapped bill by ID (#%d): %s', $bill->id, $bill->name));
+
+ return $bill;
+ }
+ Log::debug('Found nothing.');
+ }
+ // 2: find by name
+ if (count($this->name) === 3) {
+ /** @var Collection $bills */
+ $bills = $this->repository->getBills();
+ $name = $this->name['value'];
+ Log::debug(sprintf('Finding bill with name %s', $name));
+ $filtered = $bills->filter(
+ function (Bill $bill) use ($name) {
+ if ($bill->name === $name) {
+ Log::debug(sprintf('Found unmapped bill by name (#%d): %s', $bill->id, $bill->name));
+
+ return $bill;
+ }
+
+ return null;
+ }
+ );
+
+ if ($filtered->count() === 1) {
+ return $filtered->first();
+ }
+ Log::debug('Found nothing.');
+ }
+
+ // 4: do not search by account number.
+ Log::debug('Found NO existing bills.');
+
+ return new Bill;
+
+ }
+
+ /**
+ * @return Bill
+ */
+ private function findMappedObject(): Bill
+ {
+ Log::debug('In findMappedObject() for Bill');
+ $fields = ['id', 'name'];
+ foreach ($fields as $field) {
+ $array = $this->$field;
+ Log::debug(sprintf('Find mapped bill based on field "%s" with value', $field), $array);
+ // check if a pre-mapped object exists.
+ $mapped = $this->getMappedObject($array);
+ if (!is_null($mapped->id)) {
+ Log::debug(sprintf('Found bill #%d!', $mapped->id));
+
+ return $mapped;
+ }
+
+ }
+ Log::debug('Found no bill on mapped data or no map present.');
+
+ return new Bill;
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return Bill
+ */
+ private function getMappedObject(array $array): Bill
+ {
+ Log::debug('In getMappedObject() for Bill');
+ if (count($array) === 0) {
+ Log::debug('Array is empty, nothing will come of this.');
+
+ return new Bill;
+ }
+
+ if (array_key_exists('mapped', $array) && is_null($array['mapped'])) {
+ Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
+
+ return new Bill;
+ }
+
+ Log::debug('Finding a mapped bill based on', $array);
+
+ $search = intval($array['mapped']);
+ $bill = $this->repository->find($search);
+
+ if (is_null($bill->id)) {
+ Log::error(sprintf('There is no bill with id #%d. Invalid mapping will be ignored!', $search));
+
+ return new Bill;
+ }
+
+
+ Log::debug(sprintf('Found bill! #%d ("%s"). Return it', $bill->id, $bill->name));
+
+ return $bill;
+ }
+
+ /**
+ * @return bool
+ */
+ private function store(): bool
+ {
+ // 1: find mapped object:
+ $mapped = $this->findMappedObject();
+ if (!is_null($mapped->id)) {
+ $this->bill = $mapped;
+
+ return true;
+ }
+ // 2: find existing by given values:
+ $found = $this->findExistingObject();
+ if (!is_null($found->id)) {
+ $this->bill = $found;
+
+ return true;
+ }
+ $name = $this->name['value'] ?? '';
+
+ if (strlen($name) === 0) {
+ return true;
+ }
+
+ Log::debug('Found no bill so must create one ourselves.');
+
+ $data = [
+ 'name' => $name,
+ ];
+
+ $this->bill = $this->repository->store($data);
+ Log::debug(sprintf('Successfully stored new bill #%d: %s', $this->bill->id, $this->bill->name));
+
+ return true;
+ }
+
+}
diff --git a/app/Import/Object/ImportBudget.php b/app/Import/Object/ImportBudget.php
new file mode 100644
index 0000000000..cbc1e05460
--- /dev/null
+++ b/app/Import/Object/ImportBudget.php
@@ -0,0 +1,235 @@
+budget = new Budget;
+ $this->repository = app(BudgetRepositoryInterface::class);
+ Log::debug('Created ImportBudget.');
+ }
+
+ /**
+ * @return Budget
+ */
+ public function getBudget(): Budget
+ {
+ if (is_null($this->budget->id)) {
+ $this->store();
+ }
+
+ return $this->budget;
+ }
+
+ /**
+ * @param array $id
+ */
+ public function setId(array $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @param array $name
+ */
+ public function setName(array $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ $this->repository->setUser($user);
+ }
+
+ /**
+ * @return Budget
+ */
+ private function findExistingObject(): Budget
+ {
+ Log::debug('In findExistingObject() for Budget');
+ // 1: find by ID, or name
+
+ if (count($this->id) === 3) {
+ Log::debug(sprintf('Finding budget with ID #%d', $this->id['value']));
+ /** @var Budget $budget */
+ $budget = $this->repository->find(intval($this->id['value']));
+ if (!is_null($budget->id)) {
+ Log::debug(sprintf('Found unmapped budget by ID (#%d): %s', $budget->id, $budget->name));
+
+ return $budget;
+ }
+ Log::debug('Found nothing.');
+ }
+ // 2: find by name
+ if (count($this->name) === 3) {
+ /** @var Collection $budgets */
+ $budgets = $this->repository->getBudgets();
+ $name = $this->name['value'];
+ Log::debug(sprintf('Finding budget with name %s', $name));
+ $filtered = $budgets->filter(
+ function (Budget $budget) use ($name) {
+ if ($budget->name === $name) {
+ Log::debug(sprintf('Found unmapped budget by name (#%d): %s', $budget->id, $budget->name));
+
+ return $budget;
+ }
+
+ return null;
+ }
+ );
+
+ if ($filtered->count() === 1) {
+ return $filtered->first();
+ }
+ Log::debug('Found nothing.');
+ }
+
+ // 4: do not search by account number.
+ Log::debug('Found NO existing budgets.');
+
+ return new Budget;
+
+ }
+
+ /**
+ * @return Budget
+ */
+ private function findMappedObject(): Budget
+ {
+ Log::debug('In findMappedObject() for Budget');
+ $fields = ['id', 'name'];
+ foreach ($fields as $field) {
+ $array = $this->$field;
+ Log::debug(sprintf('Find mapped budget based on field "%s" with value', $field), $array);
+ // check if a pre-mapped object exists.
+ $mapped = $this->getMappedObject($array);
+ if (!is_null($mapped->id)) {
+ Log::debug(sprintf('Found budget #%d!', $mapped->id));
+
+ return $mapped;
+ }
+
+ }
+ Log::debug('Found no budget on mapped data or no map present.');
+
+ return new Budget;
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return Budget
+ */
+ private function getMappedObject(array $array): Budget
+ {
+ Log::debug('In getMappedObject() for Budget');
+ if (count($array) === 0) {
+ Log::debug('Array is empty, nothing will come of this.');
+
+ return new Budget;
+ }
+
+ if (array_key_exists('mapped', $array) && is_null($array['mapped'])) {
+ Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
+
+ return new Budget;
+ }
+
+ Log::debug('Finding a mapped budget based on', $array);
+
+ $search = intval($array['mapped']);
+ $budget = $this->repository->find($search);
+
+ if (is_null($budget->id)) {
+ Log::error(sprintf('There is no budget with id #%d. Invalid mapping will be ignored!', $search));
+
+ return new Budget;
+ }
+
+ Log::debug(sprintf('Found budget! #%d ("%s"). Return it', $budget->id, $budget->name));
+
+ return $budget;
+ }
+
+ /**
+ * @return bool
+ */
+ private function store(): bool
+ {
+ // 1: find mapped object:
+ $mapped = $this->findMappedObject();
+ if (!is_null($mapped->id)) {
+ $this->budget = $mapped;
+
+ return true;
+ }
+ // 2: find existing by given values:
+ $found = $this->findExistingObject();
+ if (!is_null($found->id)) {
+ $this->budget = $found;
+
+ return true;
+ }
+ $name = $this->name['value'] ?? '';
+
+ if (strlen($name) === 0) {
+ return true;
+ }
+
+ Log::debug('Found no budget so must create one ourselves.');
+
+ $data = [
+ 'name' => $name,
+ ];
+
+ $this->budget = $this->repository->store($data);
+ Log::debug(sprintf('Successfully stored new budget #%d: %s', $this->budget->id, $this->budget->name));
+
+ return true;
+ }
+
+
+}
diff --git a/app/Import/Object/ImportCategory.php b/app/Import/Object/ImportCategory.php
new file mode 100644
index 0000000000..b86a73d6ec
--- /dev/null
+++ b/app/Import/Object/ImportCategory.php
@@ -0,0 +1,229 @@
+category = new Category();
+ $this->repository = app(CategoryRepositoryInterface::class);
+ Log::debug('Created ImportCategory.');
+ }
+
+ /**
+ * @return Category
+ */
+ public function getCategory(): Category
+ {
+ if (is_null($this->category->id)) {
+ $this->store();
+ }
+
+ return $this->category;
+ }
+
+ /**
+ * @param array $id
+ */
+ public function setId(array $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @param array $name
+ */
+ public function setName(array $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ $this->repository->setUser($user);
+ }
+
+ /**
+ * @return Category
+ */
+ private function findExistingObject(): Category
+ {
+ Log::debug('In findExistingObject() for Category');
+ // 1: find by ID, or name
+
+ if (count($this->id) === 3) {
+ Log::debug(sprintf('Finding category with ID #%d', $this->id['value']));
+ /** @var Category $category */
+ $category = $this->repository->find(intval($this->id['value']));
+ if (!is_null($category->id)) {
+ Log::debug(sprintf('Found unmapped category by ID (#%d): %s', $category->id, $category->name));
+
+ return $category;
+ }
+ Log::debug('Found nothing.');
+ }
+ // 2: find by name
+ if (count($this->name) === 3) {
+ /** @var Collection $categories */
+ $categories = $this->repository->getCategories();
+ $name = $this->name['value'];
+ Log::debug(sprintf('Finding category with name %s', $name));
+ $filtered = $categories->filter(
+ function (Category $category) use ($name) {
+ if ($category->name === $name) {
+ Log::debug(sprintf('Found unmapped category by name (#%d): %s', $category->id, $category->name));
+
+ return $category;
+ }
+
+ return null;
+ }
+ );
+
+ if ($filtered->count() === 1) {
+ return $filtered->first();
+ }
+ Log::debug('Found nothing.');
+ }
+
+ // 4: do not search by account number.
+ Log::debug('Found NO existing categories.');
+
+ return new Category;
+
+ }
+
+ /**
+ * @return Category
+ */
+ private function findMappedObject(): Category
+ {
+ Log::debug('In findMappedObject() for Category');
+ $fields = ['id', 'name'];
+ foreach ($fields as $field) {
+ $array = $this->$field;
+ Log::debug(sprintf('Find mapped category based on field "%s" with value', $field), $array);
+ // check if a pre-mapped object exists.
+ $mapped = $this->getMappedObject($array);
+ if (!is_null($mapped->id)) {
+ Log::debug(sprintf('Found category #%d!', $mapped->id));
+
+ return $mapped;
+ }
+
+ }
+ Log::debug('Found no category on mapped data or no map present.');
+
+ return new Category;
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return Category
+ */
+ private function getMappedObject(array $array): Category
+ {
+ Log::debug('In getMappedObject() for Category');
+ if (count($array) === 0) {
+ Log::debug('Array is empty, nothing will come of this.');
+
+ return new Category;
+ }
+
+ if (array_key_exists('mapped', $array) && is_null($array['mapped'])) {
+ Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
+
+ return new Category;
+ }
+
+ Log::debug('Finding a mapped category based on', $array);
+
+ $search = intval($array['mapped']);
+ $category = $this->repository->find($search);
+
+ if (is_null($category->id)) {
+ Log::error(sprintf('There is no category with id #%d. Invalid mapping will be ignored!', $search));
+
+ return new Category;
+ }
+
+ Log::debug(sprintf('Found category! #%d ("%s"). Return it', $category->id, $category->name));
+
+ return $category;
+ }
+
+ /**
+ * @return bool
+ */
+ private function store(): bool
+ {
+ // 1: find mapped object:
+ $mapped = $this->findMappedObject();
+ if (!is_null($mapped->id)) {
+ $this->category = $mapped;
+
+ return true;
+ }
+ // 2: find existing by given values:
+ $found = $this->findExistingObject();
+ if (!is_null($found->id)) {
+ $this->category = $found;
+
+ return true;
+ }
+ $name = $this->name['value'] ?? '';
+
+ if (strlen($name) === 0) {
+ return true;
+ }
+
+ Log::debug('Found no category so must create one ourselves.');
+
+ $data = [
+ 'name' => $name,
+ ];
+
+ $this->category = $this->repository->store($data);
+ Log::debug(sprintf('Successfully stored new category #%d: %s', $this->category->id, $this->category->name));
+
+ return true;
+ }
+
+
+}
diff --git a/app/Import/Object/ImportCurrency.php b/app/Import/Object/ImportCurrency.php
new file mode 100644
index 0000000000..e282e29228
--- /dev/null
+++ b/app/Import/Object/ImportCurrency.php
@@ -0,0 +1,225 @@
+currency = new TransactionCurrency;
+ $this->repository = app(CurrencyRepositoryInterface::class);
+ }
+
+ /**
+ * @return TransactionCurrency
+ */
+ public function getTransactionCurrency(): TransactionCurrency
+ {
+ if (!is_null($this->currency->id)) {
+ return $this->currency;
+ }
+ Log::debug('In createCurrency()');
+ // check if any of them is mapped:
+ $mapped = $this->findMappedObject();
+
+ if (!is_null($mapped->id)) {
+
+ Log::debug('Mapped existing currency.', ['new' => $mapped->toArray()]);
+ $this->currency = $mapped;
+
+ return $mapped;
+ }
+
+ $searched = $this->findExistingObject();
+ if (!is_null($searched->id)) {
+ Log::debug('Found existing currency.', ['found' => $searched->toArray()]);
+ $this->currency = $searched;
+
+ return $searched;
+ }
+ $data = [
+ 'code' => $this->code['value'] ?? null,
+ 'symbol' => $this->symbol['value'] ?? null,
+ 'name' => $this->name['value'] ?? null,
+ 'decimal_places' => 2,
+ ];
+ if (is_null($data['code'])) {
+ Log::debug('Need at least a code to create currency, return nothing.');
+
+ return new TransactionCurrency();
+ }
+
+ Log::debug('Search for maps resulted in nothing, create new one based on', $data);
+ $currency = $this->repository->store($data);
+ $this->currency = $currency;
+ Log::info('Made new currency.', ['input' => $data, 'new' => $currency->toArray()]);
+
+
+ return $currency;
+
+ }
+
+ /**
+ * @param array $code
+ */
+ public function setCode(array $code)
+ {
+ $this->code = $code;
+ }
+
+ /**
+ * @param array $id
+ */
+ public function setId(array $id)
+ {
+ $id['value'] = intval($id['value']);
+ $this->id = $id;
+ }
+
+ /**
+ * @param array $name
+ */
+ public function setName(array $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @param array $symbol
+ */
+ public function setSymbol(array $symbol)
+ {
+ $this->symbol = $symbol;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ $this->repository->setUser($user);
+ }
+
+ /**
+ * @return TransactionCurrency
+ */
+ private function findExistingObject(): TransactionCurrency
+ {
+ $search = [
+ 'id' => 'find',
+ 'code' => 'findByCode',
+ 'symbol' => 'findBySymbol',
+ 'name' => 'findByName',
+ ];
+ foreach ($search as $field => $function) {
+ $value = $this->$field['value'] ?? null;
+ if (!is_null($value)) {
+ Log::debug(sprintf('Searching for %s using function %s and value %s', $field, $function, $value));
+ $currency = $this->repository->$function($value);
+
+ if (!is_null($currency->id)) {
+ return $currency;
+ }
+ }
+ }
+
+ return new TransactionCurrency();
+ }
+
+ /**
+ * @return TransactionCurrency
+ */
+ private function findMappedObject(): TransactionCurrency
+ {
+ Log::debug('In findMappedObject()');
+ $fields = ['id', 'code', 'name', 'symbol'];
+ foreach ($fields as $field) {
+ $array = $this->$field;
+ Log::debug(sprintf('Find mapped currency based on field "%s" with value', $field), $array);
+ // check if a pre-mapped object exists.
+ $mapped = $this->getMappedObject($array);
+ if (!is_null($mapped->id)) {
+ Log::debug(sprintf('Found currency #%d!', $mapped->id));
+
+ return $mapped;
+ }
+
+ }
+ Log::debug('Found no currency on mapped data or no map present.');
+
+ return new TransactionCurrency;
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return TransactionCurrency
+ */
+ private function getMappedObject(array $array): TransactionCurrency
+ {
+ Log::debug('In getMappedObject()');
+ if (count($array) === 0) {
+ Log::debug('Array is empty, nothing will come of this.');
+
+ return new TransactionCurrency;
+ }
+
+ if (array_key_exists('mapped', $array) && is_null($array['mapped'])) {
+ Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
+
+ return new TransactionCurrency;
+ }
+
+ Log::debug('Finding a mapped object based on', $array);
+
+ $search = intval($array['mapped']);
+ $currency = $this->repository->find($search);
+
+
+ if (is_null($currency->id)) {
+ Log::error(sprintf('There is no currency with id #%d. Invalid mapping will be ignored!', $search));
+
+ return new TransactionCurrency;
+ }
+
+ Log::debug(sprintf('Found currency! #%d ("%s"). Return it', $currency->id, $currency->name));
+
+ return $currency;
+ }
+
+
+}
diff --git a/app/Import/Object/ImportJournal.php b/app/Import/Object/ImportJournal.php
new file mode 100644
index 0000000000..c4aa0feaa3
--- /dev/null
+++ b/app/Import/Object/ImportJournal.php
@@ -0,0 +1,286 @@
+description === '') {
+ return '(no description)';
+ }
+
+ return $this->description;
+ }
+
+
+ /**
+ * ImportEntry constructor.
+ */
+ public function __construct()
+ {
+ $this->errors = new Collection;
+ $this->asset = new ImportAccount;
+ $this->opposing = new ImportAccount;
+ $this->bill = new ImportBill;
+ $this->category = new ImportCategory;
+ $this->budget = new ImportBudget;
+ $this->currency = new ImportCurrency;
+ }
+
+ /**
+ * @param array $modifier
+ */
+ public function addToModifier(array $modifier)
+ {
+ $this->modifiers[] = $modifier;
+ }
+
+ /**
+ * @return TransactionJournal
+ * @throws FireflyException
+ */
+ public function createTransactionJournal(): TransactionJournal
+ {
+ exit('does not work yet');
+ }
+
+ /**
+ * @return string
+ */
+ public function getAmount(): string
+ {
+
+ /** @var ConverterInterface $amountConverter */
+ $amountConverter = app(Amount::class);
+ $this->amount = $amountConverter->convert($this->amount);
+ // modify
+ foreach ($this->modifiers as $modifier) {
+ $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role'])));
+ /** @var ConverterInterface $converter */
+ $converter = app($class);
+ if ($converter->convert($modifier['value']) === -1) {
+ $this->amount = Steam::negative($this->amount);
+ }
+ }
+
+ return $this->amount;
+ }
+
+ /**
+ * @return ImportCurrency
+ */
+ public function getCurrency(): ImportCurrency
+ {
+ return $this->currency;
+ }
+
+ /**
+ * @param string $format
+ *
+ * @return Carbon
+ */
+ public function getDate(string $format): Carbon
+ {
+ $date = new Carbon;
+ try {
+ $date = Carbon::createFromFormat($format, $this->date);
+ } catch (InvalidArgumentException $e) {
+ // don't care, just log.
+ Log::error(sprintf('Import journal cannot parse date "%s" from value "%s" so will return current date instead.', $format, $this->date));
+ }
+
+ return $date;
+ }
+
+ /**
+ * @param string $hash
+ */
+ public function setHash(string $hash)
+ {
+ $this->hash = $hash;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+
+ // set user for related objects:
+ $this->asset->setUser($user);
+ $this->opposing->setUser($user);
+ $this->budget->setUser($user);
+ $this->category->setUser($user);
+ $this->bill->setUser($user);
+ }
+
+ /**
+ * @param array $array
+ *
+ * @throws FireflyException
+ */
+ public function setValue(array $array)
+ {
+ switch ($array['role']) {
+ default:
+ throw new FireflyException(sprintf('ImportJournal cannot handle "%s" with value "%s".', $array['role'], $array['value']));
+ case 'account-id':
+ $this->asset->setAccountId($array);
+ break;
+ case 'amount':
+ $this->amount = $array['value'];
+ break;
+ case 'account-iban':
+ $this->asset->setAccountIban($array);
+ break;
+ case 'account-name':
+ $this->asset->setAccountName($array);
+ break;
+ case 'account-number':
+ $this->asset->setAccountNumber($array);
+ break;
+ case 'bill-id':
+ $this->bill->setId($array);
+ break;
+ case 'bill-name':
+ $this->bill->setName($array);
+ break;
+ case 'budget-id':
+ $this->budget->setId($array);
+ break;
+ case 'budget-name':
+ $this->budget->setName($array);
+ break;
+ case 'category-id':
+ $this->category->setId($array);
+ break;
+ case 'category-name':
+ $this->category->setName($array);
+ break;
+ case 'currency-code':
+ $this->currency->setCode($array);
+ break;
+ case 'currency-id':
+ $this->currency->setId($array);
+ break;
+ case 'currency-name':
+ $this->currency->setName($array);
+ break;
+ case 'currency-symbol':
+ $this->currency->setSymbol($array);
+ break;
+ case 'date-transaction':
+ $this->date = $array['value'];
+ break;
+ case 'description':
+ $this->description .= $array['value'];
+ break;
+ case 'sepa-ct-op':
+ case 'sepa-ct-id':
+ case 'sepa-db':
+ $this->notes .= ' ' . $array['value'];
+ $this->notes = trim($this->notes);
+ break;
+ case 'external-id':
+ $this->externalId = $array['value'];
+ break;
+ case '_ignore':
+ break;
+ case 'ing-debet-credit':
+ case 'rabo-debet-credit':
+ $this->addToModifier($array);
+ break;
+ case 'opposing-iban':
+ $this->opposing->setAccountIban($array);
+ break;
+ case 'opposing-name':
+ $this->opposing->setAccountName($array);
+ break;
+ case 'opposing-number':
+ $this->opposing->setAccountNumber($array);
+ break;
+ case 'opposing-id':
+ $this->opposing->setAccountId($array);
+ break;
+ case 'tags-comma':
+ case 'tags-space':
+ $this->tags[] = $array;
+ break;
+ case 'date-interest':
+ $this->metaDates['interest_date'] = $array['value'];
+ break;
+ case 'date-book':
+ $this->metaDates['book_date'] = $array['value'];
+ break;
+ case 'date-process':
+ $this->metaDates['process_date'] = $array['value'];
+ break;
+ }
+ }
+}
diff --git a/app/Import/Routine/ImportRoutine.php b/app/Import/Routine/ImportRoutine.php
new file mode 100644
index 0000000000..b032bc06d8
--- /dev/null
+++ b/app/Import/Routine/ImportRoutine.php
@@ -0,0 +1,176 @@
+journals = new Collection;
+ $this->errors = new Collection;
+ }
+
+ /**
+ *
+ */
+ public function run(): bool
+ {
+ if ($this->job->status !== 'configured') {
+ Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status));
+
+ return false;
+ }
+ set_time_limit(0);
+ Log::info(sprintf('Start with import job %s', $this->job->key));
+
+ $importObjects = $this->getImportObjects();
+ $this->lines = $importObjects->count();
+
+ // once done, use storage thing to actually store them:
+ Log::info(sprintf('Returned %d valid objects from file processor', $this->lines));
+
+ $storage = $this->storeObjects($importObjects);
+ Log::debug('Back in run()');
+
+ // update job:
+ $this->job->status = 'finished';
+ $this->job->save();
+
+ Log::debug('Updated job...');
+
+ $this->journals = $storage->journals;
+ $this->errors = $storage->errors;
+
+ Log::debug('Going to call createImportTag()');
+
+ // create tag, link tag to all journals:
+ $this->createImportTag();
+
+ Log::info(sprintf('Done with import job %s', $this->job->key));
+
+
+ return true;
+ }
+
+ /**
+ * @param ImportJob $job
+ */
+ public function setJob(ImportJob $job)
+ {
+ $this->job = $job;
+ }
+
+ /**
+ * @return Collection
+ */
+ protected function getImportObjects(): Collection
+ {
+ $objects = new Collection;
+ $type = $this->job->file_type;
+ $class = config(sprintf('firefly.import_processors.%s', $type));
+ /** @var FileProcessorInterface $processor */
+ $processor = app($class);
+ $processor->setJob($this->job);
+
+ if ($this->job->status === 'configured') {
+
+ // set job as "running"...
+ $this->job->status = 'running';
+ $this->job->save();
+
+ Log::debug('Job is configured, start with run()');
+ $processor->run();
+ $objects = $processor->getObjects();
+ }
+
+ return $objects;
+ }
+
+ /**
+ *
+ */
+ private function createImportTag(): Tag
+ {
+ Log::debug('Now in createImportTag()');
+ /** @var TagRepositoryInterface $repository */
+ $repository = app(TagRepositoryInterface::class);
+ $repository->setUser($this->job->user);
+ $data = [
+ 'tag' => trans('firefly.import_with_key', ['key' => $this->job->key]),
+ 'date' => new Carbon,
+ 'description' => null,
+ 'latitude' => null,
+ 'longitude' => null,
+ 'zoomLevel' => null,
+ 'tagMode' => 'nothing',
+ ];
+ $tag = $repository->store($data);
+ $extended = $this->job->extended_status;
+ $extended['tag'] = $tag->id;
+ $this->job->extended_status = $extended;
+ $this->job->save();
+
+ Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
+ Log::debug('Looping journals...');
+ $journalIds = $this->journals->pluck('id')->toArray();
+ $tagId = $tag->id;
+ foreach ($journalIds as $journalId) {
+ Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
+ DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
+ }
+ Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag));
+
+ return $tag;
+ }
+
+ /**
+ * @param Collection $objects
+ *
+ * @return ImportStorage
+ */
+ private function storeObjects(Collection $objects): ImportStorage
+ {
+ $storage = new ImportStorage;
+ $storage->setJob($this->job);
+ $storage->setDateFormat($this->job->configuration['date-format']);
+ $storage->setObjects($objects);
+ $storage->store();
+ Log::info('Back in storeObjects()');
+
+ return $storage;
+ }
+}
diff --git a/app/Import/Setup/CsvSetup.php b/app/Import/Setup/CsvSetup.php
deleted file mode 100644
index 51ccedb338..0000000000
--- a/app/Import/Setup/CsvSetup.php
+++ /dev/null
@@ -1,505 +0,0 @@
-defaultImportAccount = new Account;
- }
-
- /**
- * Create initial (empty) configuration array.
- *
- * @return bool
- */
- public function configure(): bool
- {
- if (is_null($this->job->configuration) || (is_array($this->job->configuration) && count($this->job->configuration) === 0)) {
- Log::debug('No config detected, will create empty one.');
- $this->job->configuration = config('csv.default_config');
- $this->job->save();
-
- return true;
- }
-
- // need to do nothing, for now.
- Log::debug('Detected config in upload, will use that one. ', $this->job->configuration);
-
- return true;
- }
-
- /**
- * @return array
- */
- public function getConfigurationData(): array
- {
- /** @var AccountRepositoryInterface $accountRepository */
- $accountRepository = app(AccountRepositoryInterface::class);
- $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
- $delimiters = [
- ',' => trans('form.csv_comma'),
- ';' => trans('form.csv_semicolon'),
- 'tab' => trans('form.csv_tab'),
- ];
-
- $specifics = [];
-
- // collect specifics.
- foreach (config('csv.import_specifics') as $name => $className) {
- $specifics[$name] = [
- 'name' => $className::getName(),
- 'description' => $className::getDescription(),
- ];
- }
-
- $data = [
- 'accounts' => ExpandedForm::makeSelectList($accounts),
- 'specifix' => [],
- 'delimiters' => $delimiters,
- 'upload_path' => storage_path('upload'),
- 'is_upload_possible' => is_writable(storage_path('upload')),
- 'specifics' => $specifics,
- ];
-
- return $data;
- }
-
- /**
- * This method returns the data required for the view that will let the user add settings to the import job.
- *
- * @return array
- */
- public function getDataForSettings(): array
- {
- Log::debug('Now in getDataForSettings()');
- if ($this->doColumnRoles()) {
- Log::debug('doColumnRoles() is true.');
- $data = $this->getDataForColumnRoles();
-
- return $data;
- }
-
- if ($this->doColumnMapping()) {
- Log::debug('doColumnMapping() is true.');
- $data = $this->getDataForColumnMapping();
-
- return $data;
- }
-
- echo 'no settings to do.';
- exit;
-
- }
-
- /**
- * This method returns the name of the view that will be shown to the user to further configure
- * the import job.
- *
- * @return string
- * @throws FireflyException
- */
- public function getViewForSettings(): string
- {
- if ($this->doColumnRoles()) {
- return 'import.csv.roles';
- }
-
- if ($this->doColumnMapping()) {
- return 'import.csv.map';
- }
- throw new FireflyException('There is no view for the current CSV import step.');
- }
-
- /**
- * This method returns whether or not the user must configure this import
- * job further.
- *
- * @return bool
- */
- public function requireUserSettings(): bool
- {
- Log::debug(sprintf('doColumnMapping is %s', $this->doColumnMapping()));
- Log::debug(sprintf('doColumnRoles is %s', $this->doColumnRoles()));
- if ($this->doColumnMapping() || $this->doColumnRoles()) {
- Log::debug('Return true');
-
- return true;
- }
- Log::debug('Return false');
-
- return false;
- }
-
- /**
- * @param array $data
- *
- * @param FileBag $files
- *
- * @return bool
- */
- public function saveImportConfiguration(array $data, FileBag $files): bool
- {
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $importId = $data['csv_import_account'] ?? 0;
- $account = $repository->find(intval($importId));
-
- $hasHeaders = isset($data['has_headers']) && intval($data['has_headers']) === 1 ? true : false;
- $config = $this->job->configuration;
- $config['has-headers'] = $hasHeaders;
- $config['date-format'] = $data['date_format'];
- $config['delimiter'] = $data['csv_delimiter'];
- $config['delimiter'] = $config['delimiter'] === 'tab' ? "\t" : $config['delimiter'];
-
- Log::debug('Entered import account.', ['id' => $importId]);
-
-
- if (!is_null($account->id)) {
- Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]);
- $config['import-account'] = $account->id;
- } else {
- Log::error('Could not find anything for csv_import_account.', ['id' => $importId]);
- }
- // loop specifics.
- if (isset($data['specifics']) && is_array($data['specifics'])) {
- foreach ($data['specifics'] as $name => $enabled) {
- // verify their content.
- $className = sprintf('FireflyIII\Import\Specifics\%s', $name);
- if (class_exists($className)) {
- $config['specifics'][$name] = 1;
- }
- }
- }
- $this->job->configuration = $config;
- $this->job->save();
-
- return true;
-
-
- }
-
- /**
- * @param ImportJob $job
- */
- public function setJob(ImportJob $job)
- {
- $this->job = $job;
- }
-
- /**
- * Store the settings filled in by the user, if applicable.
- *
- * @param Request $request
- *
- */
- public function storeSettings(Request $request)
- {
- $config = $this->job->configuration;
- $all = $request->all();
- if ($request->get('settings') == 'roles') {
- $count = $config['column-count'];
-
- $roleSet = 0; // how many roles have been defined
- $mapSet = 0; // how many columns must be mapped
- for ($i = 0; $i < $count; $i++) {
- $selectedRole = $all['role'][$i] ?? '_ignore';
- $doMapping = isset($all['map'][$i]) && $all['map'][$i] == '1' ? true : false;
- if ($selectedRole == '_ignore' && $doMapping === true) {
- $doMapping = false; // cannot map ignored columns.
- }
- if ($selectedRole != '_ignore') {
- $roleSet++;
- }
- if ($doMapping === true) {
- $mapSet++;
- }
- $config['column-roles'][$i] = $selectedRole;
- $config['column-do-mapping'][$i] = $doMapping;
- }
- if ($roleSet > 0) {
- $config['column-roles-complete'] = true;
- $this->job->configuration = $config;
- $this->job->save();
- }
- if ($mapSet === 0) {
- // skip setting of map:
- $config['column-mapping-complete'] = true;
- }
- }
- if ($request->get('settings') == 'map') {
- if (isset($all['mapping'])) {
- foreach ($all['mapping'] as $index => $data) {
- $config['column-mapping-config'][$index] = [];
- foreach ($data as $value => $mapId) {
- $mapId = intval($mapId);
- if ($mapId !== 0) {
- $config['column-mapping-config'][$index][$value] = intval($mapId);
- }
- }
- }
- }
-
- // set thing to be completed.
- $config['column-mapping-complete'] = true;
- $this->job->configuration = $config;
- $this->job->save();
- }
- }
-
- /**
- * @return bool
- */
- private function doColumnMapping(): bool
- {
- $mapArray = $this->job->configuration['column-do-mapping'] ?? [];
- $doMap = false;
- foreach ($mapArray as $value) {
- if ($value === true) {
- $doMap = true;
- break;
- }
- }
-
- return $this->job->configuration['column-mapping-complete'] === false && $doMap;
- }
-
- /**
- * @return bool
- */
- private function doColumnRoles(): bool
- {
- return $this->job->configuration['column-roles-complete'] === false;
- }
-
- /**
- * @return array
- * @throws FireflyException
- */
- private function getDataForColumnMapping(): array
- {
- $config = $this->job->configuration;
- $data = [];
- $indexes = [];
-
- foreach ($config['column-do-mapping'] as $index => $mustBeMapped) {
- if ($mustBeMapped) {
-
- $column = $config['column-roles'][$index] ?? '_ignore';
-
- // is valid column?
- $validColumns = array_keys(config('csv.import_roles'));
- if (!in_array($column, $validColumns)) {
- throw new FireflyException(sprintf('"%s" is not a valid column.', $column));
- }
-
- $canBeMapped = config('csv.import_roles.' . $column . '.mappable');
- $preProcessMap = config('csv.import_roles.' . $column . '.pre-process-map');
- if ($canBeMapped) {
- $mapperClass = config('csv.import_roles.' . $column . '.mapper');
- $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass);
- /** @var MapperInterface $mapper */
- $mapper = new $mapperName;
- $indexes[] = $index;
- $data[$index] = [
- 'name' => $column,
- 'mapper' => $mapperName,
- 'index' => $index,
- 'options' => $mapper->getMap(),
- 'preProcessMap' => null,
- 'values' => [],
- ];
- if ($preProcessMap) {
- $preClass = sprintf(
- '\\FireflyIII\\Import\\MapperPreProcess\\%s',
- config('csv.import_roles.' . $column . '.pre-process-mapper')
- );
- $data[$index]['preProcessMap'] = $preClass;
- }
- }
-
- }
- }
-
- // in order to actually map we also need all possible values from the CSV file.
- $content = $this->job->uploadFileContents();
- /** @var Reader $reader */
- $reader = Reader::createFromString($content);
- $reader->setDelimiter($config['delimiter']);
- $results = $reader->fetch();
- $validSpecifics = array_keys(config('csv.import_specifics'));
-
- foreach ($results as $rowIndex => $row) {
-
- // skip first row?
- if ($rowIndex === 0 && $config['has-headers']) {
- continue;
- }
-
- // run specifics here:
- // and this is the point where the specifix go to work.
- foreach ($config['specifics'] as $name => $enabled) {
-
- if (!in_array($name, $validSpecifics)) {
- throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
- }
- $class = config('csv.import_specifics.' . $name);
- /** @var SpecificInterface $specific */
- $specific = app($class);
-
- // it returns the row, possibly modified:
- $row = $specific->run($row);
- }
-
- //do something here
- foreach ($indexes as $index) { // this is simply 1, 2, 3, etc.
- if (!isset($row[$index])) {
- // don't really know how to handle this. Just skip, for now.
- continue;
- }
- $value = $row[$index];
- if (strlen($value) > 0) {
-
- // we can do some preprocessing here,
- // which is exclusively to fix the tags:
- if (!is_null($data[$index]['preProcessMap'])) {
- /** @var PreProcessorInterface $preProcessor */
- $preProcessor = app($data[$index]['preProcessMap']);
- $result = $preProcessor->run($value);
- $data[$index]['values'] = array_merge($data[$index]['values'], $result);
-
- Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]);
- Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]);
- Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $data[$index]['values']]);
-
-
- continue;
- }
-
- $data[$index]['values'][] = $value;
- }
- }
- }
- foreach ($data as $index => $entry) {
- $data[$index]['values'] = array_unique($data[$index]['values']);
- }
-
- return $data;
- }
-
- /**
- * This method collects the data that will enable a user to choose column content.
- *
- * @return array
- */
- private function getDataForColumnRoles(): array
- {
- Log::debug('Now in getDataForColumnRoles()');
- $config = $this->job->configuration;
- $data = [
- 'columns' => [],
- 'columnCount' => 0,
- 'columnHeaders' => [],
- ];
-
- // show user column role configuration.
- $content = $this->job->uploadFileContents();
-
- // create CSV reader.
- $reader = Reader::createFromString($content);
- $reader->setDelimiter($config['delimiter']);
- $start = $config['has-headers'] ? 1 : 0;
- $end = $start + config('csv.example_rows');
- $header = [];
- if ($config['has-headers']) {
- $header = $reader->fetchOne(0);
- }
-
-
- // collect example data in $data['columns']
- Log::debug(sprintf('While %s is smaller than %d', $start, $end));
- while ($start < $end) {
- $row = $reader->fetchOne($start);
- Log::debug(sprintf('Row %d has %d columns', $start, count($row)));
- // run specifics here:
- // and this is the point where the specifix go to work.
- foreach ($config['specifics'] as $name => $enabled) {
- /** @var SpecificInterface $specific */
- $specific = app('FireflyIII\Import\Specifics\\' . $name);
- Log::debug(sprintf('Will now apply specific "%s" to row %d.', $name, $start));
- // it returns the row, possibly modified:
- $row = $specific->run($row);
- }
-
- foreach ($row as $index => $value) {
- $value = trim($value);
- $data['columnHeaders'][$index] = $header[$index] ?? '';
- if (strlen($value) > 0) {
- $data['columns'][$index][] = $value;
- }
- }
- $start++;
- $data['columnCount'] = count($row) > $data['columnCount'] ? count($row) : $data['columnCount'];
- }
-
- // make unique example data
- foreach ($data['columns'] as $index => $values) {
- $data['columns'][$index] = array_unique($values);
- }
-
- $data['set_roles'] = [];
- // collect possible column roles:
- $data['available_roles'] = [];
- foreach (array_keys(config('csv.import_roles')) as $role) {
- $data['available_roles'][$role] = trans('csv.column_' . $role);
- }
-
- $config['column-count'] = $data['columnCount'];
- $this->job->configuration = $config;
- $this->job->save();
-
- return $data;
-
-
- }
-}
diff --git a/app/Import/Setup/SetupInterface.php b/app/Import/Setup/SetupInterface.php
deleted file mode 100644
index cffae80eba..0000000000
--- a/app/Import/Setup/SetupInterface.php
+++ /dev/null
@@ -1,88 +0,0 @@
-row[8] = $matches[4]; // 'opposing-account-name'
$this->row[7] = $matches[4]; // 'description'
- if ($matches[1] == 'GEA') {
+ if ($matches[1] === 'GEA') {
$this->row[7] = 'GEA ' . $matches[4]; // 'description'
}
diff --git a/app/Import/Storage/ImportStorage.php b/app/Import/Storage/ImportStorage.php
new file mode 100644
index 0000000000..964fafa1f7
--- /dev/null
+++ b/app/Import/Storage/ImportStorage.php
@@ -0,0 +1,493 @@
+objects = new Collection;
+ $this->journals = new Collection;
+ $this->errors = new Collection;
+ }
+
+ /**
+ * @param string $dateFormat
+ */
+ public function setDateFormat(string $dateFormat)
+ {
+ $this->dateFormat = $dateFormat;
+ }
+
+ /**
+ * @param ImportJob $job
+ */
+ public function setJob(ImportJob $job)
+ {
+ $this->job = $job;
+ $repository = app(CurrencyRepositoryInterface::class);
+ $repository->setUser($job->user);
+ $this->currencyRepository = $repository;
+ $this->rules = $this->getUserRules();
+ }
+
+ /**
+ * @param Collection $objects
+ */
+ public function setObjects(Collection $objects)
+ {
+ $this->objects = $objects;
+ }
+
+ /**
+ * Do storage of import objects
+ */
+ public function store()
+ {
+ $this->defaultCurrency = Amount::getDefaultCurrencyByUser($this->job->user);
+
+ // routine below consists of 3 steps.
+ /**
+ * @var int $index
+ * @var ImportJournal $object
+ */
+ foreach ($this->objects as $index => $object) {
+ try {
+ $this->storeImportJournal($index, $object);
+ } catch (FireflyException $e) {
+ $this->errors->push($e->getMessage());
+ Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage()));
+ }
+ }
+ Log::info('ImportStorage has finished.');
+
+ return true;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ *
+ * @return bool
+ */
+ protected function applyRules(TransactionJournal $journal): bool
+ {
+ if ($this->rules->count() > 0) {
+
+ /** @var Rule $rule */
+ foreach ($this->rules as $rule) {
+ Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id));
+ $processor = Processor::make($rule);
+ $processor->handleTransactionJournal($journal);
+
+ if ($rule->stop_processing) {
+ return true;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $journalId
+ * @param int $accountId
+ * @param int $currencyId
+ * @param string $amount
+ *
+ * @return bool
+ * @throws FireflyException
+ */
+ private function createTransaction(int $journalId, int $accountId, int $currencyId, string $amount): bool
+ {
+ $transaction = new Transaction;
+ $transaction->account_id = $accountId;
+ $transaction->transaction_journal_id = $journalId;
+ $transaction->transaction_currency_id = $currencyId;
+ $transaction->amount = $amount;
+ $transaction->save();
+ if (is_null($transaction->id)) {
+ $errorText = join(', ', $transaction->getErrors()->all());
+ throw new FireflyException($errorText);
+ }
+ Log::debug(sprintf('Created transaction with ID #%d, account #%d, amount %s', $transaction->id, $accountId, $amount));
+
+ return true;
+ }
+
+ /**
+ * @param ImportJournal $importJournal
+ *
+ * @return TransactionCurrency
+ */
+ private function getCurrency(ImportJournal $importJournal, Account $account): TransactionCurrency
+ {
+ // start with currency pref of account, if any:
+ $currency = $this->currencyRepository->find(intval($account->getMeta('currency_id')));
+ if (!is_null($currency->id)) {
+ return $currency;
+ }
+
+ // use given currency
+ $currency = $importJournal->getCurrency()->getTransactionCurrency();
+ if (!is_null($currency->id)) {
+ return $currency;
+ }
+
+ // backup to default
+ $currency = $this->defaultCurrency;
+
+ return $currency;
+ }
+
+ /**
+ * @param ImportAccount $account
+ * @param $amount
+ *
+ * @return Account
+ */
+ private function getOpposingAccount(ImportAccount $account, $amount): Account
+ {
+ if (bccomp($amount, '0') === -1) {
+ Log::debug(sprintf('%s is negative, create opposing expense account.', $amount));
+ $account->setExpectedType(AccountType::EXPENSE);
+
+ return $account->getAccount();
+ }
+ Log::debug(sprintf('%s is positive, create opposing revenue account.', $amount));
+ // amount is positive, it's a deposit, opposing is an revenue:
+ $account->setExpectedType(AccountType::REVENUE);
+
+ $databaseAccount = $account->getAccount();
+
+ return $databaseAccount;
+
+ }
+
+ /**
+ * @param string $amount
+ *
+ * @return TransactionType
+ */
+ private function getTransactionType(string $amount): TransactionType
+ {
+ $transactionType = new TransactionType();
+ // amount is negative, it's a withdrawal, opposing is an expense:
+ if (bccomp($amount, '0') === -1) {
+ $transactionType = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
+ }
+ if (bccomp($amount, '0') === 1) {
+ $transactionType = TransactionType::whereType(TransactionType::DEPOSIT)->first();
+ }
+
+ return $transactionType;
+ }
+
+ /**
+ * @return Collection
+ */
+ private function getUserRules(): Collection
+ {
+ $set = Rule::distinct()
+ ->where('rules.user_id', $this->job->user->id)
+ ->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
+ ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
+ ->where('rule_groups.active', 1)
+ ->where('rule_triggers.trigger_type', 'user_action')
+ ->where('rule_triggers.trigger_value', 'store-journal')
+ ->where('rules.active', 1)
+ ->orderBy('rule_groups.order', 'ASC')
+ ->orderBy('rules.order', 'ASC')
+ ->get(['rules.*', 'rule_groups.order']);
+ Log::debug(sprintf('Found %d user rules.', $set->count()));
+
+ return $set;
+
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param Bill $bill
+ */
+ private function storeBill(TransactionJournal $journal, Bill $bill)
+ {
+ if (!is_null($bill->id)) {
+ Log::debug(sprintf('Linked bill #%d to journal #%d', $bill->id, $journal->id));
+ $journal->bill()->associate($bill);
+ $journal->save();
+ }
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param Budget $budget
+ */
+ private function storeBudget(TransactionJournal $journal, Budget $budget)
+ {
+ if (!is_null($budget->id)) {
+ Log::debug(sprintf('Linked budget #%d to journal #%d', $budget->id, $journal->id));
+ $journal->budgets()->save($budget);
+ }
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param Category $category
+ */
+ private function storeCategory(TransactionJournal $journal, Category $category)
+ {
+
+ if (!is_null($category->id)) {
+ Log::debug(sprintf('Linked category #%d to journal #%d', $category->id, $journal->id));
+ $journal->categories()->save($category);
+ }
+
+ }
+
+ private function storeImportJournal(int $index, ImportJournal $importJournal): bool
+ {
+ Log::debug(sprintf('Going to store object #%d with description "%s"', $index, $importJournal->getDescription()));
+ $importJournal->asset->setDefaultAccountId($this->job->configuration['import-account']);
+ $asset = $importJournal->asset->getAccount();
+ $amount = $importJournal->getAmount();
+ $currency = $this->getCurrency($importJournal, $asset);
+ $date = $importJournal->getDate($this->dateFormat);
+ $transactionType = $this->getTransactionType($amount);
+ $opposing = $this->getOpposingAccount($importJournal->opposing, $amount);
+
+ // if opposing is an asset account, it's a transfer:
+ if ($opposing->accountType->type === AccountType::ASSET) {
+ Log::debug(sprintf('Opposing account #%d %s is an asset account, make transfer.', $opposing->id, $opposing->name));
+ $transactionType = TransactionType::whereType(TransactionType::TRANSFER)->first();
+ }
+
+ // verify that opposing account is of the correct type:
+ if ($opposing->accountType->type === AccountType::EXPENSE && $transactionType->type !== TransactionType::WITHDRAWAL) {
+ Log::error(sprintf('Row #%d is imported as a %s but opposing is an expense account. This cannot be!', $index, $transactionType->type));
+ }
+
+ /*** First step done! */
+ $this->job->addStepsDone(1);
+
+ // could be that transfer is double: verify this.
+ if ($this->verifyDoubleTransfer($transactionType, $importJournal)) {
+ // add three steps:
+ $this->job->addStepsDone(3);
+ // throw error
+ throw new FireflyException('Detected a possible duplicate, skip this one.');
+
+ }
+
+ // create a journal:
+ $journal = new TransactionJournal;
+ $journal->user_id = $this->job->user_id;
+ $journal->transaction_type_id = $transactionType->id;
+ $journal->transaction_currency_id = $currency->id;
+ $journal->description = $importJournal->getDescription();
+ $journal->date = $date->format('Y-m-d');
+ $journal->order = 0;
+ $journal->tag_count = 0;
+ $journal->completed = false;
+
+ if (!$journal->save()) {
+ $errorText = join(', ', $journal->getErrors()->all());
+ // add three steps:
+ $this->job->addStepsDone(3);
+ // throw error
+ throw new FireflyException($errorText);
+ }
+
+ // save meta data:
+ $journal->setMeta('importHash', $importJournal->hash);
+ Log::debug(sprintf('Created journal with ID #%d', $journal->id));
+
+ // create transactions:
+ $this->createTransaction($journal->id, $asset->id, $currency->id, $amount);
+ $this->createTransaction($journal->id, $opposing->id, $currency->id, Steam::opposite($amount));
+
+ /*** Another step done! */
+ $this->job->addStepsDone(1);
+
+ // store meta object things:
+ $this->storeCategory($journal, $importJournal->category->getCategory());
+ $this->storeBudget($journal, $importJournal->budget->getBudget());
+ $this->storeBill($journal, $importJournal->bill->getBill());
+ $this->storeMeta($journal, $importJournal->metaDates);
+
+ // sepa thing as note:
+ if (strlen($importJournal->notes) > 0) {
+ $journal->setMeta('notes', $importJournal->notes);
+ }
+
+ // set journal completed:
+ $journal->completed = true;
+ $journal->save();
+
+ $this->job->addStepsDone(1);
+
+ // run rules:
+ $this->applyRules($journal);
+ $this->job->addStepsDone(1);
+ $this->journals->push($journal);
+
+ Log::info(
+ sprintf(
+ 'Imported new journal #%d with description "%s" and amount %s %s.', $journal->id, $journal->description, $journal->transactionCurrency->code,
+ $amount
+ )
+ );
+
+ return true;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param array $dates
+ */
+ private function storeMeta(TransactionJournal $journal, array $dates)
+ {
+ // all other date fields as meta thing:
+ foreach ($dates as $name => $value) {
+ try {
+ $date = new Carbon($value);
+ $journal->setMeta($name, $date);
+ } catch (\Exception $e) {
+ // don't care, ignore:
+ Log::warning(sprintf('Could not parse "%s" into a valid Date object for field %s', $value, $name));
+ }
+ }
+ }
+
+ /**
+ * This method checks if the given transaction is a transfer and if so, if it might be a duplicate of an already imported transfer.
+ * This is important for import files that cover multiple accounts (and include both A<>B and B<>A transactions).
+ *
+ * @param TransactionType $transactionType
+ * @param ImportJournal $importJournal
+ *
+ * @return bool
+ */
+ private function verifyDoubleTransfer(TransactionType $transactionType, ImportJournal $importJournal): bool
+ {
+ if ($transactionType->type === TransactionType::TRANSFER) {
+ $amount = Steam::positive($importJournal->getAmount());
+ $asset = $importJournal->asset->getAccount();
+ $opposing = $this->getOpposingAccount($importJournal->opposing, $amount);
+ $date = $importJournal->getDate($this->dateFormat);
+ $description = $importJournal->getDescription();
+ $set = TransactionJournal::
+ leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->leftJoin(
+ 'transactions AS source', function (JoinClause $join) {
+ $join->on('transaction_journals.id', '=', 'source.transaction_journal_id')->where('source.amount', '<', 0);
+ }
+ )
+ ->leftJoin(
+ 'transactions AS destination', function (JoinClause $join) {
+ $join->on('transaction_journals.id', '=', 'destination.transaction_journal_id')->where(
+ 'destination.amount', '>', 0
+ );
+ }
+ )
+ ->leftJoin('accounts as source_accounts', 'source.account_id', '=', 'source_accounts.id')
+ ->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
+ ->where('transaction_journals.user_id', $this->job->user_id)
+ ->where('transaction_types.type', TransactionType::TRANSFER)
+ ->where('transaction_journals.date', $date->format('Y-m-d'))
+ ->where('destination.amount', $amount)
+ ->get(
+ ['transaction_journals.id', 'transaction_journals.encrypted', 'transaction_journals.description',
+ 'source_accounts.name as source_name', 'destination_accounts.name as destination_name']
+ );
+ if ($set->count() > 0) {
+ $filtered = $set->filter(
+ function (TransactionJournal $journal) use ($asset, $opposing, $description) {
+ $match = true;
+ $compare = [$asset->name, $opposing->name];
+
+ if ($journal->description !== $description) {
+ $match = false;
+ }
+ // when both are in array match is true. So reverse:
+ if (!(in_array(app('steam')->tryDecrypt($journal->source_name), $compare)
+ && in_array(
+ app('steam')->tryDecrypt($journal->destination_name), $compare
+ ))
+ ) {
+ $match = false;
+ }
+
+ if ($match) {
+ return $journal;
+ }
+
+ return null;
+ }
+ );
+ if (count($filtered) > 0) {
+ return true;
+ }
+ }
+ }
+
+
+ return false;
+ }
+
+}
diff --git a/app/Import/notes.txt b/app/Import/notes.txt
deleted file mode 100644
index 603e185f84..0000000000
--- a/app/Import/notes.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-The import routine is as follows:
-
-1. Upload and setup:
-
-User uploads a file with entries. The Setup/SetupInterface gives the user
-the opportunity (in any number of steps) to do the necessary configuration.
-This could also be skipped of course. An ImportJob object is created with a
-basic and empty configuration.
-
-Helper classes are as follows, greatly modelled to the CSV importer:
-
-- The Mapper classes give back lists of Firefly objects. You can show them to the
-user in order to help you convert text values to their Firefly counterparts.
-For example, the user maps "Gcrsr" to category Groceries.
-- The Converter classes exist to help convert text values to their Firely counterparts.
-Feed "AB12ABCD897829" to the AssetAccountIban Converter and you should end up with a new
-or found asset account. The previously built mapping is used to narrow it down. Submit an empty
-mapping if this one is not relevant.
-
-The mapping and possibly other configuration options are stored in a newly created
-ImportJob object, stored in the database. This import job holds a reference to the uploaded file
-(placed encrypted in /storage/uploads) and the status of the import.
-
-2. Actual import
-
-Using either the command line or the web interface the user can tell Firefly to start the import.
-
-The ImporterInterface runs and creates an ImportEntry for each line, blob or whatever distinction it
-wants.
-
-For each line, the ImporterInterface should run each field through the selected Converter in order
-to convert the text values to their Firefly counterparts. Again, this is modelled to the CSV importer
-and may need an update for MT940.
-
-In any case, this newly minted set of ImportEntries (it cannot be saved or stored atm,
-this has to be done in one go) is then fed to the ImportValidator which will reject entries
-(almost never) and corrects fields if necessary.
-
-
-- Adds the date (today) if no date is present.
-- Determins the type of transaction (withdrawal, deposit, transfer).
-- Determins the types of accounts involved (asset, expense, revenue).
-- Determins the currency of the transaction.
-- Adds a default description if there isn't one present.
-
-This set of corrected ImportEntries is then fed to the ImportStorage class which will generate
-TransactionJournals, Transactions and other related objects.
\ No newline at end of file
diff --git a/app/Jobs/MailError.php b/app/Jobs/MailError.php
index 05de2aca0a..fd4a0164ff 100644
--- a/app/Jobs/MailError.php
+++ b/app/Jobs/MailError.php
@@ -80,7 +80,7 @@ class MailError extends Job implements ShouldQueue
Mail::send(
['emails.error-html', 'emails.error-text'], $args,
function (Message $message) use ($email) {
- if ($email != 'mail@example.com') {
+ if ($email !== 'mail@example.com') {
$message->to($email, $email)->subject('Caught an error in Firely III');
}
}
diff --git a/app/Mail/RegisteredUser.php b/app/Mail/RegisteredUser.php
index f48ddfe6d5..ea8e6a7e63 100644
--- a/app/Mail/RegisteredUser.php
+++ b/app/Mail/RegisteredUser.php
@@ -12,17 +12,18 @@ class RegisteredUser extends Mailable
/** @var string */
public $address;
/** @var string */
- public $ip;
+ public $ipAddress;
/**
* Create a new message instance.
*
- * @return void
+ * @param string $address
+ * @param string $ipAddress
*/
- public function __construct(string $address, string $ip)
+ public function __construct(string $address, string $ipAddress)
{
- $this->address = $address;
- $this->ip = $ip;
+ $this->address = $address;
+ $this->ipAddress = $ipAddress;
}
/**
diff --git a/app/Mail/RequestedNewPassword.php b/app/Mail/RequestedNewPassword.php
index b97a47c50f..bd2d9e90b0 100644
--- a/app/Mail/RequestedNewPassword.php
+++ b/app/Mail/RequestedNewPassword.php
@@ -10,7 +10,7 @@ class RequestedNewPassword extends Mailable
{
use Queueable, SerializesModels;
/** @var string */
- public $ip;
+ public $ipAddress;
/** @var string */
public $url;
@@ -18,12 +18,12 @@ class RequestedNewPassword extends Mailable
* RequestedNewPassword constructor.
*
* @param string $url
- * @param string $ip
+ * @param string $ipAddress
*/
- public function __construct(string $url, string $ip)
+ public function __construct(string $url, string $ipAddress)
{
- $this->url = $url;
- $this->ip = $ip;
+ $this->url = $url;
+ $this->ipAddress = $ipAddress;
}
/**
diff --git a/app/Models/Account.php b/app/Models/Account.php
index cfef7e34af..f6b4481c1d 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -95,7 +95,7 @@ class Account extends Model
/** @var Account $account */
foreach ($set as $account) {
- if ($account->name == $fields['name']) {
+ if ($account->name === $fields['name']) {
return $account;
}
}
@@ -116,7 +116,7 @@ class Account extends Model
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
@@ -187,7 +187,7 @@ class Account extends Model
public function getMeta(string $fieldName): string
{
foreach ($this->accountMeta as $meta) {
- if ($meta->name == $fieldName) {
+ if ($meta->name === $fieldName) {
return strval($meta->data);
}
}
diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php
index ddca8ad0d1..ce8eeec9fc 100644
--- a/app/Models/Attachment.php
+++ b/app/Models/Attachment.php
@@ -55,7 +55,7 @@ class Attachment extends Model
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
diff --git a/app/Models/Bill.php b/app/Models/Bill.php
index 4a858720e5..d879cbd8d3 100644
--- a/app/Models/Bill.php
+++ b/app/Models/Bill.php
@@ -62,7 +62,7 @@ class Bill extends Model
public static function routeBinder(Bill $value)
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
@@ -77,7 +77,7 @@ class Bill extends Model
public function getMatchAttribute($value)
{
- if (intval($this->match_encrypted) == 1) {
+ if (intval($this->match_encrypted) === 1) {
return Crypt::decrypt($value);
}
@@ -92,7 +92,7 @@ class Bill extends Model
public function getNameAttribute($value)
{
- if (intval($this->name_encrypted) == 1) {
+ if (intval($this->name_encrypted) === 1) {
return Crypt::decrypt($value);
}
diff --git a/app/Models/Budget.php b/app/Models/Budget.php
index d5f71f21ee..cbbaafab24 100644
--- a/app/Models/Budget.php
+++ b/app/Models/Budget.php
@@ -66,7 +66,7 @@ class Budget extends Model
$set = $query->get(['budgets.*']);
/** @var Budget $budget */
foreach ($set as $budget) {
- if ($budget->name == $fields['name']) {
+ if ($budget->name === $fields['name']) {
return $budget;
}
}
@@ -85,7 +85,7 @@ class Budget extends Model
public static function routeBinder(Budget $value)
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
diff --git a/app/Models/Category.php b/app/Models/Category.php
index fd7241702f..e10e384d35 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -67,7 +67,7 @@ class Category extends Model
$set = $query->get(['categories.*']);
/** @var Category $category */
foreach ($set as $category) {
- if ($category->name == $fields['name']) {
+ if ($category->name === $fields['name']) {
return $category;
}
}
@@ -86,7 +86,7 @@ class Category extends Model
public static function routeBinder(Category $value)
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php
index 2c7d12558c..bfd66f881f 100644
--- a/app/Models/CurrencyExchangeRate.php
+++ b/app/Models/CurrencyExchangeRate.php
@@ -50,4 +50,4 @@ class CurrencyExchangeRate extends Model
return $this->belongsTo(User::class);
}
-}
\ No newline at end of file
+}
diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php
index 66dfc167eb..dea6e581c9 100644
--- a/app/Models/ImportJob.php
+++ b/app/Models/ImportJob.php
@@ -42,11 +42,11 @@ class ImportJob extends Model
protected $validStatus
= [
- 'import_status_never_started', // initial state
- 'import_configuration_saved', // import configuration saved. This step is going to be obsolete.
- 'settings_complete', // aka: ready for import.
- 'import_running', // import currently underway
- 'import_complete', // done with everything
+ 'new',
+ 'initialized',
+ 'configured',
+ 'running',
+ 'finished',
];
/**
@@ -66,13 +66,28 @@ class ImportJob extends Model
throw new NotFoundHttpException;
}
+ /**
+ * @param int $index
+ * @param string $message
+ *
+ * @return bool
+ */
+ public function addError(int $index, string $message): bool
+ {
+ $extended = $this->extended_status;
+ $extended['errors'][$index][] = $message;
+ $this->extended_status = $extended;
+
+ return true;
+ }
+
/**
* @param int $count
*/
public function addStepsDone(int $count)
{
$status = $this->extended_status;
- $status['steps_done'] += $count;
+ $status['done'] += $count;
$this->extended_status = $status;
$this->save();
@@ -84,7 +99,7 @@ class ImportJob extends Model
public function addTotalSteps(int $count)
{
$status = $this->extended_status;
- $status['total_steps'] += $count;
+ $status['steps'] += $count;
$this->extended_status = $status;
$this->save();
@@ -109,7 +124,7 @@ class ImportJob extends Model
if (is_null($value)) {
return [];
}
- if (strlen($value) == 0) {
+ if (strlen($value) === 0) {
return [];
}
@@ -123,7 +138,7 @@ class ImportJob extends Model
*/
public function getExtendedStatusAttribute($value)
{
- if (strlen($value) == 0) {
+ if (strlen($value) === 0) {
return [];
}
@@ -165,7 +180,7 @@ class ImportJob extends Model
$disk = Storage::disk('upload');
$encryptedContent = $disk->get($fileName);
$content = Crypt::decrypt($encryptedContent);
- Log::debug(sprintf('Content size is %d bytes.', $content));
+ Log::debug(sprintf('Content size is %d bytes.', strlen($content)));
return $content;
}
diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php
index e648623ba4..e7659a80e8 100644
--- a/app/Models/PiggyBank.php
+++ b/app/Models/PiggyBank.php
@@ -58,7 +58,7 @@ class PiggyBank extends Model
public static function routeBinder(PiggyBank $value)
{
if (auth()->check()) {
- if ($value->account->user_id == auth()->user()->id) {
+ if ($value->account->user_id === auth()->user()->id) {
return $value;
}
}
diff --git a/app/Models/Rule.php b/app/Models/Rule.php
index 28637d2893..59d572c424 100644
--- a/app/Models/Rule.php
+++ b/app/Models/Rule.php
@@ -51,7 +51,7 @@ class Rule extends Model
public static function routeBinder(Rule $value)
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php
index 8ab7f6a117..1b62d9a1fe 100644
--- a/app/Models/RuleGroup.php
+++ b/app/Models/RuleGroup.php
@@ -52,7 +52,7 @@ class RuleGroup extends Model
public static function routeBinder(RuleGroup $value)
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
diff --git a/app/Models/Tag.php b/app/Models/Tag.php
index 30018b5e87..4ac3c24356 100644
--- a/app/Models/Tag.php
+++ b/app/Models/Tag.php
@@ -66,7 +66,7 @@ class Tag extends Model
$set = $query->get(['tags.*']);
/** @var Tag $tag */
foreach ($set as $tag) {
- if ($tag->tag == $fields['tag']) {
+ if ($tag->tag === $fields['tag']) {
return $tag;
}
}
@@ -87,7 +87,7 @@ class Tag extends Model
public static function routeBinder(Tag $value)
{
if (auth()->check()) {
- if ($value->user_id == auth()->user()->id) {
+ if ($value->user_id === auth()->user()->id) {
return $value;
}
}
@@ -133,6 +133,10 @@ class Tag extends Model
*/
public function getTagAttribute($value)
{
+ if (is_null($value)) {
+ return null;
+ }
+
return Crypt::decrypt($value);
}
diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php
index ef5deeadcd..c9b139d485 100644
--- a/app/Models/Transaction.php
+++ b/app/Models/Transaction.php
@@ -26,14 +26,13 @@ use Watson\Validating\ValidatingTrait;
*/
class Transaction extends Model
{
-
/**
* The attributes that should be casted to native types.
*
* @var array
*/
protected $casts
- = [
+ = [
'created_at' => 'date',
'updated_at' => 'date',
'deleted_at' => 'date',
@@ -41,17 +40,19 @@ class Transaction extends Model
'encrypted' => 'boolean', // model does not have these fields though
'bill_name_encrypted' => 'boolean',
];
- protected $dates = ['created_at', 'updated_at', 'deleted_at'];
- protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier'];
- protected $hidden = ['encrypted'];
+ protected $dates = ['created_at', 'updated_at', 'deleted_at'];
+ protected $fillable
+ = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier', 'transaction_currency_id', 'foreign_currency_id',
+ 'foreign_amount'];
+ protected $hidden = ['encrypted'];
protected $rules
- = [
- 'account_id' => 'required|exists:accounts,id',
- 'transaction_journal_id' => 'required|exists:transaction_journals,id',
- 'description' => 'between:0,1024',
- 'amount' => 'required|numeric',
+ = [
+ 'account_id' => 'required|exists:accounts,id',
+ 'transaction_journal_id' => 'required|exists:transaction_journals,id',
+ 'transaction_currency_id' => 'required|exists:transaction_currencies,id',
+ 'description' => 'between:0,1024',
+ 'amount' => 'required|numeric',
];
- use SoftDeletes, ValidatingTrait;
/**
* @param Builder $query
@@ -74,6 +75,8 @@ class Transaction extends Model
return false;
}
+ use SoftDeletes, ValidatingTrait;
+
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
@@ -98,6 +101,14 @@ class Transaction extends Model
return $this->belongsToMany('FireflyIII\Models\Category');
}
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function foreignCurrency()
+ {
+ return $this->belongsTo('FireflyIII\Models\TransactionCurrency', 'foreign_currency_id');
+ }
+
/**
* @param $value
*
@@ -160,6 +171,14 @@ class Transaction extends Model
$this->attributes['amount'] = strval(round($value, 12));
}
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function transactionCurrency()
+ {
+ return $this->belongsTo('FireflyIII\Models\TransactionCurrency');
+ }
+
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php
index 53506bc61d..977ef1ea90 100644
--- a/app/Models/TransactionJournal.php
+++ b/app/Models/TransactionJournal.php
@@ -19,6 +19,7 @@ use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Models\TransactionJournalTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Log;
@@ -66,13 +67,12 @@ class TransactionJournal extends Model
/** @var array */
protected $rules
= [
- 'user_id' => 'required|exists:users,id',
- 'transaction_type_id' => 'required|exists:transaction_types,id',
- 'transaction_currency_id' => 'required|exists:transaction_currencies,id',
- 'description' => 'required|between:1,1024',
- 'completed' => 'required|boolean',
- 'date' => 'required|date',
- 'encrypted' => 'required|boolean',
+ 'user_id' => 'required|exists:users,id',
+ 'transaction_type_id' => 'required|exists:transaction_types,id',
+ 'description' => 'required|between:1,1024',
+ 'completed' => 'required|boolean',
+ 'date' => 'required|date',
+ 'encrypted' => 'required|boolean',
];
/**
@@ -115,7 +115,7 @@ class TransactionJournal extends Model
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
- public function budgets()
+ public function budgets(): BelongsToMany
{
return $this->belongsToMany('FireflyIII\Models\Budget');
}
@@ -123,7 +123,7 @@ class TransactionJournal extends Model
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
- public function categories()
+ public function categories(): BelongsToMany
{
return $this->belongsToMany('FireflyIII\Models\Category');
}
@@ -204,10 +204,10 @@ class TransactionJournal extends Model
/**
* @return bool
*/
- public function isDeposit()
+ public function isDeposit(): bool
{
if (!is_null($this->transaction_type_type)) {
- return $this->transaction_type_type == TransactionType::DEPOSIT;
+ return $this->transaction_type_type === TransactionType::DEPOSIT;
}
return $this->transactionType->isDeposit();
@@ -217,10 +217,10 @@ class TransactionJournal extends Model
*
* @return bool
*/
- public function isOpeningBalance()
+ public function isOpeningBalance(): bool
{
if (!is_null($this->transaction_type_type)) {
- return $this->transaction_type_type == TransactionType::OPENING_BALANCE;
+ return $this->transaction_type_type === TransactionType::OPENING_BALANCE;
}
return $this->transactionType->isOpeningBalance();
@@ -230,10 +230,10 @@ class TransactionJournal extends Model
*
* @return bool
*/
- public function isTransfer()
+ public function isTransfer(): bool
{
if (!is_null($this->transaction_type_type)) {
- return $this->transaction_type_type == TransactionType::TRANSFER;
+ return $this->transaction_type_type === TransactionType::TRANSFER;
}
return $this->transactionType->isTransfer();
@@ -243,10 +243,10 @@ class TransactionJournal extends Model
*
* @return bool
*/
- public function isWithdrawal()
+ public function isWithdrawal(): bool
{
if (!is_null($this->transaction_type_type)) {
- return $this->transaction_type_type == TransactionType::WITHDRAWAL;
+ return $this->transaction_type_type === TransactionType::WITHDRAWAL;
}
return $this->transactionType->isWithdrawal();
@@ -255,7 +255,7 @@ class TransactionJournal extends Model
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
- public function piggyBankEvents()
+ public function piggyBankEvents(): HasMany
{
return $this->hasMany('FireflyIII\Models\PiggyBankEvent');
}
@@ -267,7 +267,7 @@ class TransactionJournal extends Model
*
* @return bool
*/
- public function save(array $options = [])
+ public function save(array $options = []): bool
{
$count = $this->tags()->count();
$this->tag_count = $count;
@@ -299,46 +299,6 @@ class TransactionJournal extends Model
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00'));
}
- /**
- * @param EloquentBuilder $query
- */
- public function scopeExpanded(EloquentBuilder $query)
- {
- // left join transaction type:
- if (!self::isJoined($query, 'transaction_types')) {
- $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
- }
-
- // left join transaction currency:
- $query->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id');
-
- // extend group by:
- $query->groupBy(
- [
- 'transaction_journals.id',
- 'transaction_journals.created_at',
- 'transaction_journals.updated_at',
- 'transaction_journals.deleted_at',
- 'transaction_journals.user_id',
- 'transaction_journals.transaction_type_id',
- 'transaction_journals.bill_id',
- 'transaction_journals.transaction_currency_id',
- 'transaction_journals.description',
- 'transaction_journals.date',
- 'transaction_journals.interest_date',
- 'transaction_journals.book_date',
- 'transaction_journals.process_date',
- 'transaction_journals.order',
- 'transaction_journals.tag_count',
- 'transaction_journals.encrypted',
- 'transaction_journals.completed',
- 'transaction_types.type',
- 'transaction_currencies.code',
- ]
- );
- $query->with(['categories', 'budgets', 'attachments', 'bill', 'transactions']);
- }
-
/**
* @param EloquentBuilder $query
*/
@@ -445,9 +405,9 @@ class TransactionJournal extends Model
}
/**
- * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ * @return HasMany
*/
- public function transactions()
+ public function transactions(): HasMany
{
return $this->hasMany('FireflyIII\Models\Transaction');
}
diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php
index ef4890bb7f..0e88f0dc8d 100644
--- a/app/Providers/FireflyServiceProvider.php
+++ b/app/Providers/FireflyServiceProvider.php
@@ -33,8 +33,6 @@ use FireflyIII\Helpers\Report\PopupReport;
use FireflyIII\Helpers\Report\PopupReportInterface;
use FireflyIII\Helpers\Report\ReportHelper;
use FireflyIII\Helpers\Report\ReportHelperInterface;
-use FireflyIII\Import\ImportProcedure;
-use FireflyIII\Import\ImportProcedureInterface;
use FireflyIII\Repositories\User\UserRepository;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Amount;
@@ -43,7 +41,7 @@ use FireflyIII\Support\FireflyConfig;
use FireflyIII\Support\Navigation;
use FireflyIII\Support\Preferences;
use FireflyIII\Support\Steam;
-use FireflyIII\Support\Twig\Account;
+use FireflyIII\Support\Twig\AmountFormat;
use FireflyIII\Support\Twig\General;
use FireflyIII\Support\Twig\Journal;
use FireflyIII\Support\Twig\PiggyBank;
@@ -51,11 +49,11 @@ use FireflyIII\Support\Twig\Rule;
use FireflyIII\Support\Twig\Transaction;
use FireflyIII\Support\Twig\Translation;
use FireflyIII\Validation\FireflyValidator;
+use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Twig;
use TwigBridge\Extension\Loader\Functions;
use Validator;
-use Illuminate\Foundation\Application;
/**
* Class FireflyServiceProvider
@@ -79,7 +77,7 @@ class FireflyServiceProvider extends ServiceProvider
Twig::addExtension(new Translation);
Twig::addExtension(new Transaction);
Twig::addExtension(new Rule);
- Twig::addExtension(new Account);
+ Twig::addExtension(new AmountFormat);
}
/**
@@ -139,7 +137,6 @@ class FireflyServiceProvider extends ServiceProvider
// other generators
$this->app->bind(ProcessorInterface::class, Processor::class);
- $this->app->bind(ImportProcedureInterface::class, ImportProcedure::class);
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
$this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class);
diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php
index 42ff742255..d221714c21 100644
--- a/app/Providers/JournalServiceProvider.php
+++ b/app/Providers/JournalServiceProvider.php
@@ -52,6 +52,9 @@ class JournalServiceProvider extends ServiceProvider
$this->registerCollector();
}
+ /**
+ *
+ */
private function registerCollector()
{
$this->app->bind(
@@ -69,6 +72,9 @@ class JournalServiceProvider extends ServiceProvider
);
}
+ /**
+ *
+ */
private function registerRepository()
{
$this->app->bind(
@@ -86,6 +92,9 @@ class JournalServiceProvider extends ServiceProvider
);
}
+ /**
+ *
+ */
private function registerTasker()
{
$this->app->bind(
@@ -102,4 +111,5 @@ class JournalServiceProvider extends ServiceProvider
}
);
}
+
}
diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php
index 423efcd23e..ba8c207939 100644
--- a/app/Repositories/Account/AccountRepository.php
+++ b/app/Repositories/Account/AccountRepository.php
@@ -24,9 +24,8 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
-use Illuminate\Database\Eloquent\Relations\HasMany;
-use Illuminate\Support\Collection;
use Log;
+use Validator;
/**
@@ -37,6 +36,7 @@ use Log;
*/
class AccountRepository implements AccountRepositoryInterface
{
+ use FindAccountsTrait;
/** @var User */
private $user;
@@ -77,192 +77,6 @@ class AccountRepository implements AccountRepositoryInterface
return true;
}
- /**
- * @param $accountId
- *
- * @return Account
- */
- public function find(int $accountId): Account
- {
- $account = $this->user->accounts()->find($accountId);
- if (is_null($account)) {
- return new Account;
- }
-
- return $account;
- }
-
- /**
- * @param string $number
- * @param array $types
- *
- * @return Account
- */
- public function findByAccountNumber(string $number, array $types): Account
- {
- $query = $this->user->accounts()
- ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
- ->where('account_meta.name', 'accountNumber')
- ->where('account_meta.data', json_encode($number));
-
- if (count($types) > 0) {
- $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
- $query->whereIn('account_types.type', $types);
- }
-
- /** @var Collection $accounts */
- $accounts = $query->get(['accounts.*']);
- if ($accounts->count() > 0) {
- return $accounts->first();
- }
-
- return new Account;
- }
-
- /**
- * @param string $iban
- * @param array $types
- *
- * @return Account
- */
- public function findByIban(string $iban, array $types): Account
- {
- $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
-
- if (count($types) > 0) {
- $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
- $query->whereIn('account_types.type', $types);
- }
-
- $accounts = $query->get(['accounts.*']);
- /** @var Account $account */
- foreach ($accounts as $account) {
- if ($account->iban === $iban) {
- return $account;
- }
- }
-
- return new Account;
- }
-
- /**
- * @param string $name
- * @param array $types
- *
- * @return Account
- */
- public function findByName(string $name, array $types): Account
- {
- $query = $this->user->accounts();
-
- if (count($types) > 0) {
- $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
- $query->whereIn('account_types.type', $types);
-
- }
- Log::debug(sprintf('Searching for account named %s of the following type(s)', $name), ['types' => $types]);
-
- $accounts = $query->get(['accounts.*']);
- /** @var Account $account */
- foreach ($accounts as $account) {
- if ($account->name === $name) {
- Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
-
- return $account;
- }
- }
- Log::debug('Found nothing.');
-
- return new Account;
- }
-
- /**
- * @param array $accountIds
- *
- * @return Collection
- */
- public function getAccountsById(array $accountIds): Collection
- {
- /** @var Collection $result */
- $query = $this->user->accounts();
-
- if (count($accountIds) > 0) {
- $query->whereIn('accounts.id', $accountIds);
- }
-
- $result = $query->get(['accounts.*']);
- $result = $result->sortBy(
- function (Account $account) {
- return strtolower($account->name);
- }
- );
-
- return $result;
- }
-
- /**
- * @param array $types
- *
- * @return Collection
- */
- public function getAccountsByType(array $types): Collection
- {
- /** @var Collection $result */
- $query = $this->user->accounts();
- if (count($types) > 0) {
- $query->accountTypeIn($types);
- }
-
- $result = $query->get(['accounts.*']);
- $result = $result->sortBy(
- function (Account $account) {
- return strtolower($account->name);
- }
- );
-
- return $result;
- }
-
- /**
- * @param array $types
- *
- * @return Collection
- */
- public function getActiveAccountsByType(array $types): Collection
- {
- /** @var Collection $result */
- $query = $this->user->accounts()->with(
- ['accountmeta' => function (HasMany $query) {
- $query->where('name', 'accountRole');
- }]
- );
- if (count($types) > 0) {
- $query->accountTypeIn($types);
- }
- $query->where('active', 1);
- $result = $query->get(['accounts.*']);
- $result = $result->sortBy(
- function (Account $account) {
- return strtolower($account->name);
- }
- );
-
- return $result;
- }
-
- /**
- * @return Account
- */
- public function getCashAccount(): Account
- {
- $type = AccountType::where('type', AccountType::CASH)->first();
- $account = Account::firstOrCreateEncrypted(
- ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1]
- );
-
- return $account;
- }
-
/**
* Returns the date of the very last transaction in this account.
*
@@ -365,7 +179,7 @@ class AccountRepository implements AccountRepositoryInterface
{
// update the account:
$account->name = $data['name'];
- $account->active = $data['active'] == '1' ? true : false;
+ $account->active = $data['active'] === '1' ? true : false;
$account->virtual_balance = $data['virtualBalance'];
$account->iban = $data['iban'];
$account->save();
@@ -423,29 +237,32 @@ class AccountRepository implements AccountRepositoryInterface
$data['accountType'] = $data['accountType'] ?? 'invalid';
$type = config('firefly.accountTypeByIdentifier.' . $data['accountType']);
$accountType = AccountType::whereType($type)->first();
-
+ $data['iban'] = $this->filterIban($data['iban']);
// verify account type
if (is_null($accountType)) {
throw new FireflyException(sprintf('Account type "%s" is invalid. Cannot create account.', $data['accountType']));
}
// account may exist already:
- $existingAccount = $this->findByName($data['name'], [$data['accountType']]);
+ $existingAccount = $this->findByName($data['name'], [$type]);
if (!is_null($existingAccount->id)) {
- throw new FireflyException(sprintf('There already is an account named "%s" of type "%s".', $data['name'], $data['accountType']));
+ Log::warning(sprintf('There already is an account named "%s" of type "%s".', $data['name'], $type));
+
+ return $existingAccount;
}
// create it:
- $newAccount = new Account(
- [
- 'user_id' => $this->user->id,
- 'account_type_id' => $accountType->id,
- 'name' => $data['name'],
- 'virtual_balance' => $data['virtualBalance'],
- 'active' => $data['active'] === true ? true : false,
- 'iban' => $data['iban'],
- ]
- );
+ $databaseData
+ = [
+ 'user_id' => $this->user->id,
+ 'account_type_id' => $accountType->id,
+ 'name' => $data['name'],
+ 'virtual_balance' => $data['virtualBalance'],
+ 'active' => $data['active'] === true ? true : false,
+ 'iban' => $data['iban'],
+ ];
+ $newAccount = new Account($databaseData);
+ Log::debug('Final account creation dataset', $databaseData);
$newAccount->save();
// verify its creation:
if (is_null($newAccount->id)) {
@@ -453,7 +270,9 @@ class AccountRepository implements AccountRepositoryInterface
sprintf('Could not create account "%s" (%d error(s))', $data['name'], $newAccount->getErrors()->count()), $newAccount->getErrors()->toArray()
);
throw new FireflyException(sprintf('Tried to create account named "%s" but failed. The logs have more details.', $data['name']));
+
}
+ Log::debug(sprintf('Created new account #%d named "%s" of type %s.', $newAccount->id, $newAccount->name, $accountType->type));
return $newAccount;
}
@@ -466,7 +285,12 @@ class AccountRepository implements AccountRepositoryInterface
*/
protected function storeInitialBalance(Account $account, array $data): TransactionJournal
{
- $amount = $data['openingBalance'];
+ $amount = strval($data['openingBalance']);
+
+ if (bccomp($amount, '0') === 0) {
+ return new TransactionJournal;
+ }
+
$name = $data['name'];
$currencyId = $data['currency_id'];
$opposing = $this->storeOpposingAccount($name);
@@ -487,18 +311,32 @@ class AccountRepository implements AccountRepositoryInterface
$firstAccount = $account;
$secondAccount = $opposing;
$firstAmount = $amount;
- $secondAmount = $amount * -1;
+ $secondAmount = bcmul($amount, '-1');
if ($data['openingBalance'] < 0) {
$firstAccount = $opposing;
$secondAccount = $account;
- $firstAmount = $amount * -1;
+ $firstAmount = bcmul($amount, '-1');
$secondAmount = $amount;
}
- $one = new Transaction(['account_id' => $firstAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $firstAmount]);
+ $one = new Transaction(
+ [
+ 'account_id' => $firstAccount->id,
+ 'transaction_journal_id' => $journal->id,
+ 'amount' => $firstAmount,
+ 'transaction_currency_id' => $currencyId,
+ ]
+ );
$one->save();// first transaction: from
- $two = new Transaction(['account_id' => $secondAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $secondAmount]);
+
+ $two = new Transaction(
+ [
+ 'account_id' => $secondAccount->id,
+ 'transaction_journal_id' => $journal->id,
+ 'amount' => $secondAmount,
+ 'transaction_currency_id' => $currencyId,]
+ );
$two->save(); // second transaction: to
Log::debug(sprintf('Stored two transactions, #%d and #%d', $one->id, $two->id));
@@ -606,9 +444,15 @@ class AccountRepository implements AccountRepositoryInterface
protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, array $data): bool
{
$date = $data['openingBalanceDate'];
- $amount = $data['openingBalance'];
+ $amount = strval($data['openingBalance']);
$currencyId = intval($data['currency_id']);
+ if (bccomp($amount, '0') === 0) {
+ $journal->delete();
+
+ return true;
+ }
+
// update date:
$journal->date = $date;
$journal->transaction_currency_id = $currencyId;
@@ -616,12 +460,14 @@ class AccountRepository implements AccountRepositoryInterface
// update transactions:
/** @var Transaction $transaction */
foreach ($journal->transactions()->get() as $transaction) {
- if ($account->id == $transaction->account_id) {
- $transaction->amount = $amount;
+ if ($account->id === $transaction->account_id) {
+ $transaction->amount = $amount;
+ $transaction->transaction_currency_id = $currencyId;
$transaction->save();
}
- if ($account->id != $transaction->account_id) {
- $transaction->amount = $amount * -1;
+ if ($account->id !== $transaction->account_id) {
+ $transaction->amount = bcmul($amount, '-1');
+ $transaction->transaction_currency_id = $currencyId;
$transaction->save();
}
}
@@ -631,6 +477,7 @@ class AccountRepository implements AccountRepositoryInterface
}
+
/**
* @param array $data
*
@@ -638,9 +485,7 @@ class AccountRepository implements AccountRepositoryInterface
*/
protected function validOpeningBalanceData(array $data): bool
{
- if (isset($data['openingBalance']) && isset($data['openingBalanceDate'])
- && bccomp(strval($data['openingBalance']), '0') !== 0
- ) {
+ if (isset($data['openingBalance']) && isset($data['openingBalanceDate'])) {
Log::debug('Array has valid opening balance data.');
return true;
@@ -649,4 +494,26 @@ class AccountRepository implements AccountRepositoryInterface
return false;
}
+
+ /**
+ * @param string $iban
+ *
+ * @return null|string
+ */
+ private function filterIban(string $iban = null)
+ {
+ if (is_null($iban)) {
+ return null;
+ }
+ $data = ['iban' => $iban];
+ $rules = ['iban' => 'required|iban'];
+ $validator = Validator::make($data, $rules);
+ if ($validator->fails()) {
+ Log::error(sprintf('Detected invalid IBAN ("%s"). Return NULL instead.', $iban));
+
+ return null;
+ }
+
+ return $iban;
+ }
}
diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php
index e444fcada0..a9e04b03d6 100644
--- a/app/Repositories/Account/AccountRepositoryInterface.php
+++ b/app/Repositories/Account/AccountRepositoryInterface.php
@@ -36,11 +36,6 @@ interface AccountRepositoryInterface
*/
public function count(array $types): int;
- /**
- * @return Account
- */
- public function getCashAccount(): Account;
-
/**
* Moved here from account CRUD.
*
@@ -103,6 +98,11 @@ interface AccountRepositoryInterface
*/
public function getActiveAccountsByType(array $types): Collection;
+ /**
+ * @return Account
+ */
+ public function getCashAccount(): Account;
+
/**
* Returns the date of the very last transaction in this account.
*
diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php
index ce2e437880..3759522c41 100644
--- a/app/Repositories/Account/AccountTasker.php
+++ b/app/Repositories/Account/AccountTasker.php
@@ -41,11 +41,10 @@ class AccountTasker implements AccountTaskerInterface
*/
public function getAccountReport(Collection $accounts, Carbon $start, Carbon $end): array
{
- $ids = $accounts->pluck('id')->toArray();
$yesterday = clone $start;
$yesterday->subDay();
- $startSet = Steam::balancesById($ids, $yesterday);
- $endSet = Steam::balancesById($ids, $end);
+ $startSet = Steam::balancesByAccounts($accounts, $yesterday);
+ $endSet = Steam::balancesByAccounts($accounts, $end);
Log::debug('Start of accountreport');
diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php
new file mode 100644
index 0000000000..e06a598a47
--- /dev/null
+++ b/app/Repositories/Account/FindAccountsTrait.php
@@ -0,0 +1,215 @@
+user->accounts()->find($accountId);
+ if (is_null($account)) {
+ return new Account;
+ }
+
+ return $account;
+ }
+
+ /**
+ * @param string $number
+ * @param array $types
+ *
+ * @return Account
+ */
+ public function findByAccountNumber(string $number, array $types): Account
+ {
+ $query = $this->user->accounts()
+ ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
+ ->where('account_meta.name', 'accountNumber')
+ ->where('account_meta.data', json_encode($number));
+
+ if (count($types) > 0) {
+ $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
+ $query->whereIn('account_types.type', $types);
+ }
+
+ /** @var Collection $accounts */
+ $accounts = $query->get(['accounts.*']);
+ if ($accounts->count() > 0) {
+ return $accounts->first();
+ }
+
+ return new Account;
+ }
+
+ /**
+ * @param string $iban
+ * @param array $types
+ *
+ * @return Account
+ */
+ public function findByIban(string $iban, array $types): Account
+ {
+ $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
+
+ if (count($types) > 0) {
+ $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
+ $query->whereIn('account_types.type', $types);
+ }
+
+ $accounts = $query->get(['accounts.*']);
+ /** @var Account $account */
+ foreach ($accounts as $account) {
+ if ($account->iban === $iban) {
+ return $account;
+ }
+ }
+
+ return new Account;
+ }
+
+ /**
+ * @param string $name
+ * @param array $types
+ *
+ * @return Account
+ */
+ public function findByName(string $name, array $types): Account
+ {
+ $query = $this->user->accounts();
+
+ if (count($types) > 0) {
+ $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
+ $query->whereIn('account_types.type', $types);
+
+ }
+ Log::debug(sprintf('Searching for account named "%s" (of user #%d) of the following type(s)', $name, $this->user->id), ['types' => $types]);
+
+ $accounts = $query->get(['accounts.*']);
+ /** @var Account $account */
+ foreach ($accounts as $account) {
+ if ($account->name === $name) {
+ Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
+
+ return $account;
+ }
+ }
+ Log::debug(sprintf('There is no account with name "%s" or types', $name), $types);
+
+ return new Account;
+ }
+
+ /**
+ * @param array $accountIds
+ *
+ * @return Collection
+ */
+ public function getAccountsById(array $accountIds): Collection
+ {
+ /** @var Collection $result */
+ $query = $this->user->accounts();
+
+ if (count($accountIds) > 0) {
+ $query->whereIn('accounts.id', $accountIds);
+ }
+
+ $result = $query->get(['accounts.*']);
+ $result = $result->sortBy(
+ function (Account $account) {
+ return strtolower($account->name);
+ }
+ );
+
+ return $result;
+ }
+
+ /**
+ * @param array $types
+ *
+ * @return Collection
+ */
+ public function getAccountsByType(array $types): Collection
+ {
+ /** @var Collection $result */
+ $query = $this->user->accounts();
+ if (count($types) > 0) {
+ $query->accountTypeIn($types);
+ }
+
+ $result = $query->get(['accounts.*']);
+ $result = $result->sortBy(
+ function (Account $account) {
+ return strtolower($account->name);
+ }
+ );
+
+ return $result;
+ }
+
+ /**
+ * @param array $types
+ *
+ * @return Collection
+ */
+ public function getActiveAccountsByType(array $types): Collection
+ {
+ /** @var Collection $result */
+ $query = $this->user->accounts()->with(
+ ['accountmeta' => function (HasMany $query) {
+ $query->where('name', 'accountRole');
+ }]
+ );
+ if (count($types) > 0) {
+ $query->accountTypeIn($types);
+ }
+ $query->where('active', 1);
+ $result = $query->get(['accounts.*']);
+ $result = $result->sortBy(
+ function (Account $account) {
+ return strtolower($account->name);
+ }
+ );
+
+ return $result;
+ }
+
+ /**
+ * @return Account
+ */
+ public function getCashAccount(): Account
+ {
+ $type = AccountType::where('type', AccountType::CASH)->first();
+ $account = Account::firstOrCreateEncrypted(
+ ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1]
+ );
+
+ return $account;
+ }
+}
diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php
index b7c174b103..91a3dcbd73 100644
--- a/app/Repositories/Attachment/AttachmentRepository.php
+++ b/app/Repositories/Attachment/AttachmentRepository.php
@@ -19,6 +19,7 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\User;
use Illuminate\Support\Collection;
+use Log;
use Storage;
/**
@@ -95,16 +96,20 @@ class AttachmentRepository implements AttachmentRepositoryInterface
public function getContent(Attachment $attachment): string
{
// create a disk.
- $disk = Storage::disk('upload');
- $file = $attachment->fileName();
+ $disk = Storage::disk('upload');
+ $file = $attachment->fileName();
+ $content = '';
if ($disk->exists($file)) {
$content = Crypt::decrypt($disk->get($file));
+ }
+ if (is_bool($content)) {
+ Log::error(sprintf('Attachment #%d may be corrupted: the content could not be decrypted.', $attachment->id));
- return $content;
+ return '';
}
- return '';
+ return $content;
}
/**
diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php
index 43599dc820..e6957abe4b 100644
--- a/app/Repositories/Bill/BillRepository.php
+++ b/app/Repositories/Bill/BillRepository.php
@@ -116,7 +116,7 @@ class BillRepository implements BillRepositoryInterface
$set = $set->sortBy(
function (Bill $bill) {
- $int = $bill->active == 1 ? 0 : 1;
+ $int = $bill->active === 1 ? 0 : 1;
return $int . strtolower($bill->name);
}
@@ -168,7 +168,7 @@ class BillRepository implements BillRepositoryInterface
$set = $set->sortBy(
function (Bill $bill) {
- $int = $bill->active == 1 ? 0 : 1;
+ $int = $bill->active === 1 ? 0 : 1;
return $int . strtolower($bill->name);
}
@@ -262,6 +262,8 @@ class BillRepository implements BillRepositoryInterface
}
/**
+ * The "paid dates" list is a list of dates of transaction journals that are linked to this bill.
+ *
* @param Bill $bill
* @param Carbon $start
* @param Carbon $end
@@ -498,7 +500,7 @@ class BillRepository implements BillRepositoryInterface
return true;
}
- if ($bill->id == $journal->bill_id) {
+ if ($bill->id === $journal->bill_id) {
// if no match, but bill used to match, remove it:
$journal->bill_id = null;
$journal->save();
diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php
index bef618f634..e0a9f262b7 100644
--- a/app/Repositories/Category/CategoryRepository.php
+++ b/app/Repositories/Category/CategoryRepository.php
@@ -160,42 +160,19 @@ class CategoryRepository implements CategoryRepositoryInterface
*/
public function lastUseDate(Category $category, Collection $accounts): Carbon
{
- $last = null;
+ $last = new Carbon('1900-01-01');
+ $lastJournalDate = $this->getLastJournalDate($category, $accounts);
- /** @var TransactionJournal $first */
- $lastJournalQuery = $category->transactionJournals()->orderBy('date', 'DESC');
-
- if ($accounts->count() > 0) {
- // filter journals:
- $ids = $accounts->pluck('id')->toArray();
- $lastJournalQuery->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id');
- $lastJournalQuery->whereIn('t.account_id', $ids);
+ if ($lastJournalDate->year !== 1900) {
+ $last = clone $lastJournalDate;
+ unset($lastJournalDate);
}
- $lastJournal = $lastJournalQuery->first(['transaction_journals.*']);
+ $lastTransactionDate = $this->getLastTransactionDate($category, $accounts);
- if ($lastJournal) {
- $last = $lastJournal->date;
- }
-
- // check transactions:
-
- $lastTransactionQuery = $category->transactions()
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->orderBy('transaction_journals.date', 'DESC');
- if ($accounts->count() > 0) {
- // filter journals:
- $ids = $accounts->pluck('id')->toArray();
- $lastTransactionQuery->whereIn('transactions.account_id', $ids);
- }
-
- $lastTransaction = $lastTransactionQuery->first(['transaction_journals.*']);
- if (!is_null($lastTransaction) && ((!is_null($last) && $lastTransaction->date < $last) || is_null($last))) {
- $last = new Carbon($lastTransaction->date);
- }
-
- if (is_null($last)) {
- return new Carbon('1900-01-01');
+ if ($lastTransactionDate->year !== 1900 && $lastTransactionDate < $last) {
+ $last = clone $lastTransactionDate;
+ unset($lastTransactionDate);
}
return $last;
@@ -479,4 +456,53 @@ class CategoryRepository implements CategoryRepositoryInterface
return $category;
}
+ /**
+ * @param Category $category
+ * @param Collection $accounts
+ *
+ * @return Carbon
+ */
+ private function getLastJournalDate(Category $category, Collection $accounts): Carbon
+ {
+ $query = $category->transactionJournals()->orderBy('date', 'DESC');
+
+ if ($accounts->count() > 0) {
+ $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id');
+ $query->whereIn('t.account_id', $accounts->pluck('id')->toArray());
+ }
+
+ $result = $query->first(['transaction_journals.*']);
+
+ if (!is_null($result)) {
+ return $result->date;
+ }
+
+ return new Carbon('1900-01-01');
+ }
+
+ /**
+ * @param Category $category
+ * @param Collection $accounts
+ *
+ * @return Carbon
+ */
+ private function getLastTransactionDate(Category $category, Collection $accounts): Carbon
+ {
+ // check transactions:
+ $query = $category->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->orderBy('transaction_journals.date', 'DESC');
+ if ($accounts->count() > 0) {
+ // filter journals:
+ $query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray());
+ }
+
+ $lastTransaction = $query->first(['transaction_journals.*']);
+ if (!is_null($lastTransaction)) {
+ return new Carbon($lastTransaction->date);
+ }
+
+ return new Carbon('1900-01-01');
+ }
+
}
diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php
index 6af984a06c..36bd597406 100644
--- a/app/Repositories/ImportJob/ImportJobRepository.php
+++ b/app/Repositories/ImportJob/ImportJobRepository.php
@@ -13,10 +13,16 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\ImportJob;
+use Crypt;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Support\Str;
+use Log;
+use SplFileObject;
+use Storage;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Class ImportJobRepository
@@ -51,13 +57,13 @@ class ImportJobRepository implements ImportJobRepositoryInterface
$importJob->user()->associate($this->user);
$importJob->file_type = $fileType;
$importJob->key = Str::random(12);
- $importJob->status = 'import_status_never_started';
+ $importJob->status = 'new';
+ $importJob->configuration = [];
$importJob->extended_status = [
- 'total_steps' => 0,
- 'steps_done' => 0,
- 'import_count' => 0,
- 'importTag' => 0,
- 'errors' => [],
+ 'steps' => 0,
+ 'done' => 0,
+ 'tag' => 0,
+ 'errors' => [],
];
$importJob->save();
@@ -86,6 +92,75 @@ class ImportJobRepository implements ImportJobRepositoryInterface
return $result;
}
+ /**
+ * @param ImportJob $job
+ * @param UploadedFile $file
+ *
+ * @return bool
+ */
+ public function processConfiguration(ImportJob $job, UploadedFile $file): bool
+ {
+ /** @var UserRepositoryInterface $repository */
+ $repository = app(UserRepositoryInterface::class);
+ // demo user's configuration upload is ignored completely.
+ if (!$repository->hasRole($this->user, 'demo')) {
+ Log::debug(
+ 'Uploaded configuration file', ['name' => $file->getClientOriginalName(), 'size' => $file->getSize(), 'mime' => $file->getClientMimeType()]
+ );
+
+ $configFileObject = new SplFileObject($file->getRealPath());
+ $configRaw = $configFileObject->fread($configFileObject->getSize());
+ $configuration = json_decode($configRaw, true);
+
+ if (!is_null($configuration) && is_array($configuration)) {
+ Log::debug('Found configuration', $configuration);
+ $this->setConfiguration($job, $configuration);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param ImportJob $job
+ * @param UploadedFile $file
+ *
+ * @return mixed
+ */
+ public function processFile(ImportJob $job, UploadedFile $file): bool
+ {
+ /** @var UserRepositoryInterface $repository */
+ $repository = app(UserRepositoryInterface::class);
+ $newName = sprintf('%s.upload', $job->key);
+ $uploaded = new SplFileObject($file->getRealPath());
+ $content = $uploaded->fread($uploaded->getSize());
+ $contentEncrypted = Crypt::encrypt($content);
+ $disk = Storage::disk('upload');
+
+
+ // user is demo user, replace upload with prepared file.
+ if ($repository->hasRole($this->user, 'demo')) {
+ $stubsDisk = Storage::disk('stubs');
+ $content = $stubsDisk->get('demo-import.csv');
+ $contentEncrypted = Crypt::encrypt($content);
+ $disk->put($newName, $contentEncrypted);
+ Log::debug('Replaced upload with demo file.');
+
+ // also set up prepared configuration.
+ $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true);
+ $this->setConfiguration($job, $configuration);
+ Log::debug('Set configuration for demo user', $configuration);
+ }
+
+ if (!$repository->hasRole($this->user, 'demo')) {
+ // user is not demo, process original upload:
+ $disk->put($newName, $contentEncrypted);
+ Log::debug('Uploaded file', ['name' => $file->getClientOriginalName(), 'size' => $file->getSize(), 'mime' => $file->getClientMimeType()]);
+ }
+
+ return true;
+ }
+
/**
* @param ImportJob $job
* @param array $configuration
diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php
index 5bdf636d42..bfdc0493b9 100644
--- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php
+++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php
@@ -15,6 +15,7 @@ namespace FireflyIII\Repositories\ImportJob;
use FireflyIII\Models\ImportJob;
use FireflyIII\User;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Interface ImportJobRepositoryInterface
@@ -37,6 +38,22 @@ interface ImportJobRepositoryInterface
*/
public function findByKey(string $key): ImportJob;
+ /**
+ * @param ImportJob $job
+ * @param UploadedFile $file
+ *
+ * @return bool
+ */
+ public function processConfiguration(ImportJob $job, UploadedFile $file): bool;
+
+ /**
+ * @param ImportJob $job
+ * @param UploadedFile $file
+ *
+ * @return mixed
+ */
+ public function processFile(ImportJob $job, UploadedFile $file): bool;
+
/**
* @param ImportJob $job
* @param array $configuration
diff --git a/app/Repositories/Journal/CreateJournalsTrait.php b/app/Repositories/Journal/CreateJournalsTrait.php
new file mode 100644
index 0000000000..44064b7432
--- /dev/null
+++ b/app/Repositories/Journal/CreateJournalsTrait.php
@@ -0,0 +1,191 @@
+ 0) {
+ $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
+ if (!is_null($tag)) {
+ Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
+ $tagRepository->connect($journal, $tag);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param Transaction $transaction
+ * @param int $budgetId
+ */
+ protected 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 Transaction $transaction
+ * @param string $category
+ */
+ protected 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);
+ }
+ }
+
+ /**
+ * The reference to storeAccounts() in this function is an indication of spagetti code but alas,
+ * I leave it as it is.
+ *
+ * @param TransactionJournal $journal
+ * @param array $transaction
+ * @param int $identifier
+ *
+ * @return Collection
+ */
+ protected function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection
+ {
+ // store source and destination accounts (depends on type)
+ $accounts = $this->storeAccounts($this->user, $journal->transactionType, $transaction);
+
+ // store transaction one way:
+ $amount = bcmul(strval($transaction['amount']), '-1');
+ $foreignAmount = is_null($transaction['foreign_amount']) ? null : bcmul(strval($transaction['foreign_amount']), '-1');
+ $one = $this->storeTransaction(
+ [
+ 'journal' => $journal,
+ 'account' => $accounts['source'],
+ 'amount' => $amount,
+ 'transaction_currency_id' => $transaction['transaction_currency_id'],
+ 'foreign_amount' => $foreignAmount,
+ 'foreign_currency_id' => $transaction['foreign_currency_id'],
+ 'description' => $transaction['description'],
+ 'category' => null,
+ 'budget' => null,
+ 'identifier' => $identifier,
+ ]
+ );
+ $this->storeCategoryWithTransaction($one, $transaction['category']);
+ $this->storeBudgetWithTransaction($one, $transaction['budget_id']);
+
+ // and the other way:
+ $amount = strval($transaction['amount']);
+ $foreignAmount = is_null($transaction['foreign_amount']) ? null : strval($transaction['foreign_amount']);
+ $two = $this->storeTransaction(
+ [
+ 'journal' => $journal,
+ 'account' => $accounts['destination'],
+ 'amount' => $amount,
+ 'transaction_currency_id' => $transaction['transaction_currency_id'],
+ 'foreign_amount' => $foreignAmount,
+ 'foreign_currency_id' => $transaction['foreign_currency_id'],
+ '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
+ */
+ protected function storeTransaction(array $data): Transaction
+ {
+ $fields = [
+ 'transaction_journal_id' => $data['journal']->id,
+ 'account_id' => $data['account']->id,
+ 'amount' => $data['amount'],
+ 'foreign_amount' => $data['foreign_amount'],
+ 'transaction_currency_id' => $data['transaction_currency_id'],
+ 'foreign_currency_id' => $data['foreign_currency_id'],
+ 'description' => $data['description'],
+ 'identifier' => $data['identifier'],
+ ];
+
+
+ if (is_null($data['foreign_currency_id'])) {
+ unset($fields['foreign_currency_id']);
+ }
+ if (is_null($data['foreign_amount'])) {
+ unset($fields['foreign_amount']);
+ }
+
+ /** @var Transaction $transaction */
+ $transaction = Transaction::create($fields);
+
+ 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;
+
+ }
+
+}
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index 98ab2dafb5..774ae77947 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -13,17 +13,9 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Journal;
-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\Tag;
-use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
-use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
@@ -37,14 +29,12 @@ use Preferences;
*/
class JournalRepository implements JournalRepositoryInterface
{
+ use CreateJournalsTrait, UpdateJournalsTrait, SupportJournalsTrait;
+
/** @var User */
private $user;
-
/** @var array */
- private $validMetaFields
- = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes', 'foreign_amount',
- 'foreign_currency_id',
- ];
+ private $validMetaFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes'];
/**
* @param TransactionJournal $journal
@@ -180,15 +170,14 @@ class JournalRepository implements JournalRepositoryInterface
// find transaction type.
/** @var TransactionType $transactionType */
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
- $accounts = $this->storeAccounts($transactionType, $data);
+ $accounts = $this->storeAccounts($this->user, $transactionType, $data);
$data = $this->verifyNativeAmount($data, $accounts);
- $currencyId = $data['currency_id'];
$amount = strval($data['amount']);
$journal = new TransactionJournal(
[
'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id,
- 'transaction_currency_id' => $currencyId,
+ 'transaction_currency_id' => $data['currency_id'], // no longer used.
'description' => $data['description'],
'completed' => 0,
'date' => $data['date'],
@@ -200,27 +189,32 @@ class JournalRepository implements JournalRepositoryInterface
$this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']);
-
// store two transactions:
$one = [
- 'journal' => $journal,
- 'account' => $accounts['source'],
- 'amount' => bcmul($amount, '-1'),
- 'description' => null,
- 'category' => null,
- 'budget' => null,
- 'identifier' => 0,
+ 'journal' => $journal,
+ 'account' => $accounts['source'],
+ 'amount' => bcmul($amount, '-1'),
+ 'transaction_currency_id' => $data['currency_id'],
+ 'foreign_amount' => is_null($data['foreign_amount']) ? null : bcmul(strval($data['foreign_amount']), '-1'),
+ 'foreign_currency_id' => $data['foreign_currency_id'],
+ 'description' => null,
+ 'category' => null,
+ 'budget' => null,
+ 'identifier' => 0,
];
$this->storeTransaction($one);
$two = [
- 'journal' => $journal,
- 'account' => $accounts['destination'],
- 'amount' => $amount,
- 'description' => null,
- 'category' => null,
- 'budget' => null,
- 'identifier' => 0,
+ 'journal' => $journal,
+ 'account' => $accounts['destination'],
+ 'amount' => $amount,
+ 'transaction_currency_id' => $data['currency_id'],
+ 'foreign_amount' => $data['foreign_amount'],
+ 'foreign_currency_id' => $data['foreign_currency_id'],
+ 'description' => null,
+ 'category' => null,
+ 'budget' => null,
+ 'identifier' => 0,
];
$this->storeTransaction($two);
@@ -256,20 +250,12 @@ class JournalRepository implements JournalRepositoryInterface
{
// update actual journal:
- $journal->description = $data['description'];
- $journal->date = $data['date'];
- $accounts = $this->storeAccounts($journal->transactionType, $data);
- $amount = strval($data['amount']);
-
- if ($data['currency_id'] !== $journal->transaction_currency_id) {
- // user has entered amount in foreign currency.
- // amount in "our" currency is $data['exchanged_amount']:
- $amount = strval($data['exchanged_amount']);
- // other values must be stored as well:
- $data['original_amount'] = $data['amount'];
- $data['original_currency_id'] = $data['currency_id'];
-
- }
+ $journal->description = $data['description'];
+ $journal->date = $data['date'];
+ $accounts = $this->storeAccounts($this->user, $journal->transactionType, $data);
+ $data = $this->verifyNativeAmount($data, $accounts);
+ $data['amount'] = strval($data['amount']);
+ $data['foreign_amount'] = is_null($data['foreign_amount']) ? null : strval($data['foreign_amount']);
// unlink all categories, recreate them:
$journal->categories()->detach();
@@ -278,9 +264,11 @@ class JournalRepository implements JournalRepositoryInterface
$this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']);
+ // negative because source loses money.
+ $this->updateSourceTransaction($journal, $accounts['source'], $data);
- $this->updateSourceTransaction($journal, $accounts['source'], bcmul($amount, '-1')); // negative because source loses money.
- $this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money.
+ // positive because destination gets money.
+ $this->updateDestinationTransaction($journal, $accounts['destination'], $data);
$journal->save();
@@ -317,9 +305,8 @@ class JournalRepository implements JournalRepositoryInterface
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->description = $data['journal_description'];
+ $journal->date = $data['date'];
$journal->save();
Log::debug(sprintf('Updated split journal #%d', $journal->id));
@@ -339,7 +326,6 @@ class JournalRepository implements JournalRepositoryInterface
}
}
-
// update tags:
if (isset($data['tags']) && is_array($data['tags'])) {
$this->updateTags($journal, $data['tags']);
@@ -351,6 +337,7 @@ class JournalRepository implements JournalRepositoryInterface
// store each transaction.
$identifier = 0;
Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions'])));
+
foreach ($data['transactions'] as $transaction) {
Log::debug(sprintf('Split journal update split transaction %d', $identifier));
$transaction = $this->appendTransactionData($transaction, $data);
@@ -362,466 +349,4 @@ class JournalRepository implements JournalRepositoryInterface
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.
- * an advancePayment takes at most one expense, infinite deposits and NO transfers.
- *
- * @param TransactionJournal $journal
- * @param array $array
- *
- * @return bool
- */
- private function saveTags(TransactionJournal $journal, array $array): bool
- {
- /** @var TagRepositoryInterface $tagRepository */
- $tagRepository = app(TagRepositoryInterface::class);
-
- foreach ($array as $name) {
- if (strlen(trim($name)) > 0) {
- $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
- if (!is_null($tag)) {
- Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
- $tagRepository->connect($journal, $tag);
- }
- }
- }
-
- return true;
- }
-
- /**
- * @param TransactionType $type
- * @param array $data
- *
- * @return array
- * @throws FireflyException
- */
- private function storeAccounts(TransactionType $type, array $data): array
- {
- $accounts = [
- 'source' => null,
- 'destination' => null,
- ];
-
- Log::debug(sprintf('Going to store accounts for type %s', $type->type));
- switch ($type->type) {
- case TransactionType::WITHDRAWAL:
- $accounts = $this->storeWithdrawalAccounts($data);
- break;
-
- case TransactionType::DEPOSIT:
- $accounts = $this->storeDepositAccounts($data);
-
- break;
- case TransactionType::TRANSFER:
- $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(sprintf('Did not recognise transaction type "%s".', $type->type));
- }
-
- 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 $accounts;
- }
-
- /**
- * @param TransactionJournal $journal
- * @param int $budgetId
- */
- private function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId)
- {
- if (intval($budgetId) > 0 && $journal->transactionType->type === TransactionType::WITHDRAWAL) {
- /** @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);
- }
- }
-
- /**
- * @param array $data
- *
- * @return array
- */
- private function storeDepositAccounts(array $data): array
- {
- Log::debug('Now in storeDepositAccounts().');
- $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
-
- Log::debug(sprintf('Destination account is #%d ("%s")', $destinationAccount->id, $destinationAccount->name));
-
- if (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]
- );
-
- Log::debug(sprintf('source account name is "%s", account is %d', $data['source_account_name'], $sourceAccount->id));
-
- return [
- 'source' => $sourceAccount,
- 'destination' => $destinationAccount,
- ];
- }
-
- Log::debug('source_account_name is empty, so default to cash account!');
-
- $sourceType = AccountType::where('type', AccountType::CASH)->first();
- $sourceAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
- );
-
- 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;
-
- }
-
- /**
- * @param array $data
- *
- * @return array
- */
- private function storeWithdrawalAccounts(array $data): array
- {
- Log::debug('Now in storeWithdrawalAccounts().');
- $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
-
- Log::debug(sprintf('Source account is #%d ("%s")', $sourceAccount->id, $sourceAccount->name));
-
- if (strlen($data['destination_account_name']) > 0) {
- $destinationType = AccountType::where('type', AccountType::EXPENSE)->first();
- $destinationAccount = Account::firstOrCreateEncrypted(
- [
- 'user_id' => $this->user->id,
- 'account_type_id' => $destinationType->id,
- 'name' => $data['destination_account_name'],
- 'active' => 1,
- ]
- );
-
- Log::debug(sprintf('destination account name is "%s", account is %d', $data['destination_account_name'], $destinationAccount->id));
-
- return [
- 'source' => $sourceAccount,
- 'destination' => $destinationAccount,
- ];
- }
- Log::debug('destination_account_name is empty, so default to cash account!');
- $destinationType = AccountType::where('type', AccountType::CASH)->first();
- $destinationAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
- );
-
- 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();
-
-
- }
-
- /**
- * @param TransactionJournal $journal
- * @param array $array
- *
- * @return bool
- */
- private function updateTags(TransactionJournal $journal, array $array): bool
- {
- // create tag repository
- /** @var TagRepositoryInterface $tagRepository */
- $tagRepository = app(TagRepositoryInterface::class);
-
-
- // find or create all tags:
- $tags = [];
- $ids = [];
- foreach ($array as $name) {
- if (strlen(trim($name)) > 0) {
- $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
- $tags[] = $tag;
- $ids[] = $tag->id;
- }
- }
-
- // delete all tags connected to journal not in this array:
- if (count($ids) > 0) {
- DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete();
- }
- // if count is zero, delete them all:
- if (count($ids) == 0) {
- DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete();
- }
-
- // connect each tag to journal (if not yet connected):
- /** @var Tag $tag */
- foreach ($tags as $tag) {
- Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
- $tagRepository->connect($journal, $tag);
- }
-
- return true;
- }
-
- /**
- * This method checks the data array and the given accounts to verify that the native amount, currency
- * and possible the foreign currency and amount are properly saved.
- *
- * @param array $data
- * @param array $accounts
- *
- * @return array
- * @throws FireflyException
- */
- private function verifyNativeAmount(array $data, array $accounts): array
- {
- /** @var TransactionType $transactionType */
- $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
- $submittedCurrencyId = $data['currency_id'];
-
- // which account to check for what the native currency is?
- $check = 'source';
- if ($transactionType->type === TransactionType::DEPOSIT) {
- $check = 'destination';
- }
- switch ($transactionType->type) {
- case TransactionType::DEPOSIT:
- case TransactionType::WITHDRAWAL:
- // continue:
- $nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id'));
-
- // does not match? Then user has submitted amount in a foreign currency:
- if ($nativeCurrencyId !== $submittedCurrencyId) {
- // store amount and submitted currency in "foreign currency" fields:
- $data['foreign_amount'] = $data['amount'];
- $data['foreign_currency_id'] = $submittedCurrencyId;
-
- // overrule the amount and currency ID fields to be the original again:
- $data['amount'] = strval($data['native_amount']);
- $data['currency_id'] = $nativeCurrencyId;
- }
- break;
- case TransactionType::TRANSFER:
- // source gets the original amount.
- $data['amount'] = strval($data['source_amount']);
- $data['currency_id'] = intval($accounts['source']->getMeta('currency_id'));
- $data['foreign_amount'] = strval($data['destination_amount']);
- $data['foreign_currency_id'] = intval($accounts['destination']->getMeta('currency_id'));
- break;
- default:
- throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type));
- }
-
- return $data;
- }
}
diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php
index b56abe0db8..5aacaa94ac 100644
--- a/app/Repositories/Journal/JournalTasker.php
+++ b/app/Repositories/Journal/JournalTasker.php
@@ -81,6 +81,8 @@ class JournalTasker implements JournalTaskerInterface
->leftJoin('account_types as source_account_types', 'source_accounts.account_type_id', '=', 'source_account_types.id')
->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
->leftJoin('account_types as destination_account_types', 'destination_accounts.account_type_id', '=', 'destination_account_types.id')
+ ->leftJoin('transaction_currencies as native_currencies', 'transactions.transaction_currency_id', '=', 'native_currencies.id')
+ ->leftJoin('transaction_currencies as foreign_currencies', 'transactions.foreign_currency_id', '=', 'foreign_currencies.id')
->where('transactions.amount', '<', 0)
->whereNull('transactions.deleted_at')
->get(
@@ -91,12 +93,23 @@ class JournalTasker implements JournalTaskerInterface
'source_accounts.encrypted as account_encrypted',
'source_account_types.type as account_type',
'transactions.amount',
+ 'transactions.foreign_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',
'destination_account_types.type as destination_account_type',
+ 'native_currencies.id as transaction_currency_id',
+ 'native_currencies.decimal_places as transaction_currency_dp',
+ 'native_currencies.code as transaction_currency_code',
+ 'native_currencies.symbol as transaction_currency_symbol',
+
+ 'foreign_currencies.id as foreign_currency_id',
+ 'foreign_currencies.decimal_places as foreign_currency_dp',
+ 'foreign_currencies.code as foreign_currency_code',
+ 'foreign_currencies.symbol as foreign_currency_symbol',
+
]
);
@@ -109,23 +122,33 @@ class JournalTasker implements JournalTaskerInterface
$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' => Steam::decrypt(intval($entry->account_encrypted), $entry->account_name),
- 'source_account_type' => $entry->account_type,
- '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_type' => $entry->destination_account_type,
- 'destination_account_name' => Steam::decrypt(intval($entry->destination_account_encrypted), $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,
+ 'source_id' => $entry->id,
+ 'source_amount' => $entry->amount,
+ 'foreign_source_amount' => $entry->foreign_amount,
+ 'description' => $entry->description,
+ 'source_account_id' => $entry->account_id,
+ 'source_account_name' => Steam::decrypt(intval($entry->account_encrypted), $entry->account_name),
+ 'source_account_type' => $entry->account_type,
+ 'source_account_before' => $sourceBalance,
+ 'source_account_after' => bcadd($sourceBalance, $entry->amount),
+ 'destination_id' => $entry->destination_id,
+ 'destination_amount' => bcmul($entry->amount, '-1'),
+ 'foreign_destination_amount' => is_null($entry->foreign_amount) ? null : bcmul($entry->foreign_amount, '-1'),
+ 'destination_account_id' => $entry->destination_account_id,
+ 'destination_account_type' => $entry->destination_account_type,
+ 'destination_account_name' => Steam::decrypt(intval($entry->destination_account_encrypted), $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,
+ 'transaction_currency_id' => $entry->transaction_currency_id,
+ 'transaction_currency_code' => $entry->transaction_currency_code,
+ 'transaction_currency_symbol' => $entry->transaction_currency_symbol,
+ 'transaction_currency_dp' => $entry->transaction_currency_dp,
+ 'foreign_currency_id' => $entry->foreign_currency_id,
+ 'foreign_currency_code' => $entry->foreign_currency_code,
+ 'foreign_currency_symbol' => $entry->foreign_currency_symbol,
+ 'foreign_currency_dp' => $entry->foreign_currency_dp,
];
if ($entry->destination_account_type === AccountType::CASH) {
$transaction['destination_account_name'] = '';
diff --git a/app/Repositories/Journal/SupportJournalsTrait.php b/app/Repositories/Journal/SupportJournalsTrait.php
new file mode 100644
index 0000000000..df57879971
--- /dev/null
+++ b/app/Repositories/Journal/SupportJournalsTrait.php
@@ -0,0 +1,246 @@
+ null,
+ 'destination' => null,
+ ];
+
+ Log::debug(sprintf('Going to store accounts for type %s', $type->type));
+ switch ($type->type) {
+ case TransactionType::WITHDRAWAL:
+ $accounts = $this->storeWithdrawalAccounts($user, $data);
+ break;
+
+ case TransactionType::DEPOSIT:
+ $accounts = $this->storeDepositAccounts($user, $data);
+
+ break;
+ case TransactionType::TRANSFER:
+ $accounts['source'] = Account::where('user_id', $user->id)->where('id', $data['source_account_id'])->first();
+ $accounts['destination'] = Account::where('user_id', $user->id)->where('id', $data['destination_account_id'])->first();
+ break;
+ default:
+ throw new FireflyException(sprintf('Did not recognise transaction type "%s".', $type->type));
+ }
+
+ 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 $accounts;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param int $budgetId
+ */
+ protected function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId)
+ {
+ if (intval($budgetId) > 0 && $journal->transactionType->type === TransactionType::WITHDRAWAL) {
+ /** @var \FireflyIII\Models\Budget $budget */
+ $budget = Budget::find($budgetId);
+ $journal->budgets()->save($budget);
+ }
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param string $category
+ */
+ protected 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 User $user
+ * @param array $data
+ *
+ * @return array
+ */
+ protected function storeDepositAccounts(User $user, array $data): array
+ {
+ Log::debug('Now in storeDepositAccounts().');
+ $destinationAccount = Account::where('user_id', $user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
+
+ Log::debug(sprintf('Destination account is #%d ("%s")', $destinationAccount->id, $destinationAccount->name));
+
+ if (strlen($data['source_account_name']) > 0) {
+ $sourceType = AccountType::where('type', 'Revenue account')->first();
+ $sourceAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
+ );
+
+ Log::debug(sprintf('source account name is "%s", account is %d', $data['source_account_name'], $sourceAccount->id));
+
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
+ }
+
+ Log::debug('source_account_name is empty, so default to cash account!');
+
+ $sourceType = AccountType::where('type', AccountType::CASH)->first();
+ $sourceAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
+ );
+
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
+ }
+
+ /**
+ * @param User $user
+ * @param array $data
+ *
+ * @return array
+ */
+ protected function storeWithdrawalAccounts(User $user, array $data): array
+ {
+ Log::debug('Now in storeWithdrawalAccounts().');
+ $sourceAccount = Account::where('user_id', $user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
+
+ Log::debug(sprintf('Source account is #%d ("%s")', $sourceAccount->id, $sourceAccount->name));
+
+ if (strlen($data['destination_account_name']) > 0) {
+ $destinationType = AccountType::where('type', AccountType::EXPENSE)->first();
+ $destinationAccount = Account::firstOrCreateEncrypted(
+ [
+ 'user_id' => $user->id,
+ 'account_type_id' => $destinationType->id,
+ 'name' => $data['destination_account_name'],
+ 'active' => 1,
+ ]
+ );
+
+ Log::debug(sprintf('destination account name is "%s", account is %d', $data['destination_account_name'], $destinationAccount->id));
+
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
+ }
+ Log::debug('destination_account_name is empty, so default to cash account!');
+ $destinationType = AccountType::where('type', AccountType::CASH)->first();
+ $destinationAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
+ );
+
+ return [
+ 'source' => $sourceAccount,
+ 'destination' => $destinationAccount,
+ ];
+ }
+
+ /**
+ * This method checks the data array and the given accounts to verify that the native amount, currency
+ * and possible the foreign currency and amount are properly saved.
+ *
+ * @param array $data
+ * @param array $accounts
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ protected function verifyNativeAmount(array $data, array $accounts): array
+ {
+ /** @var TransactionType $transactionType */
+ $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
+ $submittedCurrencyId = $data['currency_id'];
+ $data['foreign_amount'] = null;
+ $data['foreign_currency_id'] = null;
+
+ // which account to check for what the native currency is?
+ $check = 'source';
+ if ($transactionType->type === TransactionType::DEPOSIT) {
+ $check = 'destination';
+ }
+ switch ($transactionType->type) {
+ case TransactionType::DEPOSIT:
+ case TransactionType::WITHDRAWAL:
+ // continue:
+ $nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id'));
+
+ // does not match? Then user has submitted amount in a foreign currency:
+ if ($nativeCurrencyId !== $submittedCurrencyId) {
+ // store amount and submitted currency in "foreign currency" fields:
+ $data['foreign_amount'] = $data['amount'];
+ $data['foreign_currency_id'] = $submittedCurrencyId;
+
+ // overrule the amount and currency ID fields to be the original again:
+ $data['amount'] = strval($data['native_amount']);
+ $data['currency_id'] = $nativeCurrencyId;
+ }
+ break;
+ case TransactionType::TRANSFER:
+ $sourceCurrencyId = intval($accounts['source']->getMeta('currency_id'));
+ $destinationCurrencyId = intval($accounts['destination']->getMeta('currency_id'));
+ $data['amount'] = strval($data['source_amount']);
+ $data['currency_id'] = intval($accounts['source']->getMeta('currency_id'));
+
+ if ($sourceCurrencyId !== $destinationCurrencyId) {
+ // accounts have different id's, save this info:
+ $data['foreign_amount'] = strval($data['destination_amount']);
+ $data['foreign_currency_id'] = $destinationCurrencyId;
+ }
+
+ break;
+ default:
+ throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type));
+ }
+
+ return $data;
+ }
+}
diff --git a/app/Repositories/Journal/UpdateJournalsTrait.php b/app/Repositories/Journal/UpdateJournalsTrait.php
new file mode 100644
index 0000000000..d4264818a6
--- /dev/null
+++ b/app/Repositories/Journal/UpdateJournalsTrait.php
@@ -0,0 +1,156 @@
+transactions()->where('amount', '>', 0)->get();
+ if ($set->count() !== 1) {
+ throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
+ }
+ /** @var Transaction $transaction */
+ $transaction = $set->first();
+ $transaction->amount = app('steam')->positive($data['amount']);
+ $transaction->transaction_currency_id = $data['currency_id'];
+ $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : app('steam')->positive($data['foreign_amount']);
+ $transaction->foreign_currency_id = $data['foreign_currency_id'];
+ $transaction->account_id = $account->id;
+ $transaction->save();
+
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param Account $account
+ * @param array $data
+ *
+ * @throws FireflyException
+ */
+ protected function updateSourceTransaction(TransactionJournal $journal, Account $account, array $data)
+ {
+ // should be one:
+ $set = $journal->transactions()->where('amount', '<', 0)->get();
+ if ($set->count() !== 1) {
+ throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
+ }
+ /** @var Transaction $transaction */
+ $transaction = $set->first();
+ $transaction->amount = bcmul(app('steam')->positive($data['amount']), '-1');
+ $transaction->transaction_currency_id = $data['currency_id'];
+ $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : bcmul(app('steam')->positive($data['foreign_amount']), '-1');
+ $transaction->foreign_currency_id = $data['foreign_currency_id'];
+ $transaction->account_id = $account->id;
+ $transaction->save();
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param array $array
+ *
+ * @return bool
+ */
+ protected function updateTags(TransactionJournal $journal, array $array): bool
+ {
+ // create tag repository
+ /** @var TagRepositoryInterface $tagRepository */
+ $tagRepository = app(TagRepositoryInterface::class);
+
+
+ // find or create all tags:
+ $tags = [];
+ $ids = [];
+ foreach ($array as $name) {
+ if (strlen(trim($name)) > 0) {
+ $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
+ $tags[] = $tag;
+ $ids[] = $tag->id;
+ }
+ }
+
+ // delete all tags connected to journal not in this array:
+ if (count($ids) > 0) {
+ DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete();
+ }
+ // if count is zero, delete them all:
+ if (count($ids) === 0) {
+ DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete();
+ }
+
+ // connect each tag to journal (if not yet connected):
+ /** @var Tag $tag */
+ foreach ($tags as $tag) {
+ Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
+ $tagRepository->connect($journal, $tag);
+ }
+
+ return true;
+ }
+}
diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php
index e721c71b63..6c05daa897 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepository.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepository.php
@@ -240,10 +240,12 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
*/
public function getPiggyBanksWithAmount(): Collection
{
- $set = $this->getPiggyBanks();
+ $currency = Amount::getDefaultCurrency();
+ $set = $this->getPiggyBanks();
foreach ($set as $piggy) {
$currentAmount = $piggy->currentRelevantRep()->currentamount ?? '0';
- $piggy->name = $piggy->name . ' (' . Amount::format($currentAmount, false) . ')';
+
+ $piggy->name = $piggy->name . ' (' . Amount::formatAnything($currency, $currentAmount, false) . ')';
}
return $set;
diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php
index 47a848427c..5d3a1f2d47 100644
--- a/app/Repositories/Rule/RuleRepository.php
+++ b/app/Repositories/Rule/RuleRepository.php
@@ -236,7 +236,7 @@ class RuleRepository implements RuleRepositoryInterface
$rule->rule_group_id = $data['rule_group_id'];
$rule->order = ($order + 1);
$rule->active = 1;
- $rule->stop_processing = intval($data['stop_processing']) == 1;
+ $rule->stop_processing = intval($data['stop_processing']) === 1;
$rule->title = $data['title'];
$rule->description = strlen($data['description']) > 0 ? $data['description'] : null;
diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php
index 63ee5b408b..581fb72ebf 100644
--- a/app/Repositories/Tag/TagRepository.php
+++ b/app/Repositories/Tag/TagRepository.php
@@ -47,7 +47,7 @@ class TagRepository implements TagRepositoryInterface
* Already connected:
*/
if ($journal->tags()->find($tag->id)) {
- Log::error(sprintf('Cannot find tag #%d', $tag->id));
+ Log::info(sprintf('Tag #%d is already connected to journal #%d.', $tag->id, $journal->id));
return false;
}
@@ -282,7 +282,7 @@ class TagRepository implements TagRepositoryInterface
* changed to an advancePayment.
*/
- if ($tag->tagMode == 'balancingAct' || $tag->tagMode == 'nothing') {
+ if ($tag->tagMode === 'balancingAct' || $tag->tagMode === 'nothing') {
foreach ($tag->transactionjournals as $journal) {
if ($journal->isTransfer()) {
return false;
@@ -394,7 +394,7 @@ class TagRepository implements TagRepositoryInterface
}
// if already has transaction journals, must match ALL asset account id's:
- if ($deposits > 0 || $withdrawals == 1) {
+ if ($deposits > 0 || $withdrawals === 1) {
Log::debug('Need to match all asset accounts.');
return $this->matchAll($journal, $tag);
diff --git a/app/Rules/Actions/RemoveTag.php b/app/Rules/Actions/RemoveTag.php
index ca9912f5f0..525579f0ed 100644
--- a/app/Rules/Actions/RemoveTag.php
+++ b/app/Rules/Actions/RemoveTag.php
@@ -52,7 +52,7 @@ class RemoveTag implements ActionInterface
/** @var Tag $tag */
$tag = $journal->user->tags()->get()->filter(
function (Tag $tag) use ($name) {
- return $tag->tag == $name;
+ return $tag->tag === $name;
}
)->first();
diff --git a/app/Rules/Actions/SetBudget.php b/app/Rules/Actions/SetBudget.php
index 62b530b8c8..599f0214da 100644
--- a/app/Rules/Actions/SetBudget.php
+++ b/app/Rules/Actions/SetBudget.php
@@ -56,7 +56,7 @@ class SetBudget implements ActionInterface
$budgets = $repository->getActiveBudgets();
$budget = $budgets->filter(
function (Budget $current) use ($search) {
- return $current->name == $search;
+ return $current->name === $search;
}
)->first();
if (is_null($budget)) {
@@ -65,7 +65,7 @@ class SetBudget implements ActionInterface
return true;
}
- if ($journal->transactionType->type == TransactionType::TRANSFER) {
+ if ($journal->transactionType->type === TransactionType::TRANSFER) {
Log::debug(sprintf('RuleAction SetBudget could not set budget of journal #%d to "%s" because journal is a transfer.', $journal->id, $search));
return true;
diff --git a/app/Rules/Processor.php b/app/Rules/Processor.php
index 579521af61..ae0ecb1fd8 100644
--- a/app/Rules/Processor.php
+++ b/app/Rules/Processor.php
@@ -254,7 +254,7 @@ final class Processor
}
}
- $result = ($hitTriggers == $foundTriggers && $foundTriggers > 0);
+ $result = ($hitTriggers === $foundTriggers && $foundTriggers > 0);
Log::debug('Result of triggered()', ['hitTriggers' => $hitTriggers, 'foundTriggers' => $foundTriggers, 'result' => $result]);
return $result;
diff --git a/app/Rules/Triggers/HasAttachment.php b/app/Rules/Triggers/HasAttachment.php
index 4fc3bd720f..6fc86c6a6c 100644
--- a/app/Rules/Triggers/HasAttachment.php
+++ b/app/Rules/Triggers/HasAttachment.php
@@ -58,4 +58,4 @@ class HasAttachment extends AbstractTrigger implements TriggerInterface
return false;
}
-}
\ No newline at end of file
+}
diff --git a/app/Services/Currency/ExchangeRateInterface.php b/app/Services/Currency/ExchangeRateInterface.php
index a37133db9a..69a8040a5d 100644
--- a/app/Services/Currency/ExchangeRateInterface.php
+++ b/app/Services/Currency/ExchangeRateInterface.php
@@ -35,4 +35,4 @@ interface ExchangeRateInterface
*/
public function setUser(User $user);
-}
\ No newline at end of file
+}
diff --git a/app/Services/Currency/FixerIO.php b/app/Services/Currency/FixerIO.php
index 50a63036fa..7a3290bb96 100644
--- a/app/Services/Currency/FixerIO.php
+++ b/app/Services/Currency/FixerIO.php
@@ -61,9 +61,11 @@ class FixerIO implements ExchangeRateInterface
/**
* @param User $user
+ *
+ * @return mixed|void
*/
public function setUser(User $user)
{
$this->user = $user;
}
-}
\ No newline at end of file
+}
diff --git a/app/Support/Amount.php b/app/Support/Amount.php
index 32e42cf3bb..071c3e48b6 100644
--- a/app/Support/Amount.php
+++ b/app/Support/Amount.php
@@ -14,9 +14,11 @@ declare(strict_types=1);
namespace FireflyIII\Support;
use FireflyIII\Exceptions\FireflyException;
-use FireflyIII\Models\Transaction;
+use FireflyIII\Models\Transaction as TransactionModel;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
+use FireflyIII\Models\TransactionType;
+use FireflyIII\User;
use Illuminate\Support\Collection;
use Preferences as Prefs;
@@ -27,6 +29,7 @@ use Preferences as Prefs;
*/
class Amount
{
+
/**
* bool $sepBySpace is $localeconv['n_sep_by_space']
* int $signPosn = $localeconv['n_sign_posn']
@@ -101,17 +104,6 @@ class Amount
return $format;
}
- /**
- * @param string $amount
- * @param bool $coloured
- *
- * @return string
- */
- public function format(string $amount, bool $coloured = true): string
- {
- return $this->formatAnything($this->getDefaultCurrency(), $amount, $coloured);
- }
-
/**
* This method will properly format the given number, in color or "black and white",
* as a currency, given two things: the currency required and the current locale.
@@ -159,49 +151,6 @@ class Amount
return $result;
}
- /**
- * Used in many places (unfortunately).
- *
- * @param string $currencyCode
- * @param string $amount
- * @param bool $coloured
- *
- * @return string
- */
- public function formatByCode(string $currencyCode, string $amount, bool $coloured = true): string
- {
- $currency = TransactionCurrency::where('code', $currencyCode)->first();
-
- return $this->formatAnything($currency, $amount, $coloured);
- }
-
- /**
- *
- * @param \FireflyIII\Models\TransactionJournal $journal
- * @param bool $coloured
- *
- * @return string
- */
- public function formatJournal(TransactionJournal $journal, bool $coloured = true): string
- {
- $currency = $journal->transactionCurrency;
-
- return $this->formatAnything($currency, $journal->amount(), $coloured);
- }
-
- /**
- * @param Transaction $transaction
- * @param bool $coloured
- *
- * @return string
- */
- public function formatTransaction(Transaction $transaction, bool $coloured = true)
- {
- $currency = $transaction->transactionJournal->transactionCurrency;
-
- return $this->formatAnything($currency, strval($transaction->amount), $coloured);
- }
-
/**
* @return Collection
*/
@@ -256,17 +205,31 @@ class Amount
}
/**
- * @return TransactionCurrency
+ * @return \FireflyIII\Models\TransactionCurrency
* @throws FireflyException
*/
public function getDefaultCurrency(): TransactionCurrency
+ {
+ $user = auth()->user();
+
+ return $this->getDefaultCurrencyByUser($user);
+ }
+
+ /**
+ * @param User $user
+ *
+ * @return \FireflyIII\Models\TransactionCurrency
+ * @throws FireflyException
+ */
+ public function getDefaultCurrencyByUser(User $user): TransactionCurrency
{
$cache = new CacheProperties;
$cache->addProperty('getDefaultCurrency');
+ $cache->addProperty($user->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
- $currencyPreference = Prefs::get('currencyPreference', config('firefly.default_currency', 'EUR'));
+ $currencyPreference = Prefs::getForUser($user, 'currencyPreference', config('firefly.default_currency', 'EUR'));
$currency = TransactionCurrency::where('code', $currencyPreference->data)->first();
if (is_null($currency)) {
throw new FireflyException(sprintf('No currency found with code "%s"', $currencyPreference->data));
@@ -295,4 +258,84 @@ class Amount
'zero' => $positive,
];
}
+
+ /**
+ * @param TransactionJournal $journal
+ * @param bool $coloured
+ *
+ * @return string
+ */
+ public function journalAmount(TransactionJournal $journal, bool $coloured = true): string
+ {
+ $amounts = [];
+ $transactions = $journal->transactions()->where('amount', '>', 0)->get();
+ /** @var TransactionModel $transaction */
+ foreach ($transactions as $transaction) {
+ // model some fields to fit "transactionAmount()":
+ $transaction->transaction_amount = $transaction->amount;
+ $transaction->transaction_foreign_amount = $transaction->foreign_amount;
+ $transaction->transaction_type_type = $journal->transactionType->type;
+ $transaction->transaction_currency_symbol = $transaction->transactionCurrency->symbol;
+ $transaction->transaction_currency_dp = $transaction->transactionCurrency->decimal_places;
+ if (!is_null($transaction->foreign_currency_id)) {
+ $transaction->foreign_currency_symbol = $transaction->foreignCurrency->symbol;
+ $transaction->foreign_currency_dp = $transaction->foreignCurrency->decimal_places;
+ }
+
+ $amounts[] = $this->transactionAmount($transaction, $coloured);
+ }
+
+ return join(' / ', $amounts);
+
+ }
+
+ /**
+ * This formats a transaction, IF that transaction has been "collected" using the JournalCollector.
+ *
+ * @param TransactionModel $transaction
+ * @param bool $coloured
+ *
+ * @return string
+ */
+ public function transactionAmount(TransactionModel $transaction, bool $coloured = true): string
+ {
+ $amount = bcmul(app('steam')->positive(strval($transaction->transaction_amount)), '-1');
+ $format = '%s';
+
+ if ($transaction->transaction_type_type === TransactionType::DEPOSIT) {
+ $amount = bcmul($amount, '-1');
+ }
+
+ if ($transaction->transaction_type_type === TransactionType::TRANSFER) {
+ $amount = app('steam')->positive($amount);
+ $coloured = false;
+ $format = '%s';
+ }
+ if($transaction->transaction_type_type === TransactionType::OPENING_BALANCE) {
+ $amount = strval($transaction->transaction_amount);
+ }
+
+ $currency = new TransactionCurrency;
+ $currency->symbol = $transaction->transaction_currency_symbol;
+ $currency->decimal_places = $transaction->transaction_currency_dp;
+ $str = sprintf($format, $this->formatAnything($currency, $amount, $coloured));
+
+
+ if (!is_null($transaction->transaction_foreign_amount)) {
+ $amount = strval($transaction->transaction_foreign_amount);
+
+ if ($transaction->transaction_type_type === TransactionType::TRANSFER) {
+ $amount = app('steam')->positive($amount);
+ $coloured = false;
+ $format = '%s';
+ }
+
+ $currency = new TransactionCurrency;
+ $currency->symbol = $transaction->foreign_currency_symbol;
+ $currency->decimal_places = $transaction->foreign_currency_dp;
+ $str .= ' (' . sprintf($format, $this->formatAnything($currency, $amount, $coloured)) . ')';
+ }
+
+ return $str;
+ }
}
diff --git a/app/Support/Binder/CurrencyCode.php b/app/Support/Binder/CurrencyCode.php
index 4616ce5ef2..39b0a73915 100644
--- a/app/Support/Binder/CurrencyCode.php
+++ b/app/Support/Binder/CurrencyCode.php
@@ -36,4 +36,4 @@ class CurrencyCode implements BinderInterface
}
throw new NotFoundHttpException;
}
-}
\ No newline at end of file
+}
diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php
index cc31681323..3b5678f886 100644
--- a/app/Support/Binder/JournalList.php
+++ b/app/Support/Binder/JournalList.php
@@ -37,15 +37,8 @@ class JournalList implements BinderInterface
$ids = explode(',', $value);
/** @var \Illuminate\Support\Collection $object */
$object = TransactionJournal::whereIn('transaction_journals.id', $ids)
- ->expanded()
->where('transaction_journals.user_id', auth()->user()->id)
- ->get(
- [
- 'transaction_journals.*',
- 'transaction_types.type AS transaction_type_type',
- 'transaction_currencies.code AS transaction_currency_code',
- ]
- );
+ ->get(['transaction_journals.*',]);
if ($object->count() > 0) {
return $object;
diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php
index 555fe427b0..d83e8e73a6 100644
--- a/app/Support/CacheProperties.php
+++ b/app/Support/CacheProperties.php
@@ -75,7 +75,7 @@ class CacheProperties
*/
public function has(): bool
{
- if (getenv('APP_ENV') == 'testing') {
+ if (getenv('APP_ENV') === 'testing') {
return false;
}
$this->md5();
@@ -119,8 +119,6 @@ class CacheProperties
$this->md5 .= json_encode($property);
}
- Log::debug(sprintf('Cache string is %s', $this->md5));
$this->md5 = md5($this->md5);
- Log::debug(sprintf('Cache MD5 is %s', $this->md5));
}
}
diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php
index dab2ed286d..c1dc2fc174 100644
--- a/app/Support/ExpandedForm.php
+++ b/app/Support/ExpandedForm.php
@@ -565,6 +565,19 @@ class ExpandedForm
unset($options['currency']);
unset($options['placeholder']);
+ // perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
+ $preFilled = session('preFilled');
+ $key = 'amount_currency_id_' . $name;
+ $sentCurrencyId = isset($preFilled[$key]) ? intval($preFilled[$key]) : $defaultCurrency->id;
+
+ // find this currency in set of currencies:
+ foreach ($currencies as $currency) {
+ if ($currency->id === $sentCurrencyId) {
+ $defaultCurrency = $currency;
+ break;
+ }
+ }
+
// make sure value is formatted nicely:
if (!is_null($value) && $value !== '') {
$value = round($value, $defaultCurrency->decimal_places);
diff --git a/app/Support/Import/Configuration/ConfigurationInterface.php b/app/Support/Import/Configuration/ConfigurationInterface.php
new file mode 100644
index 0000000000..7f13ebe167
--- /dev/null
+++ b/app/Support/Import/Configuration/ConfigurationInterface.php
@@ -0,0 +1,46 @@
+getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
+ $delimiters = [
+ ',' => trans('form.csv_comma'),
+ ';' => trans('form.csv_semicolon'),
+ 'tab' => trans('form.csv_tab'),
+ ];
+
+ $specifics = [];
+
+ // collect specifics.
+ foreach (config('csv.import_specifics') as $name => $className) {
+ $specifics[$name] = [
+ 'name' => $className::getName(),
+ 'description' => $className::getDescription(),
+ ];
+ }
+
+ $data = [
+ 'accounts' => ExpandedForm::makeSelectList($accounts),
+ 'specifix' => [],
+ 'delimiters' => $delimiters,
+ 'specifics' => $specifics,
+ ];
+
+ return $data;
+ }
+
+ /**
+ * @param ImportJob $job
+ *
+ * @return ConfigurationInterface
+ */
+ public function setJob(ImportJob $job): ConfigurationInterface
+ {
+ $this->job = $job;
+
+ return $this;
+ }
+
+ /**
+ * Store the result.
+ *
+ * @param array $data
+ *
+ * @return bool
+ */
+ public function storeConfiguration(array $data): bool
+ {
+ /** @var AccountRepositoryInterface $repository */
+ $repository = app(AccountRepositoryInterface::class);
+ $importId = $data['csv_import_account'] ?? 0;
+ $account = $repository->find(intval($importId));
+
+ $hasHeaders = isset($data['has_headers']) && intval($data['has_headers']) === 1 ? true : false;
+ $config = $this->job->configuration;
+ $config['initial-config-complete'] = true;
+ $config['has-headers'] = $hasHeaders;
+ $config['date-format'] = $data['date_format'];
+ $config['delimiter'] = $data['csv_delimiter'];
+ $config['delimiter'] = $config['delimiter'] === 'tab' ? "\t" : $config['delimiter'];
+
+ Log::debug('Entered import account.', ['id' => $importId]);
+
+
+ if (!is_null($account->id)) {
+ Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]);
+ $config['import-account'] = $account->id;
+ }
+
+ if (is_null($account->id)) {
+ Log::error('Could not find anything for csv_import_account.', ['id' => $importId]);
+ }
+
+ $config = $this->storeSpecifics($data, $config);
+ $this->job->configuration = $config;
+ $this->job->save();
+
+ return true;
+ }
+
+ /**
+ * @param array $data
+ * @param array $config
+ *
+ * @return array
+ */
+ private function storeSpecifics(array $data, array $config): array
+ {
+ // loop specifics.
+ if (isset($data['specifics']) && is_array($data['specifics'])) {
+ foreach ($data['specifics'] as $name => $enabled) {
+ // verify their content.
+ $className = sprintf('FireflyIII\Import\Specifics\%s', $name);
+ if (class_exists($className)) {
+ $config['specifics'][$name] = 1;
+ }
+ }
+ }
+
+ return $config;
+ }
+}
diff --git a/app/Support/Import/Configuration/Csv/Map.php b/app/Support/Import/Configuration/Csv/Map.php
new file mode 100644
index 0000000000..f9cae51a0c
--- /dev/null
+++ b/app/Support/Import/Configuration/Csv/Map.php
@@ -0,0 +1,270 @@
+configuration = $this->job->configuration;
+ $this->getMappableColumns();
+
+ // in order to actually map we also need all possible values from the CSV file.
+ $content = $this->job->uploadFileContents();
+ /** @var Reader $reader */
+ $reader = Reader::createFromString($content);
+ $reader->setDelimiter($this->configuration['delimiter']);
+ $offset = $this->configuration['has-headers'] ? 1 : 0;
+ $results = $reader->setOffset($offset)->fetch();
+ $this->validSpecifics = array_keys(config('csv.import_specifics'));
+ $indexes = array_keys($this->data);
+ $rowIndex = 0;
+ foreach ($results as $rowIndex => $row) {
+
+ $row = $this->runSpecifics($row);
+
+ //do something here
+
+ foreach ($indexes as $index) { // this is simply 1, 2, 3, etc.
+ if (!isset($row[$index])) {
+ // don't really know how to handle this. Just skip, for now.
+ continue;
+ }
+ $value = $row[$index];
+ if (strlen($value) > 0) {
+
+ // we can do some preprocessing here,
+ // which is exclusively to fix the tags:
+ if (!is_null($this->data[$index]['preProcessMap']) && strlen($this->data[$index]['preProcessMap']) > 0) {
+ /** @var PreProcessorInterface $preProcessor */
+ $preProcessor = app($this->data[$index]['preProcessMap']);
+ $result = $preProcessor->run($value);
+ $this->data[$index]['values'] = array_merge($this->data[$index]['values'], $result);
+
+ Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]);
+ Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]);
+ Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $this->data[$index]['values']]);
+
+
+ continue;
+ }
+
+ $this->data[$index]['values'][] = $value;
+ }
+ }
+ }
+ foreach ($this->data as $index => $entry) {
+ $this->data[$index]['values'] = array_unique($this->data[$index]['values']);
+ asort($this->data[$index]['values']);
+ }
+
+ // save number of rows, thus number of steps, in job:
+ $steps = $rowIndex * 5;
+ $extended = $this->job->extended_status;
+ $extended['steps'] = $steps;
+ $this->job->extended_status = $extended;
+ $this->job->save();
+
+ return $this->data;
+
+ }
+
+ /**
+ * @param ImportJob $job
+ *
+ * @return ConfigurationInterface
+ */
+ public function setJob(ImportJob $job): ConfigurationInterface
+ {
+ $this->job = $job;
+
+ return $this;
+ }
+
+ /**
+ * Store the result.
+ *
+ * @param array $data
+ *
+ * @return bool
+ */
+ public function storeConfiguration(array $data): bool
+ {
+ $config = $this->job->configuration;
+
+ foreach ($data['mapping'] as $index => $data) {
+ $config['column-mapping-config'][$index] = [];
+ foreach ($data as $value => $mapId) {
+ $mapId = intval($mapId);
+ if ($mapId !== 0) {
+ $config['column-mapping-config'][$index][$value] = intval($mapId);
+ }
+ }
+ }
+
+ // set thing to be completed.
+ $config['column-mapping-complete'] = true;
+ $this->job->configuration = $config;
+ $this->job->save();
+
+ return true;
+ }
+
+ /**
+ * @param string $column
+ *
+ * @return MapperInterface
+ */
+ private function createMapper(string $column): MapperInterface
+ {
+ $mapperClass = config('csv.import_roles.' . $column . '.mapper');
+ $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass);
+ /** @var MapperInterface $mapper */
+ $mapper = new $mapperName;
+
+ return $mapper;
+ }
+
+ /**
+ * @return bool
+ */
+ private function getMappableColumns(): bool
+ {
+ $config = $this->job->configuration;
+
+ /**
+ * @var int $index
+ * @var bool $mustBeMapped
+ */
+ foreach ($config['column-do-mapping'] as $index => $mustBeMapped) {
+ $column = $this->validateColumnName($config['column-roles'][$index] ?? '_ignore');
+ $shouldMap = $this->shouldMapColumn($column, $mustBeMapped);
+ if ($shouldMap) {
+
+
+ // create configuration entry for this specific column and add column to $this->data array for later processing.
+ $this->data[$index] = [
+ 'name' => $column,
+ 'index' => $index,
+ 'options' => $this->createMapper($column)->getMap(),
+ 'preProcessMap' => $this->getPreProcessorName($column),
+ 'values' => [],
+ ];
+ }
+ }
+
+ return true;
+
+ }
+
+ /**
+ * @param string $column
+ *
+ * @return string
+ */
+ private function getPreProcessorName(string $column): string
+ {
+ $name = '';
+ $hasPreProcess = config('csv.import_roles.' . $column . '.pre-process-map');
+ $preProcessClass = config('csv.import_roles.' . $column . '.pre-process-mapper');
+
+ if (!is_null($hasPreProcess) && $hasPreProcess === true && !is_null($preProcessClass)) {
+ $name = sprintf('\\FireflyIII\\Import\\MapperPreProcess\\%s', $preProcessClass);
+ }
+
+ return $name;
+ }
+
+ /**
+ * @param array $row
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ private function runSpecifics(array $row): array
+ {
+ // run specifics here:
+ // and this is the point where the specifix go to work.
+ foreach ($this->configuration['specifics'] as $name => $enabled) {
+
+ if (!in_array($name, $this->validSpecifics)) {
+ throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
+ }
+ $class = config('csv.import_specifics.' . $name);
+ /** @var SpecificInterface $specific */
+ $specific = app($class);
+
+ // it returns the row, possibly modified:
+ $row = $specific->run($row);
+ }
+
+ return $row;
+ }
+
+ /**
+ * @param string $column
+ * @param bool $mustBeMapped
+ *
+ * @return bool
+ */
+ private function shouldMapColumn(string $column, bool $mustBeMapped): bool
+ {
+ $canBeMapped = config('csv.import_roles.' . $column . '.mappable');
+
+ return ($canBeMapped && $mustBeMapped);
+ }
+
+ /**
+ * @param string $column
+ *
+ * @return string
+ * @throws FireflyException
+ */
+ private function validateColumnName(string $column): string
+ {
+ // is valid column?
+ $validColumns = array_keys(config('csv.import_roles'));
+ if (!in_array($column, $validColumns)) {
+ throw new FireflyException(sprintf('"%s" is not a valid column.', $column));
+ }
+
+ return $column;
+ }
+}
diff --git a/app/Support/Import/Configuration/Csv/Roles.php b/app/Support/Import/Configuration/Csv/Roles.php
new file mode 100644
index 0000000000..445628aeab
--- /dev/null
+++ b/app/Support/Import/Configuration/Csv/Roles.php
@@ -0,0 +1,267 @@
+job->configuration;
+ $content = $this->job->uploadFileContents();
+
+ // create CSV reader.
+ $reader = Reader::createFromString($content);
+ $reader->setDelimiter($config['delimiter']);
+ $start = $config['has-headers'] ? 1 : 0;
+ $end = $start + config('csv.example_rows');
+
+ // set data:
+ $this->data = [
+ 'examples' => [],
+ 'roles' => $this->getRoles(),
+ 'total' => 0,
+ 'headers' => $config['has-headers'] ? $reader->fetchOne(0) : [],
+ ];
+
+ while ($start < $end) {
+ $row = $reader->fetchOne($start);
+ $row = $this->processSpecifics($row);
+ $count = count($row);
+ $this->data['total'] = $count > $this->data['total'] ? $count : $this->data['total'];
+ $this->processRow($row);
+ $start++;
+ }
+
+ $this->updateColumCount();
+ $this->makeExamplesUnique();
+
+ return $this->data;
+ }
+
+ /**
+ * @param ImportJob $job
+ *
+ * @return ConfigurationInterface
+ */
+ public function setJob(ImportJob $job): ConfigurationInterface
+ {
+ $this->job = $job;
+
+ return $this;
+ }
+
+ /**
+ * Store the result.
+ *
+ * @param array $data
+ *
+ * @return bool
+ */
+ public function storeConfiguration(array $data): bool
+ {
+ Log::debug('Now in storeConfiguration of Roles.');
+ $config = $this->job->configuration;
+ $count = $config['column-count'];
+ for ($i = 0; $i < $count; $i++) {
+ $role = $data['role'][$i] ?? '_ignore';
+ $mapping = isset($data['map'][$i]) && $data['map'][$i] === '1' ? true : false;
+ $config['column-roles'][$i] = $role;
+ $config['column-do-mapping'][$i] = $mapping;
+ Log::debug(sprintf('Column %d has been given role %s', $i, $role));
+ }
+
+
+ $this->job->configuration = $config;
+ $this->job->save();
+
+ $this->ignoreUnmappableColumns();
+ $this->setRolesComplete();
+ $this->isMappingNecessary();
+
+
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ private function getRoles(): array
+ {
+ $roles = [];
+ foreach (array_keys(config('csv.import_roles')) as $role) {
+ $roles[$role] = trans('csv.column_' . $role);
+ }
+
+ return $roles;
+
+ }
+
+ /**
+ * @return bool
+ */
+ private function ignoreUnmappableColumns(): bool
+ {
+ $config = $this->job->configuration;
+ $count = $config['column-count'];
+ for ($i = 0; $i < $count; $i++) {
+ $role = $config['column-roles'][$i] ?? '_ignore';
+ $mapping = $config['column-do-mapping'][$i] ?? false;
+
+ if ($role === '_ignore' && $mapping === true) {
+ $mapping = false;
+ Log::debug(sprintf('Column %d has type %s so it cannot be mapped.', $i, $role));
+ }
+ $config['column-do-mapping'][$i] = $mapping;
+ }
+
+ $this->job->configuration = $config;
+ $this->job->save();
+
+ return true;
+
+ }
+
+ /**
+ * @return bool
+ */
+ private function isMappingNecessary()
+ {
+ $config = $this->job->configuration;
+ $count = $config['column-count'];
+ $toBeMapped = 0;
+ for ($i = 0; $i < $count; $i++) {
+ $mapping = $config['column-do-mapping'][$i] ?? false;
+ if ($mapping === true) {
+ $toBeMapped++;
+ }
+ }
+ Log::debug(sprintf('Found %d columns that need mapping.', $toBeMapped));
+ if ($toBeMapped === 0) {
+ // skip setting of map, because none need to be mapped:
+ $config['column-mapping-complete'] = true;
+ $this->job->configuration = $config;
+ $this->job->save();
+ }
+
+ return true;
+ }
+
+ /**
+ * make unique example data
+ */
+ private function makeExamplesUnique(): bool
+ {
+ foreach ($this->data['examples'] as $index => $values) {
+ $this->data['examples'][$index] = array_unique($values);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $row
+ *
+ * @return bool
+ */
+ private function processRow(array $row): bool
+ {
+ foreach ($row as $index => $value) {
+ $value = trim($value);
+ if (strlen($value) > 0) {
+ $this->data['examples'][$index][] = $value;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * run specifics here:
+ * and this is the point where the specifix go to work.
+ *
+ * @param array $row
+ *
+ * @return array
+ */
+ private function processSpecifics(array $row): array
+ {
+ foreach ($this->job->configuration['specifics'] as $name => $enabled) {
+ /** @var SpecificInterface $specific */
+ $specific = app('FireflyIII\Import\Specifics\\' . $name);
+ $row = $specific->run($row);
+ }
+
+ return $row;
+ }
+
+ /**
+ * @return bool
+ */
+ private function setRolesComplete(): bool
+ {
+ $config = $this->job->configuration;
+ $count = $config['column-count'];
+ $assigned = 0;
+ $hasAmount = false;
+ for ($i = 0; $i < $count; $i++) {
+ $role = $config['column-roles'][$i] ?? '_ignore';
+ if ($role !== '_ignore') {
+ $assigned++;
+ }
+ if ($role === 'amount') {
+ $hasAmount = true;
+ }
+ }
+ if ($assigned > 0 && $hasAmount) {
+ $config['column-roles-complete'] = true;
+ $this->job->configuration = $config;
+ $this->job->save();
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ private function updateColumCount(): bool
+ {
+ $config = $this->job->configuration;
+ $count = $this->data['total'];
+ $config['column-count'] = $count;
+ $this->job->configuration = $config;
+ $this->job->save();
+
+ return true;
+ }
+}
diff --git a/app/Support/Models/TransactionJournalTrait.php b/app/Support/Models/TransactionJournalTrait.php
index c9cea3ec49..99f87b8684 100644
--- a/app/Support/Models/TransactionJournalTrait.php
+++ b/app/Support/Models/TransactionJournalTrait.php
@@ -15,16 +15,21 @@ namespace FireflyIII\Support\Models;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionJournalMeta;
+use FireflyIII\Models\TransactionType;
use FireflyIII\Support\CacheProperties;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
/**
* Class TransactionJournalTrait
*
- * @property int $id
- * @method Collection transactions()
- * @method bool isWithdrawal()
+ * @property int $id
+ * @property Carbon $date
+ * @property string $transaction_type_type
+ * @property TransactionType $transactionType
*
* @package FireflyIII\Support\Models
*/
@@ -91,6 +96,16 @@ trait TransactionJournalTrait
return 0;
}
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ abstract public function budgets(): BelongsToMany;
+
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ abstract public function categories(): BelongsToMany;
+
/**
* @return string
*/
@@ -180,6 +195,19 @@ trait TransactionJournalTrait
return $list;
}
+ /**
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ abstract public function getMeta(string $name);
+
+ /**
+ * @return bool
+ */
+ abstract public function isDeposit(): bool;
+
/**
* @param Builder $query
* @param string $table
@@ -201,6 +229,29 @@ trait TransactionJournalTrait
return false;
}
+ /**
+ *
+ * @return bool
+ */
+ abstract public function isOpeningBalance(): bool;
+
+ /**
+ *
+ * @return bool
+ */
+ abstract public function isTransfer(): bool;
+
+ /**
+ *
+ * @return bool
+ */
+ abstract public function isWithdrawal(): bool;
+
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ abstract public function piggyBankEvents(): HasMany;
+
/**
* @return int
*/
@@ -213,6 +264,31 @@ trait TransactionJournalTrait
return 0;
}
+ /**
+ * @return Transaction
+ */
+ public function positiveTransaction(): Transaction
+ {
+ return $this->transactions()->where('amount', '>', 0)->first();
+ }
+
+ /**
+ * Save the model to the database.
+ *
+ * @param array $options
+ *
+ * @return bool
+ */
+ abstract public function save(array $options = []): bool;
+
+ /**
+ * @param string $name
+ * @param $value
+ *
+ * @return TransactionJournalMeta
+ */
+ abstract public function setMeta(string $name, $value): TransactionJournalMeta;
+
/**
* @return Collection
*/
@@ -273,4 +349,9 @@ trait TransactionJournalTrait
return $typeStr;
}
+
+ /**
+ * @return HasMany
+ */
+ abstract public function transactions(): HasMany;
}
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index dc28a613db..464fbed037 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -96,7 +96,7 @@ class Navigation
// if the range is custom, the end of the period
// is another X days (x is the difference between start)
// and end added to $theCurrentEnd
- if ($repeatFreq == 'custom') {
+ if ($repeatFreq === 'custom') {
/** @var Carbon $tStart */
$tStart = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $tEnd */
@@ -393,7 +393,7 @@ class Navigation
return $date;
}
- if ($repeatFreq == 'half-year' || $repeatFreq == '6M') {
+ if ($repeatFreq === 'half-year' || $repeatFreq === '6M') {
$month = $date->month;
$date->startOfYear();
if ($month >= 7) {
@@ -496,7 +496,7 @@ class Navigation
return $end;
}
- if ($range == '6M') {
+ if ($range === '6M') {
if ($start->month >= 7) {
$end->endOfYear();
@@ -532,7 +532,7 @@ class Navigation
return $start;
}
- if ($range == '6M') {
+ if ($range === '6M') {
if ($start->month >= 7) {
$start->startOfYear()->addMonths(6);
diff --git a/app/Support/Search/Modifier.php b/app/Support/Search/Modifier.php
index 18e94d3aff..acfae12cf6 100644
--- a/app/Support/Search/Modifier.php
+++ b/app/Support/Search/Modifier.php
@@ -86,17 +86,17 @@ class Modifier
case 'date':
case 'on':
$res = self::sameDate($transaction->date, $modifier['value']);
- Log::debug(sprintf('Date is %s? %s', $modifier['value'], var_export($res, true)));
+ Log::debug(sprintf('Date same as %s? %s', $modifier['value'], var_export($res, true)));
break;
case 'date_before':
case 'before':
$res = self::dateBefore($transaction->date, $modifier['value']);
- Log::debug(sprintf('Date is %s? %s', $modifier['value'], var_export($res, true)));
+ Log::debug(sprintf('Date before %s? %s', $modifier['value'], var_export($res, true)));
break;
case 'date_after':
case 'after':
$res = self::dateAfter($transaction->date, $modifier['value']);
- Log::debug(sprintf('Date is %s? %s', $modifier['value'], var_export($res, true)));
+ Log::debug(sprintf('Date before %s? %s', $modifier['value'], var_export($res, true)));
break;
}
@@ -208,4 +208,4 @@ class Modifier
return self::stringCompare($journalCategory, $search) || self::stringCompare($transactionCategory, $search);
}
-}
\ No newline at end of file
+}
diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php
index 4b9ffacb31..ce7b271973 100644
--- a/app/Support/Search/Search.php
+++ b/app/Support/Search/Search.php
@@ -17,11 +17,6 @@ namespace FireflyIII\Support\Search;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
-use FireflyIII\Models\Account;
-use FireflyIII\Models\AccountType;
-use FireflyIII\Models\Budget;
-use FireflyIII\Models\Category;
-use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\User;
use Illuminate\Support\Collection;
@@ -53,7 +48,7 @@ class Search implements SearchInterface
public function __construct()
{
$this->modifiers = new Collection;
- $this->validModifiers = config('firefly.search_modifiers');
+ $this->validModifiers = (array)config('firefly.search_modifiers');
}
/**
@@ -98,112 +93,20 @@ class Search implements SearchInterface
}
}
- /**
- * @return Collection
- */
- public function searchAccounts(): Collection
- {
- $words = $this->words;
- $accounts = $this->user->accounts()
- ->accountTypeIn([AccountType::DEFAULT, AccountType::ASSET, AccountType::EXPENSE, AccountType::REVENUE, AccountType::BENEFICIARY])
- ->get(['accounts.*']);
- /** @var Collection $result */
- $result = $accounts->filter(
- function (Account $account) use ($words) {
- if ($this->strpos_arr(strtolower($account->name), $words)) {
- return $account;
- }
-
- return false;
- }
- );
-
- $result = $result->slice(0, $this->limit);
-
- return $result;
- }
-
- /**
- * @return Collection
- */
- public function searchBudgets(): Collection
- {
- /** @var Collection $set */
- $set = auth()->user()->budgets()->get();
- $words = $this->words;
- /** @var Collection $result */
- $result = $set->filter(
- function (Budget $budget) use ($words) {
- if ($this->strpos_arr(strtolower($budget->name), $words)) {
- return $budget;
- }
-
- return false;
- }
- );
-
- $result = $result->slice(0, $this->limit);
-
- return $result;
- }
-
- /**
- * @return Collection
- */
- public function searchCategories(): Collection
- {
- $words = $this->words;
- $categories = $this->user->categories()->get();
- /** @var Collection $result */
- $result = $categories->filter(
- function (Category $category) use ($words) {
- if ($this->strpos_arr(strtolower($category->name), $words)) {
- return $category;
- }
-
- return false;
- }
- );
- $result = $result->slice(0, $this->limit);
-
- return $result;
- }
-
- /**
- * @return Collection
- */
- public function searchTags(): Collection
- {
- $words = $this->words;
- $tags = $this->user->tags()->get();
- /** @var Collection $result */
- $result = $tags->filter(
- function (Tag $tag) use ($words) {
- if ($this->strpos_arr(strtolower($tag->tag), $words)) {
- return $tag;
- }
-
- return false;
- }
- );
- $result = $result->slice(0, $this->limit);
-
- return $result;
- }
-
/**
* @return Collection
*/
public function searchTransactions(): Collection
{
+ Log::debug('Start of searchTransactions()');
$pageSize = 100;
$processed = 0;
$page = 1;
$result = new Collection();
+ $startTime = microtime(true);
do {
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
- $collector->setUser($this->user);
$collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page);
if ($this->hasModifiers()) {
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
@@ -247,7 +150,11 @@ class Search implements SearchInterface
Log::debug(sprintf('reachedEndOfList: %s', var_export($reachedEndOfList, true)));
Log::debug(sprintf('foundEnough: %s', var_export($foundEnough, true)));
- } while (!$reachedEndOfList && !$foundEnough);
+ // break at some point so the script does not crash:
+ $currentTime = microtime(true) - $startTime;
+ Log::debug(sprintf('Have been running for %f seconds.', $currentTime));
+
+ } while (!$reachedEndOfList && !$foundEnough && $currentTime <= 30);
$result = $result->slice(0, $this->limit);
diff --git a/app/Support/Search/SearchInterface.php b/app/Support/Search/SearchInterface.php
index a06ea95d27..d709d02b67 100644
--- a/app/Support/Search/SearchInterface.php
+++ b/app/Support/Search/SearchInterface.php
@@ -38,25 +38,6 @@ interface SearchInterface
*/
public function parseQuery(string $query);
- /**
- * @return Collection
- */
- public function searchAccounts(): Collection;
-
- /**
- * @return Collection
- */
- public function searchBudgets(): Collection;
-
- /**
- * @return Collection
- */
- public function searchCategories(): Collection;
-
- /**
- * @return Collection
- */
- public function searchTags(): Collection;
/**
* @return Collection
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index 3b5a27ad0d..7b08463261 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -13,11 +13,14 @@ declare(strict_types=1);
namespace FireflyIII\Support;
+use Amount as GlobalAmount;
use Carbon\Carbon;
use Crypt;
use DB;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
+use Illuminate\Contracts\Encryption\DecryptException;
+use Illuminate\Support\Collection;
/**
* Class Steam
@@ -45,14 +48,32 @@ class Steam
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
-
- $balance = strval(
- $account->transactions()->leftJoin(
- 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
- )->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount')
+ $currencyId = intval($account->getMeta('currency_id'));
+ // use system default currency:
+ if ($currencyId === 0) {
+ $currency = GlobalAmount::getDefaultCurrency();
+ $currencyId = $currency->id;
+ }
+ // first part: get all balances in own currency:
+ $nativeBalance = strval(
+ $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
+ ->where('transactions.transaction_currency_id', $currencyId)
+ ->sum('transactions.amount')
);
- $virtual = is_null($account->virtual_balance) ? '0' : strval($account->virtual_balance);
- $balance = bcadd($balance, $virtual);
+
+ // get all balances in foreign currency:
+ $foreignBalance = strval(
+ $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
+ ->where('transactions.foreign_currency_id', $currencyId)
+ ->sum('transactions.foreign_amount')
+ );
+ $balance = bcadd($nativeBalance, $foreignBalance);
+ $virtual = is_null($account->virtual_balance) ? '0' : strval($account->virtual_balance);
+ $balance = bcadd($balance, $virtual);
$cache->store($balance);
return $balance;
@@ -76,13 +97,26 @@ class Steam
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
+ $currencyId = intval($account->getMeta('currency_id'));
- $balance = strval(
- $account->transactions()->leftJoin(
- 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
- )->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount')
+ $nativeBalance = strval(
+ $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
+ ->where('transactions.transaction_currency_id', $currencyId)
+ ->sum('transactions.amount')
);
+ // get all balances in foreign currency:
+ $foreignBalance = strval(
+ $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
+ ->where('transactions.foreign_currency_id', $currencyId)
+ ->sum('transactions.foreign_amount')
+ );
+ $balance = bcadd($nativeBalance, $foreignBalance);
+
$cache->store($balance);
return $balance;
@@ -111,27 +145,55 @@ class Steam
return $cache->get(); // @codeCoverageIgnore
}
- $balances = [];
$start->subDay();
$end->addDay();
- $startBalance = $this->balance($account, $start);
- $balances[$start->format('Y-m-d')] = $startBalance;
+ $balances = [];
+ $formatted = $start->format('Y-m-d');
+ $startBalance = $this->balance($account, $start);
+ $balances[$formatted] = $startBalance;
+ $currencyId = intval($account->getMeta('currency_id'));
$start->addDay();
// query!
- $set = $account->transactions()
- ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->groupBy('transaction_journals.date')
- ->orderBy('transaction_journals.date', 'ASC')
- ->whereNull('transaction_journals.deleted_at')
- ->get(['transaction_journals.date', DB::raw('SUM(transactions.amount) AS modified')]);
+ $set = $account->transactions()
+ ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
+ ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
+ ->groupBy('transaction_journals.date')
+ ->groupBy('transactions.transaction_currency_id')
+ ->groupBy('transactions.foreign_currency_id')
+ ->orderBy('transaction_journals.date', 'ASC')
+ ->whereNull('transaction_journals.deleted_at')
+ ->get(
+ [
+ 'transaction_journals.date',
+ 'transactions.transaction_currency_id',
+ DB::raw('SUM(transactions.amount) AS modified'),
+ 'transactions.foreign_currency_id',
+ DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'),
+ ]
+ );
+
$currentBalance = $startBalance;
+ /** @var Transaction $entry */
foreach ($set as $entry) {
- $modified = is_null($entry->modified) ? '0' : strval($entry->modified);
- $currentBalance = bcadd($currentBalance, $modified);
- $balances[$entry->date] = $currentBalance;
+ // normal amount and foreign amount
+ $modified = is_null($entry->modified) ? '0' : strval($entry->modified);
+ $foreignModified = is_null($entry->modified_foreign) ? '0' : strval($entry->modified_foreign);
+ $amount = '0';
+ if ($currencyId === $entry->transaction_currency_id) {
+ // use normal amount:
+ $amount = $modified;
+ }
+ if ($currencyId === $entry->foreign_currency_id) {
+ // use normal amount:
+ $amount = $foreignModified;
+ }
+
+ $currentBalance = bcadd($currentBalance, $amount);
+ $carbon = new Carbon($entry->date);
+ $date = $carbon->format('Y-m-d');
+ $balances[$date] = $currentBalance;
}
$cache->store($balances);
@@ -144,14 +206,14 @@ class Steam
/**
* This method always ignores the virtual balance.
*
- * @param array $ids
- * @param \Carbon\Carbon $date
+ * @param \Illuminate\Support\Collection $accounts
+ * @param \Carbon\Carbon $date
*
* @return array
*/
- public function balancesById(array $ids, Carbon $date): array
+ public function balancesByAccounts(Collection $accounts, Carbon $date): array
{
-
+ $ids = $accounts->pluck('id')->toArray();
// cache this property.
$cache = new CacheProperties;
$cache->addProperty($ids);
@@ -161,21 +223,13 @@ class Steam
return $cache->get(); // @codeCoverageIgnore
}
- $balances = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
- ->groupBy('transactions.account_id')
- ->whereIn('transactions.account_id', $ids)
- ->whereNull('transaction_journals.deleted_at')
- ->get(['transactions.account_id', DB::raw('sum(transactions.amount) AS aggregate')]);
-
+ // need to do this per account.
$result = [];
- foreach ($balances as $entry) {
- $accountId = intval($entry->account_id);
- $balance = $entry->aggregate;
- $result[$accountId] = $balance;
+ /** @var Account $account */
+ foreach ($accounts as $account) {
+ $result[$account->id] = $this->balance($account, $date);
}
-
$cache->store($result);
return $result;
@@ -195,6 +249,21 @@ class Steam
return $value;
}
+ /**
+ * @param $value
+ *
+ * @return mixed
+ */
+ public function tryDecrypt($value)
+ {
+ try {
+ $value = Crypt::decrypt($value);
+ } catch (DecryptException $e) {
+ // do not care.
+ }
+
+ return $value;
+ }
/**
* @param array $accounts
@@ -217,6 +286,32 @@ class Steam
return $list;
}
+ /**
+ * @param string $amount
+ *
+ * @return string
+ */
+ public function negative(string $amount): string
+ {
+ if (bccomp($amount, '0') === 1) {
+ $amount = bcmul($amount, '-1');
+ }
+
+ return $amount;
+ }
+
+ /**
+ * @param string $amount
+ *
+ * @return string
+ */
+ public function opposite(string $amount): string
+ {
+ $amount = bcmul($amount, '-1');
+
+ return $amount;
+ }
+
/**
* @param $string
*
@@ -252,8 +347,6 @@ class Steam
}
- // parse PHP size:
-
/**
* @param string $amount
*
diff --git a/app/Support/Twig/Account.php b/app/Support/Twig/Account.php
deleted file mode 100644
index d2a5520505..0000000000
--- a/app/Support/Twig/Account.php
+++ /dev/null
@@ -1,63 +0,0 @@
-formatAmountByAccount(),
- ];
-
- }
-
- /**
- * Will return "active" when a part of the route matches the argument.
- * ie. "accounts" will match "accounts.index".
- *
- * @return Twig_SimpleFunction
- */
- protected function formatAmountByAccount(): Twig_SimpleFunction
- {
- return new Twig_SimpleFunction(
- 'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string {
- $currencyId = intval($account->getMeta('currency_id'));
- if ($currencyId === 0) {
- // Format using default currency:
- return AmountFacade::format($amount, $coloured);
- }
- $currency = TransactionCurrency::find($currencyId);
-
- return AmountFacade::formatAnything($currency, $amount, $coloured);
- }, ['is_safe' => ['html']]
- );
- }
-
-
-}
\ No newline at end of file
diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php
new file mode 100644
index 0000000000..55e5c28942
--- /dev/null
+++ b/app/Support/Twig/AmountFormat.php
@@ -0,0 +1,268 @@
+formatAmount(),
+ $this->formatAmountPlain(),
+ ];
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getFunctions(): array
+ {
+ return [
+ $this->formatAmountByAccount(),
+ $this->transactionAmount(),
+ $this->journalAmount(),
+ $this->formatDestinationAfter(),
+ $this->formatDestinationBefore(),
+ $this->formatSourceAfter(),
+ $this->formatSourceBefore(),
+ $this->formatAmountByCurrency(),
+ ];
+
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName(): string
+ {
+ return 'FireflyIII\Support\Twig\AmountFormat';
+ }
+
+ /**
+ *
+ * @return Twig_SimpleFilter
+ */
+ protected function formatAmount(): Twig_SimpleFilter
+ {
+ return new Twig_SimpleFilter(
+ 'formatAmount', function (string $string): string {
+
+ $currency = app('amount')->getDefaultCurrency();
+
+ return app('amount')->formatAnything($currency, $string, true);
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * Will format the amount by the currency related to the given account.
+ *
+ * @return Twig_SimpleFunction
+ */
+ protected function formatAmountByAccount(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string {
+ $currencyId = intval($account->getMeta('currency_id'));
+
+ if ($currencyId !== 0) {
+ $currency = TransactionCurrency::find($currencyId);
+
+ return app('amount')->formatAnything($currency, $amount, $coloured);
+ }
+ $currency = app('amount')->getDefaultCurrency();
+
+ return app('amount')->formatAnything($currency, $amount, $coloured);
+
+
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * Will format the amount by the currency related to the given account.
+ *
+ * @return Twig_SimpleFunction
+ */
+ protected function formatAmountByCurrency(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatAmountByCurrency', function (TransactionCurrency $currency, string $amount, bool $coloured = true): string {
+
+ return app('amount')->formatAnything($currency, $amount, $coloured);
+
+
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFilter
+ */
+ protected function formatAmountPlain(): Twig_SimpleFilter
+ {
+ return new Twig_SimpleFilter(
+ 'formatAmountPlain', function (string $string): string {
+
+ $currency = app('amount')->getDefaultCurrency();
+
+ return app('amount')->formatAnything($currency, $string, false);
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ protected function formatDestinationAfter(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatDestinationAfter', function (array $transaction): string {
+
+ // build fake currency for main amount.
+ $format = new TransactionCurrency;
+ $format->decimal_places = $transaction['transaction_currency_dp'];
+ $format->symbol = $transaction['transaction_currency_symbol'];
+ $string = app('amount')->formatAnything($format, $transaction['destination_account_after'], true);
+
+ // also append foreign amount for clarity:
+ if (!is_null($transaction['foreign_destination_amount'])) {
+ // build fake currency for foreign amount
+ $format = new TransactionCurrency;
+ $format->decimal_places = $transaction['foreign_currency_dp'];
+ $format->symbol = $transaction['foreign_currency_symbol'];
+ $string .= ' (' . app('amount')->formatAnything($format, $transaction['foreign_destination_amount'], true) . ')';
+ }
+
+
+ return $string;
+
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ protected function formatDestinationBefore(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatDestinationBefore', function (array $transaction): string {
+
+ // build fake currency for main amount.
+ $format = new TransactionCurrency;
+ $format->decimal_places = $transaction['transaction_currency_dp'];
+ $format->symbol = $transaction['transaction_currency_symbol'];
+
+ return app('amount')->formatAnything($format, $transaction['destination_account_before'], true);
+
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ protected function formatSourceAfter(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatSourceAfter', function (array $transaction): string {
+
+ // build fake currency for main amount.
+ $format = new TransactionCurrency;
+ $format->decimal_places = $transaction['transaction_currency_dp'];
+ $format->symbol = $transaction['transaction_currency_symbol'];
+ $string = app('amount')->formatAnything($format, $transaction['source_account_after'], true);
+
+ // also append foreign amount for clarity:
+ if (!is_null($transaction['foreign_source_amount'])) {
+ // build fake currency for foreign amount
+ $format = new TransactionCurrency;
+ $format->decimal_places = $transaction['foreign_currency_dp'];
+ $format->symbol = $transaction['foreign_currency_symbol'];
+ $string .= ' (' . app('amount')->formatAnything($format, $transaction['foreign_source_amount'], true) . ')';
+ }
+
+
+ return $string;
+
+
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ protected function formatSourceBefore(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatSourceBefore', function (array $transaction): string {
+
+ // build fake currency for main amount.
+ $format = new TransactionCurrency;
+ $format->decimal_places = $transaction['transaction_currency_dp'];
+ $format->symbol = $transaction['transaction_currency_symbol'];
+
+ return app('amount')->formatAnything($format, $transaction['source_account_before'], true);
+
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ protected function journalAmount(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'journalAmount', function (TransactionJournal $journal): string {
+
+ return app('amount')->journalAmount($journal, true);
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ protected function transactionAmount(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'transactionAmount', function (TransactionModel $transaction): string {
+
+ return app('amount')->transactionAmount($transaction, true);
+ }, ['is_safe' => ['html']]
+ );
+ }
+
+}
diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php
index 5db0ba95f6..09183c84e5 100644
--- a/app/Support/Twig/General.php
+++ b/app/Support/Twig/General.php
@@ -38,9 +38,6 @@ class General extends Twig_Extension
public function getFilters(): array
{
return [
- $this->formatAmount(),
- $this->formatAmountPlain(),
- $this->formatJournal(),
$this->balance(),
$this->formatFilesize(),
$this->mimeIcon(),
@@ -113,7 +110,7 @@ class General extends Twig_Extension
$what = $args[2]; // name of the route.
$activeWhat = $context['what'] ?? false;
- if ($what == $activeWhat && !(strpos(Route::getCurrentRoute()->getName(), $route) === false)) {
+ if ($what === $activeWhat && !(strpos(Route::getCurrentRoute()->getName(), $route) === false)) {
return 'active';
}
@@ -135,7 +132,7 @@ class General extends Twig_Extension
$args = func_get_args();
$route = $args[0]; // name of the route.
- if (Route::getCurrentRoute()->getName() == $route) {
+ if (Route::getCurrentRoute()->getName() === $route) {
return 'active';
}
@@ -173,33 +170,6 @@ class General extends Twig_Extension
);
}
- /**
- *
- * @return Twig_SimpleFilter
- */
- protected function formatAmount(): Twig_SimpleFilter
- {
- return new Twig_SimpleFilter(
- 'formatAmount', function (string $string): string {
-
- return app('amount')->format($string);
- }, ['is_safe' => ['html']]
- );
- }
-
- /**
- * @return Twig_SimpleFilter
- */
- protected function formatAmountPlain(): Twig_SimpleFilter
- {
- return new Twig_SimpleFilter(
- 'formatAmountPlain', function (string $string): string {
-
- return app('amount')->format($string, false);
- }, ['is_safe' => ['html']]
- );
- }
-
/**
* @return Twig_SimpleFilter
*/
@@ -223,17 +193,6 @@ class General extends Twig_Extension
);
}
- /**
- * @return Twig_SimpleFilter
- */
- protected function formatJournal(): Twig_SimpleFilter
- {
- return new Twig_SimpleFilter(
- 'formatJournal', function (TransactionJournal $journal): string {
- return app('amount')->formatJournal($journal);
- }, ['is_safe' => ['html']]
- );
- }
/**
* @return Twig_SimpleFunction
diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php
index d3c0f314e5..82a5553c4d 100644
--- a/app/Support/Twig/Journal.php
+++ b/app/Support/Twig/Journal.php
@@ -51,7 +51,7 @@ class Journal extends Twig_Extension
$array = [];
/** @var Account $entry */
foreach ($list as $entry) {
- if ($entry->accountType->type == AccountType::CASH) {
+ if ($entry->accountType->type === AccountType::CASH) {
$array[] = '(cash)';
continue;
}
@@ -123,7 +123,7 @@ class Journal extends Twig_Extension
$array = [];
/** @var Account $entry */
foreach ($list as $entry) {
- if ($entry->accountType->type == 'Cash account') {
+ if ($entry->accountType->type === AccountType::CASH) {
$array[] = '(cash)';
continue;
}
diff --git a/app/Support/Twig/Rule.php b/app/Support/Twig/Rule.php
index 2503cec452..9924e6f7a0 100644
--- a/app/Support/Twig/Rule.php
+++ b/app/Support/Twig/Rule.php
@@ -70,7 +70,7 @@ class Rule extends Twig_Extension
$ruleTriggers = array_keys(Config::get('firefly.rule-triggers'));
$possibleTriggers = [];
foreach ($ruleTriggers as $key) {
- if ($key != 'user_action') {
+ if ($key !== 'user_action') {
$possibleTriggers[$key] = trans('firefly.rule_trigger_' . $key . '_choice');
}
}
diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php
index d4b8e84850..3774940216 100644
--- a/app/Support/Twig/Transaction.php
+++ b/app/Support/Twig/Transaction.php
@@ -13,10 +13,8 @@ declare(strict_types=1);
namespace FireflyIII\Support\Twig;
-use Amount;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction as TransactionModel;
-use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use Steam;
use Twig_Extension;
@@ -30,49 +28,6 @@ use Twig_SimpleFunction;
*/
class Transaction extends Twig_Extension
{
-
- /**
- * @return Twig_SimpleFunction
- */
- public function formatAnything(): Twig_SimpleFunction
- {
- return new Twig_SimpleFunction(
- 'formatAnything', function (TransactionCurrency $currency, string $amount): string {
-
- return Amount::formatAnything($currency, $amount, true);
-
- }, ['is_safe' => ['html']]
- );
- }
-
- /**
- * @return Twig_SimpleFunction
- */
- public function formatAnythingPlain(): Twig_SimpleFunction
- {
- return new Twig_SimpleFunction(
- 'formatAnythingPlain', function (TransactionCurrency $currency, string $amount): string {
-
- return Amount::formatAnything($currency, $amount, false);
-
- }, ['is_safe' => ['html']]
- );
- }
-
- /**
- * @return Twig_SimpleFunction
- */
- public function formatByCode(): Twig_SimpleFunction
- {
- return new Twig_SimpleFunction(
- 'formatByCode', function (string $currencyCode, string $amount): string {
-
- return Amount::formatByCode($currencyCode, $amount, true);
-
- }, ['is_safe' => ['html']]
- );
- }
-
/**
* @return array
*/
@@ -91,17 +46,13 @@ class Transaction extends Twig_Extension
public function getFunctions(): array
{
$functions = [
- $this->formatAnything(),
- $this->formatAnythingPlain(),
$this->transactionSourceAccount(),
$this->transactionDestinationAccount(),
- $this->optionalJournalAmount(),
$this->transactionBudgets(),
$this->transactionIdBudgets(),
$this->transactionCategories(),
$this->transactionIdCategories(),
$this->splitJournalIndicator(),
- $this->formatByCode(),
];
return $functions;
@@ -117,33 +68,6 @@ class Transaction extends Twig_Extension
return 'transaction';
}
- /**
- * @return Twig_SimpleFunction
- */
- public function optionalJournalAmount(): Twig_SimpleFunction
- {
- return new Twig_SimpleFunction(
- 'optionalJournalAmount', function (int $journalId, string $transactionAmount, string $code, string $type): string {
- // get amount of journal:
- $amount = strval(TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->where('amount', '<', 0)->sum('amount'));
- // display deposit and transfer positive
- if ($type === TransactionType::DEPOSIT || $type === TransactionType::TRANSFER) {
- $amount = bcmul($amount, '-1');
- }
-
- // not equal to transaction amount?
- if (bccomp($amount, $transactionAmount) !== 0 && bccomp($amount, bcmul($transactionAmount, '-1')) !== 0) {
- //$currency =
- return sprintf(' (%s)', Amount::formatByCode($code, $amount, true));
- }
-
- return '';
-
-
- }, ['is_safe' => ['html']]
- );
- }
-
/**
* @return Twig_SimpleFunction
*/
diff --git a/app/User.php b/app/User.php
index f5ee28f920..da5e565d38 100644
--- a/app/User.php
+++ b/app/User.php
@@ -149,7 +149,7 @@ class User extends Authenticatable
{
foreach ($this->roles as $role) {
- if ($role->name == $name) {
+ if ($role->name === $name) {
return true;
}
}
@@ -214,9 +214,9 @@ class User extends Authenticatable
*/
public function sendPasswordResetNotification($token)
{
- $ip = Request::ip();
+ $ipAddress = Request::ip();
- event(new RequestedNewPassword($this, $token, $ip));
+ event(new RequestedNewPassword($this, $token, $ipAddress));
}
/**
diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php
index cf4b60c3f9..e08c95a22a 100644
--- a/app/Validation/FireflyValidator.php
+++ b/app/Validation/FireflyValidator.php
@@ -211,7 +211,7 @@ class FireflyValidator extends Validator
// count budgets, should have at least one
$count = $budgets->filter(
function (Budget $budget) use ($value) {
- return $budget->name == $value;
+ return $budget->name === $value;
}
)->count();
@@ -329,7 +329,7 @@ class FireflyValidator extends Validator
/** @var AccountMeta $entry */
foreach ($set as $entry) {
- if ($entry->data == $value) {
+ if ($entry->data === $value) {
return false;
}
@@ -398,7 +398,7 @@ class FireflyValidator extends Validator
/** @var PiggyBank $entry */
foreach ($set as $entry) {
$fieldValue = $this->tryDecrypt($entry->name);
- if ($fieldValue == $value) {
+ if ($fieldValue === $value) {
return false;
}
}
@@ -460,7 +460,7 @@ class FireflyValidator extends Validator
$set = $user->accounts()->where('account_type_id', $type->id)->get();
/** @var Account $entry */
foreach ($set as $entry) {
- if ($entry->name == $value) {
+ if ($entry->name === $value) {
return false;
}
}
@@ -486,7 +486,7 @@ class FireflyValidator extends Validator
$set = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get();
/** @var Account $entry */
foreach ($set as $entry) {
- if ($entry->name == $value) {
+ if ($entry->name === $value) {
return false;
}
}
@@ -510,7 +510,7 @@ class FireflyValidator extends Validator
$set = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get();
/** @var Account $entry */
foreach ($set as $entry) {
- if ($entry->name == $value) {
+ if ($entry->name === $value) {
return false;
}
}
@@ -534,7 +534,7 @@ class FireflyValidator extends Validator
$set = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get();
/** @var Account $entry */
foreach ($set as $entry) {
- if ($entry->name == $value) {
+ if ($entry->name === $value) {
return false;
}
}
diff --git a/composer.lock b/composer.lock
index 5363e7571a..1118947c51 100644
--- a/composer.lock
+++ b/composer.lock
@@ -104,16 +104,16 @@
},
{
"name": "davejamesmiller/laravel-breadcrumbs",
- "version": "3.0.2",
+ "version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/davejamesmiller/laravel-breadcrumbs.git",
- "reference": "6ca5a600003ecb52a5b5af14dad82033058604e1"
+ "reference": "0b0f4792dee645b0f084164aa17d4320e4bb734f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/davejamesmiller/laravel-breadcrumbs/zipball/6ca5a600003ecb52a5b5af14dad82033058604e1",
- "reference": "6ca5a600003ecb52a5b5af14dad82033058604e1",
+ "url": "https://api.github.com/repos/davejamesmiller/laravel-breadcrumbs/zipball/0b0f4792dee645b0f084164aa17d4320e4bb734f",
+ "reference": "0b0f4792dee645b0f084164aa17d4320e4bb734f",
"shasum": ""
},
"require": {
@@ -124,8 +124,7 @@
"require-dev": {
"mockery/mockery": "0.9.*",
"orchestra/testbench": "3.2.*|3.3.*",
- "phpunit/phpunit": "4.*",
- "satooshi/php-coveralls": "0.6.*"
+ "phpunit/phpunit": "4.*"
},
"type": "library",
"autoload": {
@@ -144,12 +143,12 @@
"homepage": "https://davejamesmiller.com/"
}
],
- "description": "A simple Laravel-style way to create breadcrumbs in Laravel 4+.",
+ "description": "A simple Laravel-style way to create breadcrumbs in Laravel.",
"homepage": "https://laravel-breadcrumbs.readthedocs.io/",
"keywords": [
"laravel"
],
- "time": "2017-01-30T21:16:53+00:00"
+ "time": "2017-06-24T11:10:49+00:00"
},
{
"name": "doctrine/annotations",
@@ -623,16 +622,16 @@
},
{
"name": "erusev/parsedown",
- "version": "1.6.2",
+ "version": "1.6.3",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
- "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01"
+ "reference": "728952b90a333b5c6f77f06ea9422b94b585878d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01",
- "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01",
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/728952b90a333b5c6f77f06ea9422b94b585878d",
+ "reference": "728952b90a333b5c6f77f06ea9422b94b585878d",
"shasum": ""
},
"require": {
@@ -661,20 +660,20 @@
"markdown",
"parser"
],
- "time": "2017-03-29T16:04:15+00:00"
+ "time": "2017-05-14T14:47:48+00:00"
},
{
"name": "laravel/framework",
- "version": "v5.4.21",
+ "version": "v5.4.28",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee"
+ "reference": "442511fc62121085d184355e4f964c88942bbecb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee",
- "reference": "2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/442511fc62121085d184355e4f964c88942bbecb",
+ "reference": "442511fc62121085d184355e4f964c88942bbecb",
"shasum": ""
},
"require": {
@@ -790,20 +789,20 @@
"framework",
"laravel"
],
- "time": "2017-04-28T15:40:01+00:00"
+ "time": "2017-06-30T13:43:07+00:00"
},
{
"name": "laravelcollective/html",
- "version": "v5.4.1",
+ "version": "v5.4.8",
"source": {
"type": "git",
"url": "https://github.com/LaravelCollective/html.git",
- "reference": "7570f25d58a00fd6909c0563808590f9cdb14d47"
+ "reference": "9b8f51e7a2368911c896f5d42757886bae0717b5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/LaravelCollective/html/zipball/7570f25d58a00fd6909c0563808590f9cdb14d47",
- "reference": "7570f25d58a00fd6909c0563808590f9cdb14d47",
+ "url": "https://api.github.com/repos/LaravelCollective/html/zipball/9b8f51e7a2368911c896f5d42757886bae0717b5",
+ "reference": "9b8f51e7a2368911c896f5d42757886bae0717b5",
"shasum": ""
},
"require": {
@@ -844,20 +843,20 @@
],
"description": "HTML and Form Builders for the Laravel Framework",
"homepage": "http://laravelcollective.com",
- "time": "2017-01-26T19:27:05+00:00"
+ "time": "2017-05-22T06:35:07+00:00"
},
{
"name": "league/commonmark",
- "version": "0.15.3",
+ "version": "0.15.4",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc"
+ "reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c8b43ee5821362216f8e9ac684f0f59de164edcc",
- "reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
+ "reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
"shasum": ""
},
"require": {
@@ -913,7 +912,7 @@
"markdown",
"parser"
],
- "time": "2016-12-19T00:11:43+00:00"
+ "time": "2017-05-09T12:47:53+00:00"
},
{
"name": "league/csv",
@@ -1057,16 +1056,16 @@
},
{
"name": "monolog/monolog",
- "version": "1.22.1",
+ "version": "1.23.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
- "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0"
+ "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0",
- "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
+ "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"shasum": ""
},
"require": {
@@ -1087,7 +1086,7 @@
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
- "swiftmailer/swiftmailer": "~5.3"
+ "swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
@@ -1131,7 +1130,7 @@
"logging",
"psr-3"
],
- "time": "2017-03-13T07:08:03+00:00"
+ "time": "2017-06-19T01:22:40+00:00"
},
{
"name": "mtdowling/cron-expression",
@@ -1583,16 +1582,16 @@
},
{
"name": "swiftmailer/swiftmailer",
- "version": "v5.4.7",
+ "version": "v5.4.8",
"source": {
"type": "git",
"url": "https://github.com/swiftmailer/swiftmailer.git",
- "reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4"
+ "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4",
- "reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/9a06dc570a0367850280eefd3f1dc2da45aef517",
+ "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517",
"shasum": ""
},
"require": {
@@ -1633,20 +1632,20 @@
"mail",
"mailer"
],
- "time": "2017-04-20T17:32:18+00:00"
+ "time": "2017-05-01T15:54:03+00:00"
},
{
"name": "symfony/console",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38"
+ "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38",
- "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38",
+ "url": "https://api.github.com/repos/symfony/console/zipball/a97e45d98c59510f085fa05225a1acb74dfe0546",
+ "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546",
"shasum": ""
},
"require": {
@@ -1654,10 +1653,16 @@
"symfony/debug": "~2.8|~3.0",
"symfony/polyfill-mbstring": "~1.0"
},
+ "conflict": {
+ "symfony/dependency-injection": "<3.3"
+ },
"require-dev": {
"psr/log": "~1.0",
+ "symfony/config": "~3.3",
+ "symfony/dependency-injection": "~3.3",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/filesystem": "~2.8|~3.0",
+ "symfony/http-kernel": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0"
},
"suggest": {
@@ -1669,7 +1674,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -1696,7 +1701,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2017-04-26T01:39:17+00:00"
+ "time": "2017-07-03T13:19:36+00:00"
},
{
"name": "symfony/css-selector",
@@ -1753,16 +1758,16 @@
},
{
"name": "symfony/debug",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686"
+ "reference": "63b85a968486d95ff9542228dc2e4247f16f9743"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686",
- "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/63b85a968486d95ff9542228dc2e4247f16f9743",
+ "reference": "63b85a968486d95ff9542228dc2e4247f16f9743",
"shasum": ""
},
"require": {
@@ -1773,13 +1778,12 @@
"symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
},
"require-dev": {
- "symfony/class-loader": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -1806,20 +1810,20 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2017-04-19T20:17:50+00:00"
+ "time": "2017-07-05T13:02:37+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.8.20",
+ "version": "v2.8.24",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "7fc8e2b4118ff316550596357325dfd92a51f531"
+ "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7fc8e2b4118ff316550596357325dfd92a51f531",
- "reference": "7fc8e2b4118ff316550596357325dfd92a51f531",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1377400fd641d7d1935981546aaef780ecd5bf6d",
+ "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d",
"shasum": ""
},
"require": {
@@ -1866,20 +1870,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2017-04-26T16:56:54+00:00"
+ "time": "2017-06-02T07:47:27+00:00"
},
{
"name": "symfony/finder",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930"
+ "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930",
- "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4",
+ "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4",
"shasum": ""
},
"require": {
@@ -1888,7 +1892,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -1915,20 +1919,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-06-01T21:01:25+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef"
+ "reference": "f347a5f561b03db95ed666959db42bbbf429b7e5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9de6add7f731e5af7f5b2e9c0da365e43383ebef",
- "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f347a5f561b03db95ed666959db42bbbf429b7e5",
+ "reference": "f347a5f561b03db95ed666959db42bbbf429b7e5",
"shasum": ""
},
"require": {
@@ -1941,7 +1945,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -1968,20 +1972,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2017-05-01T14:55:58+00:00"
+ "time": "2017-06-24T09:29:48+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v3.2.8",
+ "version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05"
+ "reference": "4ad34a0d20a5848c0fcbf6ff6a2ff1cd9cf4b9ed"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/46e8b209abab55c072c47d72d5cd1d62c0585e05",
- "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4ad34a0d20a5848c0fcbf6ff6a2ff1cd9cf4b9ed",
+ "reference": "4ad34a0d20a5848c0fcbf6ff6a2ff1cd9cf4b9ed",
"shasum": ""
},
"require": {
@@ -1989,18 +1993,21 @@
"psr/log": "~1.0",
"symfony/debug": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0",
- "symfony/http-foundation": "~2.8.13|~3.1.6|~3.2"
+ "symfony/http-foundation": "~3.3"
},
"conflict": {
- "symfony/config": "<2.8"
+ "symfony/config": "<2.8",
+ "symfony/dependency-injection": "<3.3",
+ "symfony/var-dumper": "<3.3"
},
"require-dev": {
+ "psr/cache": "~1.0",
"symfony/browser-kit": "~2.8|~3.0",
"symfony/class-loader": "~2.8|~3.0",
"symfony/config": "~2.8|~3.0",
"symfony/console": "~2.8|~3.0",
"symfony/css-selector": "~2.8|~3.0",
- "symfony/dependency-injection": "~2.8|~3.0",
+ "symfony/dependency-injection": "~3.3",
"symfony/dom-crawler": "~2.8|~3.0",
"symfony/expression-language": "~2.8|~3.0",
"symfony/finder": "~2.8|~3.0",
@@ -2009,7 +2016,7 @@
"symfony/stopwatch": "~2.8|~3.0",
"symfony/templating": "~2.8|~3.0",
"symfony/translation": "~2.8|~3.0",
- "symfony/var-dumper": "~3.2"
+ "symfony/var-dumper": "~3.3"
},
"suggest": {
"symfony/browser-kit": "",
@@ -2023,7 +2030,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -2050,20 +2057,20 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2017-05-01T17:46:48+00:00"
+ "time": "2017-05-29T21:02:12+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.3.0",
+ "version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
+ "reference": "f29dca382a6485c3cbe6379f0c61230167681937"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
- "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937",
+ "reference": "f29dca382a6485c3cbe6379f0c61230167681937",
"shasum": ""
},
"require": {
@@ -2075,7 +2082,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.3-dev"
+ "dev-master": "1.4-dev"
}
},
"autoload": {
@@ -2109,20 +2116,20 @@
"portable",
"shim"
],
- "time": "2016-11-14T01:06:16+00:00"
+ "time": "2017-06-09T14:24:12+00:00"
},
{
"name": "symfony/polyfill-php56",
- "version": "v1.3.0",
+ "version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php56.git",
- "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c"
+ "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c",
- "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c",
+ "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/bc0b7d6cb36b10cfabb170a3e359944a95174929",
+ "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929",
"shasum": ""
},
"require": {
@@ -2132,7 +2139,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.3-dev"
+ "dev-master": "1.4-dev"
}
},
"autoload": {
@@ -2165,20 +2172,20 @@
"portable",
"shim"
],
- "time": "2016-11-14T01:06:16+00:00"
+ "time": "2017-06-09T08:25:21+00:00"
},
{
"name": "symfony/polyfill-util",
- "version": "v1.3.0",
+ "version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-util.git",
- "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb"
+ "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb",
- "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb",
+ "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ebccbde4aad410f6438d86d7d261c6b4d2b9a51d",
+ "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d",
"shasum": ""
},
"require": {
@@ -2187,7 +2194,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.3-dev"
+ "dev-master": "1.4-dev"
}
},
"autoload": {
@@ -2217,20 +2224,20 @@
"polyfill",
"shim"
],
- "time": "2016-11-14T01:06:16+00:00"
+ "time": "2017-06-09T08:25:21+00:00"
},
{
"name": "symfony/process",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0"
+ "reference": "5ab8949b682b1bf9d4511a228b5e045c96758c30"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
- "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
+ "url": "https://api.github.com/repos/symfony/process/zipball/5ab8949b682b1bf9d4511a228b5e045c96758c30",
+ "reference": "5ab8949b682b1bf9d4511a228b5e045c96758c30",
"shasum": ""
},
"require": {
@@ -2239,7 +2246,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -2266,36 +2273,39 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-07-03T08:12:02+00:00"
},
{
"name": "symfony/routing",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "5029745d6d463585e8b487dbc83d6333f408853a"
+ "reference": "dc70bbd0ca7b19259f63cdacc8af370bc32a4728"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/5029745d6d463585e8b487dbc83d6333f408853a",
- "reference": "5029745d6d463585e8b487dbc83d6333f408853a",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/dc70bbd0ca7b19259f63cdacc8af370bc32a4728",
+ "reference": "dc70bbd0ca7b19259f63cdacc8af370bc32a4728",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"conflict": {
- "symfony/config": "<2.8"
+ "symfony/config": "<2.8",
+ "symfony/dependency-injection": "<3.3",
+ "symfony/yaml": "<3.3"
},
"require-dev": {
"doctrine/annotations": "~1.0",
"doctrine/common": "~2.2",
"psr/log": "~1.0",
"symfony/config": "~2.8|~3.0",
+ "symfony/dependency-injection": "~3.3",
"symfony/expression-language": "~2.8|~3.0",
"symfony/http-foundation": "~2.8|~3.0",
- "symfony/yaml": "~2.8|~3.0"
+ "symfony/yaml": "~3.3"
},
"suggest": {
"doctrine/annotations": "For using the annotation loader",
@@ -2308,7 +2318,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -2341,20 +2351,20 @@
"uri",
"url"
],
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-06-24T09:29:48+00:00"
},
{
"name": "symfony/translation",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "f4a04d2df710f81515df576b2de06bdeee518b83"
+ "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/f4a04d2df710f81515df576b2de06bdeee518b83",
- "reference": "f4a04d2df710f81515df576b2de06bdeee518b83",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3",
+ "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3",
"shasum": ""
},
"require": {
@@ -2362,13 +2372,14 @@
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
- "symfony/config": "<2.8"
+ "symfony/config": "<2.8",
+ "symfony/yaml": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.8|~3.0",
"symfony/intl": "^2.8.18|^3.2.5",
- "symfony/yaml": "~2.8|~3.0"
+ "symfony/yaml": "~3.3"
},
"suggest": {
"psr/log": "To use logging capability in translator",
@@ -2378,7 +2389,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -2405,20 +2416,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-06-24T16:45:30+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8"
+ "reference": "9ee920bba1d2ce877496dcafca7cbffff4dbe08a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
- "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9ee920bba1d2ce877496dcafca7cbffff4dbe08a",
+ "reference": "9ee920bba1d2ce877496dcafca7cbffff4dbe08a",
"shasum": ""
},
"require": {
@@ -2430,7 +2441,7 @@
},
"require-dev": {
"ext-iconv": "*",
- "twig/twig": "~1.20|~2.0"
+ "twig/twig": "~1.34|~2.4"
},
"suggest": {
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
@@ -2439,7 +2450,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -2473,7 +2484,7 @@
"debug",
"dump"
],
- "time": "2017-05-01T14:55:58+00:00"
+ "time": "2017-07-05T13:02:37+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -2687,20 +2698,20 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
- "version": "v2.3.2",
+ "version": "v2.4.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
- "reference": "24e4f0261e352d3fd86d0447791b56ae49398674"
+ "reference": "af98b3a4ccac9364f2145fae974ff3392ec402b1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/24e4f0261e352d3fd86d0447791b56ae49398674",
- "reference": "24e4f0261e352d3fd86d0447791b56ae49398674",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/af98b3a4ccac9364f2145fae974ff3392ec402b1",
+ "reference": "af98b3a4ccac9364f2145fae974ff3392ec402b1",
"shasum": ""
},
"require": {
- "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*",
+ "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*",
"maximebf/debugbar": "~1.13.0",
"php": ">=5.5.9",
"symfony/finder": "~2.7|~3.0"
@@ -2708,7 +2719,15 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.3-dev"
+ "dev-master": "2.4-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Barryvdh\\Debugbar\\ServiceProvider"
+ ],
+ "aliases": {
+ "Debugbar": "Barryvdh\\Debugbar\\Facade"
+ }
}
},
"autoload": {
@@ -2737,32 +2756,34 @@
"profiler",
"webprofiler"
],
- "time": "2017-01-19T08:19:49+00:00"
+ "time": "2017-06-14T07:44:44+00:00"
},
{
"name": "barryvdh/laravel-ide-helper",
- "version": "v2.3.2",
+ "version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
- "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5"
+ "reference": "87a02ff574f722c685e011f76692ab869ab64f6c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5",
- "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5",
+ "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/87a02ff574f722c685e011f76692ab869ab64f6c",
+ "reference": "87a02ff574f722c685e011f76692ab869ab64f6c",
"shasum": ""
},
"require": {
"barryvdh/reflection-docblock": "^2.0.4",
- "illuminate/console": "^5.0,<5.5",
- "illuminate/filesystem": "^5.0,<5.5",
- "illuminate/support": "^5.0,<5.5",
+ "illuminate/console": "^5.0,<5.6",
+ "illuminate/filesystem": "^5.0,<5.6",
+ "illuminate/support": "^5.0,<5.6",
"php": ">=5.4.0",
"symfony/class-loader": "^2.3|^3.0"
},
"require-dev": {
"doctrine/dbal": "~2.3",
+ "illuminate/config": "^5.0,<5.6",
+ "illuminate/view": "^5.0,<5.6",
"phpunit/phpunit": "4.*",
"scrutinizer/ocular": "~1.1",
"squizlabs/php_codesniffer": "~2.3"
@@ -2774,6 +2795,11 @@
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider"
+ ]
}
},
"autoload": {
@@ -2803,7 +2829,7 @@
"phpstorm",
"sublime"
],
- "time": "2017-02-22T12:27:33+00:00"
+ "time": "2017-06-16T14:08:59+00:00"
},
{
"name": "barryvdh/reflection-docblock",
@@ -3725,16 +3751,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "5.7.19",
+ "version": "5.7.21",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1"
+ "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1",
- "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b91adfb64264ddec5a2dee9851f354aa66327db",
+ "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db",
"shasum": ""
},
"require": {
@@ -3752,7 +3778,7 @@
"phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "^3.2",
"sebastian/comparator": "^1.2.4",
- "sebastian/diff": "~1.2",
+ "sebastian/diff": "^1.4.3",
"sebastian/environment": "^1.3.4 || ^2.0",
"sebastian/exporter": "~2.0",
"sebastian/global-state": "^1.1",
@@ -3803,20 +3829,20 @@
"testing",
"xunit"
],
- "time": "2017-04-03T02:22:27+00:00"
+ "time": "2017-06-21T08:11:54+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
- "version": "3.4.3",
+ "version": "3.4.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24"
+ "reference": "a23b761686d50a560cc56233b9ecf49597cc9118"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
- "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118",
+ "reference": "a23b761686d50a560cc56233b9ecf49597cc9118",
"shasum": ""
},
"require": {
@@ -3862,7 +3888,7 @@
"mock",
"xunit"
],
- "time": "2016-12-08T20:27:08+00:00"
+ "time": "2017-06-30T09:13:00+00:00"
},
{
"name": "satooshi/php-coveralls",
@@ -4033,23 +4059,23 @@
},
{
"name": "sebastian/diff",
- "version": "1.4.1",
+ "version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
+ "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
- "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+ "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^5.3.3 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.8"
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
},
"type": "library",
"extra": {
@@ -4081,7 +4107,7 @@
"keywords": [
"diff"
],
- "time": "2015-12-08T07:14:41+00:00"
+ "time": "2017-05-22T07:24:03+00:00"
},
{
"name": "sebastian/environment",
@@ -4437,16 +4463,16 @@
},
{
"name": "symfony/class-loader",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
- "reference": "fc4c04bfd17130a9dccfded9578353f311967da7"
+ "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/class-loader/zipball/fc4c04bfd17130a9dccfded9578353f311967da7",
- "reference": "fc4c04bfd17130a9dccfded9578353f311967da7",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/386a294d621576302e7cc36965d6ed53b8c73c4f",
+ "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f",
"shasum": ""
},
"require": {
@@ -4462,7 +4488,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -4489,27 +4515,33 @@
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-06-02T09:51:43+00:00"
},
{
"name": "symfony/config",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "e5533fcc0b3dd377626153b2852707878f363728"
+ "reference": "a094618deb9a3fe1c3cf500a796e167d0495a274"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/e5533fcc0b3dd377626153b2852707878f363728",
- "reference": "e5533fcc0b3dd377626153b2852707878f363728",
+ "url": "https://api.github.com/repos/symfony/config/zipball/a094618deb9a3fe1c3cf500a796e167d0495a274",
+ "reference": "a094618deb9a3fe1c3cf500a796e167d0495a274",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"symfony/filesystem": "~2.8|~3.0"
},
+ "conflict": {
+ "symfony/dependency-injection": "<3.3",
+ "symfony/finder": "<3.3"
+ },
"require-dev": {
+ "symfony/dependency-injection": "~3.3",
+ "symfony/finder": "~3.3",
"symfony/yaml": "~3.0"
},
"suggest": {
@@ -4518,7 +4550,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -4545,7 +4577,7 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-06-16T12:40:34+00:00"
},
{
"name": "symfony/dom-crawler",
@@ -4605,16 +4637,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "040651db13cf061827a460cc10f6e36a445c45b4"
+ "reference": "311fa718389efbd8b627c272b9324a62437018cc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/040651db13cf061827a460cc10f6e36a445c45b4",
- "reference": "040651db13cf061827a460cc10f6e36a445c45b4",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/311fa718389efbd8b627c272b9324a62437018cc",
+ "reference": "311fa718389efbd8b627c272b9324a62437018cc",
"shasum": ""
},
"require": {
@@ -4623,7 +4655,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -4650,20 +4682,20 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-06-24T09:29:48+00:00"
},
{
"name": "symfony/stopwatch",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
- "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3"
+ "reference": "602a15299dc01556013b07167d4f5d3a60e90d15"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a0105afb670dbd38f521105c444de1b8e10cfe3",
- "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3",
+ "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15",
+ "reference": "602a15299dc01556013b07167d4f5d3a60e90d15",
"shasum": ""
},
"require": {
@@ -4672,7 +4704,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -4699,20 +4731,20 @@
],
"description": "Symfony Stopwatch Component",
"homepage": "https://symfony.com",
- "time": "2017-04-12T14:13:17+00:00"
+ "time": "2017-04-12T14:14:56+00:00"
},
{
"name": "symfony/yaml",
- "version": "v3.2.8",
+ "version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6"
+ "reference": "1f93a8d19b8241617f5074a123e282575b821df8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6",
- "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/1f93a8d19b8241617f5074a123e282575b821df8",
+ "reference": "1f93a8d19b8241617f5074a123e282575b821df8",
"shasum": ""
},
"require": {
@@ -4727,7 +4759,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
},
"autoload": {
@@ -4754,7 +4786,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2017-05-01T14:55:58+00:00"
+ "time": "2017-06-15T12:58:50+00:00"
},
{
"name": "webmozart/assert",
diff --git a/config/csv.php b/config/csv.php
index 5da40e41e0..cc23de32f1 100644
--- a/config/csv.php
+++ b/config/csv.php
@@ -292,6 +292,7 @@ return [
// number of example rows:
'example_rows' => 5,
'default_config' => [
+ 'initial-config-complete' => false,
'has-headers' => false, // assume
'date-format' => 'Ymd', // assume
'delimiter' => ',', // assume
diff --git a/config/firefly.php b/config/firefly.php
index cb76de0723..5fffbbf7a8 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -23,15 +23,21 @@ return [
'is_demo_site' => false,
],
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
- 'version' => '4.4.3',
- 'maxUploadSize' => 5242880,
+ 'version' => '4.6.2',
+ 'maxUploadSize' => 15242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
'list_length' => 10,
'export_formats' => [
'csv' => 'FireflyIII\Export\Exporter\CsvExporter',
],
'import_formats' => [
- 'csv' => 'FireflyIII\Import\Importer\CsvImporter',
+ 'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator',
+ ],
+ 'import_configurators' => [
+ 'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator',
+ ],
+ 'import_processors' => [
+ 'csv' => 'FireflyIII\Import\FileProcessor\CsvProcessor',
],
'default_export_format' => 'csv',
'default_import_format' => 'csv',
diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php
index 283a342b20..c014cb7078 100644
--- a/database/factories/ModelFactory.php
+++ b/database/factories/ModelFactory.php
@@ -89,6 +89,7 @@ $factory->define(
'user_id' => 1,
'transaction_type_id' => 1,
'bill_id' => null,
+ // TODO update this transaction currency reference.
'transaction_currency_id' => 1,
'description' => $faker->words(3, true),
'date' => '2017-01-01',
@@ -216,25 +217,31 @@ $factory->define(
$factory->define(
FireflyIII\Models\Transaction::class, function (Faker\Generator $faker) {
return [
- 'transaction_amount' => strval($faker->randomFloat(2, -100, 100)),
- 'destination_amount' => strval($faker->randomFloat(2, -100, 100)),
- 'opposing_account_id' => $faker->numberBetween(1, 10),
- 'source_account_id' => $faker->numberBetween(1, 10),
- 'opposing_account_name' => $faker->words(3, true),
- 'description' => $faker->words(3, true),
- 'source_account_name' => $faker->words(3, true),
- 'destination_account_id' => $faker->numberBetween(1, 10),
- 'date' => new Carbon,
- 'destination_account_name' => $faker->words(3, true),
- 'amount' => strval($faker->randomFloat(2, -100, 100)),
- 'budget_id' => 0,
- 'category' => $faker->words(3, true),
- 'transaction_journal_id' => $faker->numberBetween(1, 10),
- 'journal_id' => $faker->numberBetween(1, 10),
- 'transaction_currency_code' => 'EUR',
- 'transaction_type_type' => 'Withdrawal',
- 'account_encrypted' => 0,
- 'account_name' => 'Some name',
+ 'transaction_amount' => strval($faker->randomFloat(2, -100, 100)),
+ 'destination_amount' => strval($faker->randomFloat(2, -100, 100)),
+ 'opposing_account_id' => $faker->numberBetween(1, 10),
+ 'source_account_id' => $faker->numberBetween(1, 10),
+ 'opposing_account_name' => $faker->words(3, true),
+ 'description' => $faker->words(3, true),
+ 'source_account_name' => $faker->words(3, true),
+ 'destination_account_id' => $faker->numberBetween(1, 10),
+ 'date' => new Carbon,
+ 'destination_account_name' => $faker->words(3, true),
+ 'amount' => strval($faker->randomFloat(2, -100, 100)),
+ 'budget_id' => 0,
+ 'category' => $faker->words(3, true),
+ 'transaction_journal_id' => $faker->numberBetween(1, 10),
+ 'journal_id' => $faker->numberBetween(1, 10),
+ 'transaction_currency_code' => 'EUR',
+ 'transaction_type_type' => 'Withdrawal',
+ 'account_encrypted' => 0,
+ 'account_name' => 'Some name',
+ 'transaction_currency_id' => 1,
+ 'transaction_currency_symbol' => '€',
+ 'foreign_destination_amount' => null,
+ 'foreign_currency_id' => null,
+ 'foreign_currency_code' => null,
+ 'foreign_currency_symbol' => null,
];
}
-);
\ No newline at end of file
+);
diff --git a/database/migrations/2016_06_16_000002_create_main_tables.php b/database/migrations/2016_06_16_000002_create_main_tables.php
index 0f5fe8d4e4..300024918a 100644
--- a/database/migrations/2016_06_16_000002_create_main_tables.php
+++ b/database/migrations/2016_06_16_000002_create_main_tables.php
@@ -474,7 +474,7 @@ class CreateMainTables extends Migration
$table->text('description')->nullable();
$table->decimal('latitude', 24, 12)->nullable();
$table->decimal('longitude', 24, 12)->nullable();
- $table->boolean('zoomLevel')->nullable();
+ $table->smallInteger('zoomLevel', false, true)->nullable();
// link user id to users table
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
diff --git a/database/migrations/2017_06_02_105232_changes_for_v450.php b/database/migrations/2017_06_02_105232_changes_for_v450.php
new file mode 100644
index 0000000000..1f44bedf52
--- /dev/null
+++ b/database/migrations/2017_06_02_105232_changes_for_v450.php
@@ -0,0 +1,42 @@
+decimal('foreign_amount', 22, 12)->nullable()->after('amount');
+ }
+ );
+
+ // add foreign transaction currency id to transactions (is nullable):
+ Schema::table(
+ 'transactions', function (Blueprint $table) {
+ $table->integer('foreign_currency_id', false, true)->default(null)->after('foreign_amount')->nullable();
+ $table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null');
+ }
+ );
+ }
+}
diff --git a/database/seeds/TransactionCurrencySeeder.php b/database/seeds/TransactionCurrencySeeder.php
index 8f51c69cd1..0d7732e80b 100644
--- a/database/seeds/TransactionCurrencySeeder.php
+++ b/database/seeds/TransactionCurrencySeeder.php
@@ -28,6 +28,7 @@ class TransactionCurrencySeeder extends Seeder
TransactionCurrency::create(['code' => 'GBP', 'name' => 'British Pound', 'symbol' => '£', 'decimal_places' => 2]);
TransactionCurrency::create(['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2]);
TransactionCurrency::create(['code' => 'XBT', 'name' => 'Bitcoin', 'symbol' => 'B', 'decimal_places' => 8]);
+ TransactionCurrency::create(['code' => 'JPY', 'name' => 'Japanese yen', 'symbol' => '¥', 'decimal_places' => 2]);
}
}
diff --git a/public/browserconfig.xml b/public/browserconfig.xml
index 74bb89ac30..b3930d0f04 100644
--- a/public/browserconfig.xml
+++ b/public/browserconfig.xml
@@ -1,9 +1,9 @@
@@ -106,7 +105,7 @@
- {{ 'budgeted'|_ }}
- - {{ session('start').formatLocalized(monthAndDayFormat) }} - - {{ session('end').formatLocalized(monthAndDayFormat) }} - |
-
-
-
-
+
+ {{ defaultCurrency.symbol|raw }}
-
- {% if budgetInformation[budget.id]['currentLimit'] %}
- {% set repAmount = budgetInformation[budget.id]['currentLimit'].amount %}
- {% else %}
- {% set repAmount = '0' %}
- {% endif %}
-
- {{ 'budget'|_ }} |
+ {{ 'budgeted'|_ }} |
+
+
+ |
- {{ 'spent'|_ }}
- - {{ session('start').formatLocalized(monthAndDayFormat) }} - - {{ session('end').formatLocalized(monthAndDayFormat) }} - + | + {% if budgetInformation[budget.id]['currentLimit'] %} + {{ budget.name }} + {% else %} + {{ budget.name }} + {% endif %} | -+ {% if budgetInformation[budget.id]['currentLimit'] %} + {% set repAmount = budgetInformation[budget.id]['currentLimit'].amount %} + {% else %} + {% set repAmount = '0' %} + {% endif %} + + |
+
+
+ {{ defaultCurrency.symbol|raw }}
+
+
+ |
+
+
-
|
-
@@ -39,7 +39,7 @@
diff --git a/resources/views/categories/create.twig b/resources/views/categories/create.twig index e99d27b20a..364e40ca1f 100644 --- a/resources/views/categories/create.twig +++ b/resources/views/categories/create.twig @@ -9,7 +9,7 @@