diff --git a/.env.example b/.env.example
index 20f71dc696..df8630b39b 100755
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,6 @@
APP_ENV=production
APP_DEBUG=false
+APP_FORCE_SSL=false
APP_KEY=SomeRandomStringOf32CharsExactly
LOG_LEVEL=warning
@@ -36,6 +37,7 @@ SEND_REGISTRATION_MAIL=true
MUST_CONFIRM_ACCOUNT=false
SHOW_INCOMPLETE_TRANSLATIONS=false
+SHOW_DEMO_WARNING=false
ANALYTICS_ID=
RUNCLEANUP=true
diff --git a/.env.testing b/.env.testing
index 852745ab42..8aeacddc74 100755
--- a/.env.testing
+++ b/.env.testing
@@ -1,5 +1,6 @@
APP_ENV=testing
APP_DEBUG=true
+APP_FORCE_SSL=false
APP_KEY=SomeRandomStringOf32CharsExactly
LOG_LEVEL=debug
diff --git a/.gitignore b/.gitignore
index 24a6fb22e5..736f45d8ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
/vendor
/node_modules
.env
-
-storage/
+_development
.env.local
diff --git a/.travis.yml b/.travis.yml
index f48b4f9dd9..61763a7648 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,6 @@ php:
- 7
install:
- - cp _development/phpunit.xml ./phpunit.xml
- phpenv config-rm xdebug.ini
- composer selfupdate
- rm composer.lock
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 412b2d343d..0b5e4cf7af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,25 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
- No unreleased changes yet.
+[3.9.0]
+### Added
+- @zjean has added code that allows you to force "https://"-URL's.
+- @tonicospinelli has added Portuguese (Brazil) translations.
+- Firefly III supports the *splitting* of transactions:
+ - A withdrawal (expense) can be split into multiple sub-transactions (with multiple destinations)
+ - Likewise for deposits (incomes). You can set multiple sources.
+ - Likewise for transfers.
+
+### Changed
+- Update a lot of libraries.
+- Big improvement to test data generation.
+- Cleaned up many repositories.
+
+### Removed
+- Front page boxes will no longer respond to credit card bills.
+
+### Fixed
+- Many bugs
## [3.8.4] - 2016-04-24
### Added
diff --git a/README.md b/README.md
index 7c92d457f4..a62386c40a 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,18 @@
-# Firefly III
+# Firefly III [](https://secure.php.net/downloads.php#v7.0.4) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://scrutinizer-ci.com/g/JC5/firefly-iii/?branch=master)
-[](https://secure.php.net/downloads.php#v7.0.4)
-[](https://packagist.org/packages/grumpydictator/firefly-iii)
-[](https://scrutinizer-ci.com/g/JC5/firefly-iii/?branch=master)
-[](https://scrutinizer-ci.com/g/JC5/firefly-iii/build-status/master)
-[](https://insight.sensiolabs.com/projects/d44c7012-5f50-41ad-add8-8445330e4102)
-[](https://codeclimate.com/github/JC5/firefly-iii)
+## A personal finances manager
-## About
+[](https://i.nder.be/hhfv03hp) [](https://i.nder.be/hhmwmqw9)
-"Firefly III" is a financial manager. It can help you keep track of expenses, income, budgets and everything in between. It even supports credit cards, shared
-household accounts and savings accounts! It's pretty fancy. You should use it to save and organise money.
+[](https://i.nder.be/g63q05m0) [](https://i.nder.be/c2g30ngg)
+
+"Firefly III" is a financial manager. It can help you keep track of expenses, income, budgets and everything in between. It even supports credit cards, shared household accounts and savings accounts! It's pretty fancy. You should use it to save and organise money.
+
+## Installation
+
+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://jc5.github.io/firefly-iii/installation-guide/).
+
+## More about Firefly III
Personal financial management is pretty difficult, and everybody has their own approach to it. Some people make budgets, other people limit their cashflow by throwing away their credit cards, others try to increase their current cashflow. There are tons of ways to save and earn money.
@@ -24,3 +26,5 @@ Firefly works on the principle that if you know where you're money is going, you
- If you feel you're missing something you can just ask me and I'll add it!
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://jc5.github.io/firefly-iii/).
+
+If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
\ No newline at end of file
diff --git a/_development/.coveralls.yml b/_development/.coveralls.yml
deleted file mode 100644
index eb8e63b57e..0000000000
--- a/_development/.coveralls.yml
+++ /dev/null
@@ -1 +0,0 @@
-src_dir: .
diff --git a/_development/.csslintrc b/_development/.csslintrc
deleted file mode 100644
index aacba956e5..0000000000
--- a/_development/.csslintrc
+++ /dev/null
@@ -1,2 +0,0 @@
---exclude-exts=.min.css
---ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
diff --git a/_development/.eslintignore b/_development/.eslintignore
deleted file mode 100644
index 96212a3593..0000000000
--- a/_development/.eslintignore
+++ /dev/null
@@ -1 +0,0 @@
-**/*{.,-}min.js
diff --git a/_development/.eslintrc b/_development/.eslintrc
deleted file mode 100644
index 9faa37508e..0000000000
--- a/_development/.eslintrc
+++ /dev/null
@@ -1,213 +0,0 @@
-ecmaFeatures:
- modules: true
- jsx: true
-
-env:
- amd: true
- browser: true
- es6: true
- jquery: true
- node: true
-
-# http://eslint.org/docs/rules/
-rules:
- # Possible Errors
- comma-dangle: [2, never]
- no-cond-assign: 2
- no-console: 0
- no-constant-condition: 2
- no-control-regex: 2
- no-debugger: 2
- no-dupe-args: 2
- no-dupe-keys: 2
- no-duplicate-case: 2
- no-empty: 2
- no-empty-character-class: 2
- no-ex-assign: 2
- no-extra-boolean-cast: 2
- no-extra-parens: 0
- no-extra-semi: 2
- no-func-assign: 2
- no-inner-declarations: [2, functions]
- no-invalid-regexp: 2
- no-irregular-whitespace: 2
- no-negated-in-lhs: 2
- no-obj-calls: 2
- no-regex-spaces: 2
- no-sparse-arrays: 2
- no-unexpected-multiline: 2
- no-unreachable: 2
- use-isnan: 2
- valid-jsdoc: 0
- valid-typeof: 2
-
- # Best Practices
- accessor-pairs: 2
- block-scoped-var: 0
- complexity: [2, 6]
- consistent-return: 0
- curly: 0
- default-case: 0
- dot-location: 0
- dot-notation: 0
- eqeqeq: 2
- guard-for-in: 2
- no-alert: 2
- no-caller: 2
- no-case-declarations: 2
- no-div-regex: 2
- no-else-return: 0
- no-empty-label: 2
- no-empty-pattern: 2
- no-eq-null: 2
- no-eval: 2
- no-extend-native: 2
- no-extra-bind: 2
- no-fallthrough: 2
- no-floating-decimal: 0
- no-implicit-coercion: 0
- no-implied-eval: 2
- no-invalid-this: 0
- no-iterator: 2
- no-labels: 0
- no-lone-blocks: 2
- no-loop-func: 2
- no-magic-number: 0
- no-multi-spaces: 0
- no-multi-str: 0
- no-native-reassign: 2
- no-new-func: 2
- no-new-wrappers: 2
- no-new: 2
- no-octal-escape: 2
- no-octal: 2
- no-proto: 2
- no-redeclare: 2
- no-return-assign: 2
- no-script-url: 2
- no-self-compare: 2
- no-sequences: 0
- no-throw-literal: 0
- no-unused-expressions: 2
- no-useless-call: 2
- no-useless-concat: 2
- no-void: 2
- no-warning-comments: 0
- no-with: 2
- radix: 2
- vars-on-top: 0
- wrap-iife: 2
- yoda: 0
-
- # Strict
- strict: 0
-
- # Variables
- init-declarations: 0
- no-catch-shadow: 2
- no-delete-var: 2
- no-label-var: 2
- no-shadow-restricted-names: 2
- no-shadow: 0
- no-undef-init: 2
- no-undef: 0
- no-undefined: 0
- no-unused-vars: 0
- no-use-before-define: 0
-
- # Node.js and CommonJS
- callback-return: 2
- global-require: 2
- handle-callback-err: 2
- no-mixed-requires: 0
- no-new-require: 0
- no-path-concat: 2
- no-process-exit: 2
- no-restricted-modules: 0
- no-sync: 0
-
- # Stylistic Issues
- array-bracket-spacing: 0
- block-spacing: 0
- brace-style: 0
- camelcase: 0
- comma-spacing: 0
- comma-style: 0
- computed-property-spacing: 0
- consistent-this: 0
- eol-last: 0
- func-names: 0
- func-style: 0
- id-length: 0
- id-match: 0
- indent: 0
- jsx-quotes: 0
- key-spacing: 0
- linebreak-style: 0
- lines-around-comment: 0
- max-depth: 0
- max-len: 0
- max-nested-callbacks: 0
- max-params: 0
- max-statements: [2, 30]
- new-cap: 0
- new-parens: 0
- newline-after-var: 0
- no-array-constructor: 0
- no-bitwise: 0
- no-continue: 0
- no-inline-comments: 0
- no-lonely-if: 0
- no-mixed-spaces-and-tabs: 0
- no-multiple-empty-lines: 0
- no-negated-condition: 0
- no-nested-ternary: 0
- no-new-object: 0
- no-plusplus: 0
- no-restricted-syntax: 0
- no-spaced-func: 0
- no-ternary: 0
- no-trailing-spaces: 0
- no-underscore-dangle: 0
- no-unneeded-ternary: 0
- object-curly-spacing: 0
- one-var: 0
- operator-assignment: 0
- operator-linebreak: 0
- padded-blocks: 0
- quote-props: 0
- quotes: 0
- require-jsdoc: 0
- semi-spacing: 0
- semi: 0
- sort-vars: 0
- space-after-keywords: 0
- space-before-blocks: 0
- space-before-function-paren: 0
- space-before-keywords: 0
- space-in-parens: 0
- space-infix-ops: 0
- space-return-throw-case: 0
- space-unary-ops: 0
- spaced-comment: 0
- wrap-regex: 0
-
- # ECMAScript 6
- arrow-body-style: 0
- arrow-parens: 0
- arrow-spacing: 0
- constructor-super: 0
- generator-star-spacing: 0
- no-arrow-condition: 0
- no-class-assign: 0
- no-const-assign: 0
- no-dupe-class-members: 0
- no-this-before-super: 0
- no-var: 0
- object-shorthand: 0
- prefer-arrow-callback: 0
- prefer-const: 0
- prefer-reflect: 0
- prefer-spread: 0
- prefer-template: 0
- require-yield: 0
diff --git a/_development/.jshintrc b/_development/.jshintrc
deleted file mode 100644
index e6395a276a..0000000000
--- a/_development/.jshintrc
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "undef": true,
- "unused": false,
- "strict": true,
- "browser": true,
- "jquery": true,
- "devel": true,
- "globals": [
- "language",
- "token",
- "currencyCode",
- "$",
- "token",
- "accountID",
- "billID",
- "currentMonthName",
- "previousMonthName",
- "nextMonthName",
- "everything",
- "moment"
- ]
-}
\ No newline at end of file
diff --git a/_development/codesniffer/ruleset.xml b/_development/codesniffer/ruleset.xml
deleted file mode 100644
index 5a6a92bb44..0000000000
--- a/_development/codesniffer/ruleset.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
';
+ print_r($data);
+
+ $journal->description = $data['journal_description'];
+ $journal->transaction_currency_id = $data['journal_currency_id'];
+ $journal->date = $data['date'];
+ $journal->interest_date = $data['interest_date'];
+ $journal->book_date = $data['book_date'];
+ $journal->process_date = $data['process_date'];
+ $journal->save();
+
+ // delete original transactions, and recreate them.
+ $journal->transactions()->delete();
+
+ foreach ($data['transactions'] as $transaction) {
+ $this->storeTransaction($journal, $transaction);
+ }
+
+ $journal->completed = true;
+ $journal->save();
+
+ return $journal;
+ }
+
+ /**
+ * @param string $type
+ * @param array $transaction
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ private function storeAccounts(string $type, array $transaction): array
+ {
+ $sourceAccount = null;
+ $destinationAccount = null;
+ switch ($type) {
+ case TransactionType::WITHDRAWAL:
+ list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($transaction);
+ break;
+ case TransactionType::DEPOSIT:
+ list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction);
+ break;
+ case TransactionType::TRANSFER:
+ $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['source_account_id'])->first();
+ $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['destination_account_id'])->first();
+ break;
+ default:
+ throw new FireflyException('Cannot handle ' . e($type));
+ }
+
+ return [$sourceAccount, $destinationAccount];
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ */
+ private function storeDepositAccounts(array $data): array
+ {
+ $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
+
+ if (strlen($data['source_account_name']) > 0) {
+ $sourceType = AccountType::where('type', 'Revenue account')->first();
+ $sourceAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
+ );
+
+ return [$sourceAccount, $destinationAccount];
+ }
+
+ $sourceType = AccountType::where('type', 'Cash account')->first();
+ $sourceAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
+ );
+
+ return [$sourceAccount, $destinationAccount];
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ */
+ private function storeWithdrawalAccounts(array $data): array
+ {
+ $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
+
+ if (strlen($data['destination_account_name']) > 0) {
+ $destinationType = AccountType::where('type', 'Expense account')->first();
+ $destinationAccount = Account::firstOrCreateEncrypted(
+ [
+ 'user_id' => $this->user->id,
+ 'account_type_id' => $destinationType->id,
+ 'name' => $data['destination_account_name'],
+ 'active' => 1,
+ ]
+ );
+
+ return [$sourceAccount, $destinationAccount];
+ }
+ $destinationType = AccountType::where('type', 'Cash account')->first();
+ $destinationAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
+ );
+
+ return [$sourceAccount, $destinationAccount];
+
+
+ }
+}
\ No newline at end of file
diff --git a/app/Crud/Split/JournalInterface.php b/app/Crud/Split/JournalInterface.php
new file mode 100644
index 0000000000..81c852398d
--- /dev/null
+++ b/app/Crud/Split/JournalInterface.php
@@ -0,0 +1,47 @@
+budgetLimit = $budgetLimit;
+ $this->end = $end;
+
+ }
+
+}
diff --git a/app/Events/BudgetLimitUpdated.php b/app/Events/BudgetLimitUpdated.php
new file mode 100644
index 0000000000..f953b84285
--- /dev/null
+++ b/app/Events/BudgetLimitUpdated.php
@@ -0,0 +1,48 @@
+budgetLimit = $budgetLimit;
+ $this->end = $end;
+
+ }
+
+}
diff --git a/app/Events/Event.php b/app/Events/Event.php
index 74534b1d8b..65b6e30824 100644
--- a/app/Events/Event.php
+++ b/app/Events/Event.php
@@ -1,4 +1,12 @@
journal = $journal;
$this->piggyBankId = $piggyBankId;
diff --git a/app/Events/TransactionJournalUpdated.php b/app/Events/TransactionJournalUpdated.php
index e6c11d44c1..03ed37a609 100644
--- a/app/Events/TransactionJournalUpdated.php
+++ b/app/Events/TransactionJournalUpdated.php
@@ -1,11 +1,18 @@
journal = $journal;
}
diff --git a/app/Events/TransactionStored.php b/app/Events/TransactionStored.php
new file mode 100644
index 0000000000..bb50964738
--- /dev/null
+++ b/app/Events/TransactionStored.php
@@ -0,0 +1,39 @@
+transaction = $transaction;
+ }
+
+}
diff --git a/app/Events/UserRegistration.php b/app/Events/UserRegistration.php
index 0a37165839..46670d59d1 100644
--- a/app/Events/UserRegistration.php
+++ b/app/Events/UserRegistration.php
@@ -1,5 +1,4 @@
0,
+ 'email' => 'unknown@example.com',
+ ];
+ if (Auth::check()) {
+ $userData['id'] = Auth::user()->id;
+ $userData['email'] = Auth::user()->email;
+ }
$data = [
'class' => get_class($exception),
'errorMessage' => $exception->getMessage(),
@@ -80,7 +92,7 @@ class Handler extends ExceptionHandler
];
// create job that will mail.
- $job = new MailError($user, env('SITE_OWNER'), Request::ip(), $data);
+ $job = new MailError($userData, env('SITE_OWNER'), Request::ip(), $data);
dispatch($job);
}
diff --git a/app/Exceptions/NotImplementedException.php b/app/Exceptions/NotImplementedException.php
index f5960285b8..6cab7e0510 100644
--- a/app/Exceptions/NotImplementedException.php
+++ b/app/Exceptions/NotImplementedException.php
@@ -1,4 +1,12 @@
repository = app('FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface');
+ /** @var AttachmentRepositoryInterface repository */
+ $this->repository = app(AttachmentRepositoryInterface::class);
// make storage:
$this->uploadDisk = Storage::disk('upload');
$this->exportDisk = Storage::disk('export');
@@ -68,8 +70,8 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
// put the explanation string in a file and attach it as well.
$file = $this->job->key . '-Source of all your attachments explained.txt';
$this->exportDisk->put($file, $this->explanationString);
- Log::debug('Also put explanation file "' . $file . '" in the zip.');
$this->getFiles()->push($file);
+
return true;
}
@@ -102,14 +104,12 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
private function exportAttachment(Attachment $attachment): bool
{
$file = $attachment->fileName();
- Log::debug('Original file is at "' . $file . '".');
if ($this->uploadDisk->exists($file)) {
try {
$decrypted = Crypt::decrypt($this->uploadDisk->get($file));
$exportFile = $this->exportFileName($attachment);
$this->exportDisk->put($exportFile, $decrypted);
$this->getFiles()->push($exportFile);
- Log::debug('Stored file content in new file "' . $exportFile . '", which will be in the final zip file.');
// explain:
$this->explain($attachment);
@@ -142,8 +142,6 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
{
$attachments = $this->repository->get();
- Log::debug('Found ' . $attachments->count() . ' attachments.');
-
return $attachments;
}
}
diff --git a/app/Export/Collector/BasicCollector.php b/app/Export/Collector/BasicCollector.php
index fb894e0deb..b6f0f1e7cb 100644
--- a/app/Export/Collector/BasicCollector.php
+++ b/app/Export/Collector/BasicCollector.php
@@ -1,5 +1,4 @@
uploadDisk->files();
- Log::debug('Found ' . count($files) . ' files in the upload directory.');
foreach ($files as $entry) {
$this->processOldUpload($entry);
}
+
return true;
}
@@ -85,11 +86,9 @@ class UploadCollector extends BasicCollector implements CollectorInterface
{
$len = strlen($this->expected);
if (substr($entry, 0, $len) === $this->expected) {
- Log::debug($entry . ' is part of this users original uploads.');
return true;
}
- Log::debug($entry . ' is not part of this users original uploads.');
return false;
}
@@ -112,7 +111,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface
// continue with file:
$date = $this->getOriginalUploadDate($entry);
$file = $this->job->key . '-Old CSV import dated ' . $date . '.csv';
- Log::debug('Will put "' . $file . '" in the zip file.');
$this->exportDisk->put($file, $content);
$this->getFiles()->push($file);
}
diff --git a/app/Export/ConfigurationFile.php b/app/Export/ConfigurationFile.php
index 706b2550d7..778369dbe8 100644
--- a/app/Export/ConfigurationFile.php
+++ b/app/Export/ConfigurationFile.php
@@ -1,5 +1,4 @@
'Y-m-d', // unfortunately, this is hard-coded.
@@ -57,8 +58,6 @@ class ConfigurationFile
$configuration['roles'][] = $types[$field];
}
$file = $this->job->key . '-configuration.json';
- Log::debug('Created JSON config file.');
- Log::debug('Will put "' . $file . '" in the ZIP file.');
$this->exportDisk->put($file, json_encode($configuration, JSON_PRETTY_PRINT));
return $file;
diff --git a/app/Export/Entry.php b/app/Export/Entry.php
deleted file mode 100644
index 521f7928e0..0000000000
--- a/app/Export/Entry.php
+++ /dev/null
@@ -1,463 +0,0 @@
-setDescription($journal->description);
- $entry->setDate($journal->date->format('Y-m-d'));
- $entry->setAmount(TransactionJournal::amount($journal));
-
- /** @var Budget $budget */
- $budget = $journal->budgets->first();
- if (!is_null($budget)) {
- $entry->setBudgetId($budget->id);
- $entry->setBudgetName($budget->name);
- }
-
- /** @var Category $category */
- $category = $journal->categories->first();
- if (!is_null($category)) {
- $entry->setCategoryId($category->id);
- $entry->setCategoryName($category->name);
- }
-
- if (!is_null($journal->bill_id)) {
- $entry->setBillId($journal->bill_id);
- $entry->setBillName($journal->bill->name);
- }
-
- /** @var Account $sourceAccount */
- $sourceAccount = TransactionJournal::sourceAccount($journal);
- $entry->setFromAccountId($sourceAccount->id);
- $entry->setFromAccountName($sourceAccount->name);
- $entry->setFromAccountIban($sourceAccount->iban);
- $entry->setFromAccountType($sourceAccount->accountType->type);
- $entry->setFromAccountNumber($sourceAccount->getMeta('accountNumber'));
-
-
- /** @var Account $destination */
- $destination = TransactionJournal::destinationAccount($journal);
- $entry->setToAccountId($destination->id);
- $entry->setToAccountName($destination->name);
- $entry->setToAccountIban($destination->iban);
- $entry->setToAccountType($destination->accountType->type);
- $entry->setToAccountNumber($destination->getMeta('accountNumber'));
-
- return $entry;
-
- }
-
- /**
- * @return array
- */
- public static function getTypes(): array
- {
- // key = field name (see top of class)
- // value = field type (see csv.php under 'roles')
- return [
- 'amount' => 'amount',
- 'date' => 'date-transaction',
- 'description' => 'description',
- 'billId' => 'bill-id',
- 'billName' => 'bill-name',
- 'budgetId' => 'budget-id',
- 'budgetName' => 'budget-name',
- 'categoryId' => 'category-id',
- 'categoryName' => 'category-name',
- 'fromAccountId' => 'account-id',
- 'fromAccountNumber' => 'account-number',
- 'fromAccountName' => 'account-name',
- 'fromAccountIban' => 'account-iban',
- 'fromAccountType' => '_ignore', // no, Firefly cannot import what it exports. I know :D
- 'toAccountId' => 'opposing-id',
- 'toAccountNumber' => 'account-number',
- 'toAccountName' => 'opposing-name',
- 'toAccountIban' => 'opposing-iban',
- 'toAccountType' => '_ignore',
- ];
- }
-
- /**
- * @return string
- */
- public function getAmount(): string
- {
- return $this->amount;
- }
-
- /**
- * @param string $amount
- */
- public function setAmount(string $amount)
- {
- $this->amount = $amount;
- }
-
- /**
- * @return int
- */
- public function getBillId(): int
- {
- return $this->billId;
- }
-
- /**
- * @param int $billId
- */
- public function setBillId(int $billId)
- {
- $this->billId = $billId;
- }
-
- /**
- * @return string
- */
- public function getBillName(): string
- {
- return $this->billName;
- }
-
- /**
- * @param string $billName
- */
- public function setBillName(string $billName)
- {
- $this->billName = $billName;
- }
-
- /**
- * @return int
- */
- public function getBudgetId(): int
- {
- return $this->budgetId;
- }
-
- /**
- * @param int $budgetId
- */
- public function setBudgetId(int $budgetId)
- {
- $this->budgetId = $budgetId;
- }
-
- /**
- * @return string
- */
- public function getBudgetName(): string
- {
- return $this->budgetName;
- }
-
- /**
- * @param string $budgetName
- */
- public function setBudgetName(string $budgetName)
- {
- $this->budgetName = $budgetName;
- }
-
- /**
- * @return int
- */
- public function getCategoryId(): int
- {
- return $this->categoryId;
- }
-
- /**
- * @param int $categoryId
- */
- public function setCategoryId(int $categoryId)
- {
- $this->categoryId = $categoryId;
- }
-
- /**
- * @return string
- */
- public function getCategoryName(): string
- {
- return $this->categoryName;
- }
-
- /**
- * @param string $categoryName
- */
- public function setCategoryName(string $categoryName)
- {
- $this->categoryName = $categoryName;
- }
-
- /**
- * @return string
- */
- public function getDate(): string
- {
- return $this->date;
- }
-
- /**
- * @param string $date
- */
- public function setDate(string $date)
- {
- $this->date = $date;
- }
-
- /**
- * @return string
- */
- public function getDescription(): string
- {
- return $this->description;
- }
-
- /**
- * @param string $description
- */
- public function setDescription(string $description)
- {
- $this->description = $description;
- }
-
- /**
- * @return string
- */
- public function getFromAccountIban(): string
- {
- return $this->fromAccountIban;
- }
-
- /**
- * @param string $fromAccountIban
- */
- public function setFromAccountIban(string $fromAccountIban)
- {
- $this->fromAccountIban = $fromAccountIban;
- }
-
- /**
- * @return int
- */
- public function getFromAccountId():int
- {
- return $this->fromAccountId;
- }
-
- /**
- * @param int $fromAccountId
- */
- public function setFromAccountId(int $fromAccountId)
- {
- $this->fromAccountId = $fromAccountId;
- }
-
- /**
- * @return string
- */
- public function getFromAccountName(): string
- {
- return $this->fromAccountName;
- }
-
- /**
- * @param string $fromAccountName
- */
- public function setFromAccountName(string $fromAccountName)
- {
- $this->fromAccountName = $fromAccountName;
- }
-
- /**
- * @return string
- */
- public function getFromAccountNumber(): string
- {
- return $this->fromAccountNumber;
- }
-
- /**
- * @param string $fromAccountNumber
- */
- public function setFromAccountNumber(string $fromAccountNumber)
- {
- $this->fromAccountNumber = $fromAccountNumber;
- }
-
- /**
- * @return string
- */
- public function getFromAccountType(): string
- {
- return $this->fromAccountType;
- }
-
- /**
- * @param string $fromAccountType
- */
- public function setFromAccountType(string $fromAccountType)
- {
- $this->fromAccountType = $fromAccountType;
- }
-
- /**
- * @return string
- */
- public function getToAccountIban(): string
- {
- return $this->toAccountIban;
- }
-
- /**
- * @param string $toAccountIban
- */
- public function setToAccountIban(string $toAccountIban)
- {
- $this->toAccountIban = $toAccountIban;
- }
-
- /**
- * @return int
- */
- public function getToAccountId(): int
- {
- return $this->toAccountId;
- }
-
- /**
- * @param int $toAccountId
- */
- public function setToAccountId(int $toAccountId)
- {
- $this->toAccountId = $toAccountId;
- }
-
- /**
- * @return string
- */
- public function getToAccountName(): string
- {
- return $this->toAccountName;
- }
-
- /**
- * @param string $toAccountName
- */
- public function setToAccountName(string $toAccountName)
- {
- $this->toAccountName = $toAccountName;
- }
-
- /**
- * @return string
- */
- public function getToAccountNumber(): string
- {
- return $this->toAccountNumber;
- }
-
- /**
- * @param string $toAccountNumber
- */
- public function setToAccountNumber(string $toAccountNumber)
- {
- $this->toAccountNumber = $toAccountNumber;
- }
-
- /**
- * @return string
- */
- public function getToAccountType(): string
- {
- return $this->toAccountType;
- }
-
- /**
- * @param string $toAccountType
- */
- public function setToAccountType(string $toAccountType)
- {
- $this->toAccountType = $toAccountType;
- }
-
-
-}
diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php
new file mode 100644
index 0000000000..531d6ef72b
--- /dev/null
+++ b/app/Export/Entry/Entry.php
@@ -0,0 +1,130 @@
+sourceAccounts = new Collection;
+ $this->destinationAccounts = new Collection;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ *
+ * @return Entry
+ */
+ public static function fromJournal(TransactionJournal $journal)
+ {
+
+ $entry = new self;
+ $entry->description = $journal->description;
+ $entry->date = $journal->date->format('Y-m-d');
+ $entry->amount = TransactionJournal::amount($journal);
+
+ $entry->budget = new EntryBudget($journal->budgets->first());
+ $entry->category = new EntryCategory($journal->categories->first());
+ $entry->bill = new EntryBill($journal->bill);
+
+ $sources = TransactionJournal::sourceAccountList($journal);
+ $destinations = TransactionJournal::destinationAccountList($journal);
+ $entry->sourceAccount = new EntryAccount($sources->first());
+ $entry->destinationAccount = new EntryAccount($destinations->first());
+
+ foreach ($sources as $source) {
+ $entry->sourceAccounts->push(new EntryAccount($source));
+ }
+
+ foreach ($destinations as $destination) {
+ $entry->destinationAccounts->push(new EntryAccount($destination));
+ }
+
+ return $entry;
+
+ }
+
+ /**
+ * @return array
+ */
+ public static function getFieldsAndTypes(): array
+ {
+ // key = field name (see top of class)
+ // value = field type (see csv.php under 'roles')
+ return [
+ 'description' => 'description',
+ 'amount' => 'amount',
+ 'date' => 'date-transaction',
+ 'source_account_id' => 'account-id',
+ 'source_account_name' => 'account-name',
+ 'source_account_iban' => 'account-iban',
+ 'source_account_type' => '_ignore',
+ 'source_account_number' => 'account-number',
+ 'destination_account_id' => 'opposing-id',
+ 'destination_account_name' => 'opposing-name',
+ 'destination_account_iban' => 'opposing-iban',
+ 'destination_account_type' => '_ignore',
+ 'destination_account_number' => 'account-number',
+ 'budget_id' => 'budget-id',
+ 'budget_name' => 'budget-name',
+ 'category_id' => 'category-id',
+ 'category_name' => 'category-name',
+ 'bill_id' => 'bill-id',
+ 'bill_name' => 'bill-name',
+ ];
+ }
+
+}
diff --git a/app/Export/Entry/EntryAccount.php b/app/Export/Entry/EntryAccount.php
new file mode 100644
index 0000000000..4a63c466a5
--- /dev/null
+++ b/app/Export/Entry/EntryAccount.php
@@ -0,0 +1,47 @@
+accountId = $account->id;
+ $this->name = $account->name;
+ $this->iban = $account->iban;
+ $this->type = $account->accountType->type;
+ $this->number = $account->getMeta('accountNumber');
+ }
+}
\ No newline at end of file
diff --git a/app/Export/Entry/EntryBill.php b/app/Export/Entry/EntryBill.php
new file mode 100644
index 0000000000..67d48af854
--- /dev/null
+++ b/app/Export/Entry/EntryBill.php
@@ -0,0 +1,41 @@
+billId = $bill->id;
+ $this->name = $bill->name;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/Export/Entry/EntryBudget.php b/app/Export/Entry/EntryBudget.php
new file mode 100644
index 0000000000..730de35406
--- /dev/null
+++ b/app/Export/Entry/EntryBudget.php
@@ -0,0 +1,41 @@
+budgetId = $budget->id;
+ $this->name = $budget->name;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/Export/Entry/EntryCategory.php b/app/Export/Entry/EntryCategory.php
new file mode 100644
index 0000000000..18d19f0642
--- /dev/null
+++ b/app/Export/Entry/EntryCategory.php
@@ -0,0 +1,40 @@
+categoryId = $category->id;
+ $this->name = $category->name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Export/Exporter/BasicExporter.php b/app/Export/Exporter/BasicExporter.php
index 1c847a5ebe..feed1a8674 100644
--- a/app/Export/Exporter/BasicExporter.php
+++ b/app/Export/Exporter/BasicExporter.php
@@ -1,5 +1,4 @@
fileName;
+ $writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
+ $rows = [];
- // create CSV writer:
- $writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
-
- // all rows:
- $rows = [];
-
- // add header:
- $first = $this->getEntries()->first();
- $rows[] = array_keys(get_object_vars($first));
-
- // then the rest:
+ // Count the maximum number of sources and destinations each entry has. May need to expand the number of export fields:
+ $maxSourceAccounts = 1;
+ $maxDestAccounts = 1;
/** @var Entry $entry */
foreach ($this->getEntries() as $entry) {
- $rows[] = array_values(get_object_vars($entry));
+ $sources = $entry->sourceAccounts->count();
+ $destinations = $entry->destinationAccounts->count();
+ $maxSourceAccounts = max($maxSourceAccounts, $sources);
+ $maxDestAccounts = max($maxDestAccounts, $destinations);
+ }
+ $rows[] = array_keys($this->getFieldsAndTypes($maxSourceAccounts, $maxDestAccounts));
+ /** @var Entry $entry */
+ foreach ($this->getEntries() as $entry) {
+ // order is defined in Entry::getFieldsAndTypes.
+ $current = [$entry->description, $entry->amount, $entry->date];
+ $sourceData = $this->getAccountData($maxSourceAccounts, $entry->sourceAccounts);
+ $current = array_merge($current, $sourceData);
+ $destData = $this->getAccountData($maxDestAccounts, $entry->destinationAccounts);
+ $current = array_merge($current, $destData);
+ $rest = [$entry->budget->budgetId, $entry->budget->name, $entry->category->categoryId, $entry->category->name, $entry->bill->billId,
+ $entry->bill->name];
+ $current = array_merge($current, $rest);
+ $rows[] = $current;
}
$writer->insertAll($rows);
+
return true;
}
+ /**
+ * @param int $max
+ * @param Collection $accounts
+ *
+ * @return array
+ */
+ private function getAccountData(int $max, Collection $accounts): array
+ {
+ $current = [];
+ for ($i = 0; $i < $max; $i++) {
+ /** @var EntryAccount $source */
+ $source = $accounts->get($i);
+ $currentId = '';
+ $currentName = '';
+ $currentIban = '';
+ $currentType = '';
+ $currentNumber = '';
+ if ($source) {
+ $currentId = $source->accountId;
+ $currentName = $source->name;
+ $currentIban = $source->iban;
+ $currentType = $source->type;
+ $currentNumber = $source->number;
+ }
+ $current[] = $currentId;
+ $current[] = $currentName;
+ $current[] = $currentIban;
+ $current[] = $currentType;
+ $current[] = $currentNumber;
+ }
+ unset($source);
+
+ return $current;
+ }
+
+ /**
+ * @param int $sources
+ * @param int $destinations
+ *
+ * @return array
+ */
+ private function getFieldsAndTypes(int $sources, int $destinations): array
+ {
+ // key = field name (see top of class)
+ // value = field type (see csv.php under 'roles')
+ $array = [
+ 'description' => 'description',
+ 'amount' => 'amount',
+ 'date' => 'date-transaction',
+ ];
+ for ($i = 0; $i < $sources; $i++) {
+ $array['source_account_' . $i . '_id'] = 'account-id';
+ $array['source_account_' . $i . '_name'] = 'account-name';
+ $array['source_account_' . $i . '_iban'] = 'account-iban';
+ $array['source_account_' . $i . '_type'] = '_ignore';
+ $array['source_account_' . $i . '_number'] = 'account-number';
+ }
+ for ($i = 0; $i < $destinations; $i++) {
+ $array['destination_account_' . $i . '_id'] = 'account-id';
+ $array['destination_account_' . $i . '_name'] = 'account-name';
+ $array['destination_account_' . $i . '_iban'] = 'account-iban';
+ $array['destination_account_' . $i . '_type'] = '_ignore';
+ $array['destination_account_' . $i . '_number'] = 'account-number';
+ }
+
+ $array['budget_id'] = 'budget-id';
+ $array['budget_name'] = 'budget-name';
+ $array['category_id'] = 'category-id';
+ $array['category_name'] = 'category-name';
+ $array['bill_id'] = 'bill-id';
+ $array['bill_name'] = 'bill-name';
+
+ return $array;
+ }
+
private function tempFile()
{
$this->fileName = $this->job->key . '-records.csv';
diff --git a/app/Export/Exporter/ExporterInterface.php b/app/Export/Exporter/ExporterInterface.php
index 57d1a174ad..a529f02be6 100644
--- a/app/Export/Exporter/ExporterInterface.php
+++ b/app/Export/Exporter/ExporterInterface.php
@@ -1,5 +1,4 @@
job]);
+ /** @var AttachmentCollector $attachmentCollector */
+ $attachmentCollector = app(AttachmentCollector::class, [$this->job]);
$attachmentCollector->run();
$this->files = $this->files->merge($attachmentCollector->getFiles());
+
return true;
}
@@ -89,17 +93,10 @@ class Processor
*/
public function collectJournals(): bool
{
- $args = [$this->accounts, Auth::user(), $this->settings['startDate'], $this->settings['endDate']];
- /** @var JournalCollector $journalCollector */
- $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args);
- $this->journals = $journalCollector->collect();
- Log::debug(
- 'Collected ' .
- $this->journals->count() . ' journals (between ' .
- $this->settings['startDate']->format('Y-m-d') . ' and ' .
- $this->settings['endDate']->format('Y-m-d')
- . ').'
- );
+ /** @var JournalRepositoryInterface $repository */
+ $repository = app(JournalRepositoryInterface::class);
+ $this->journals = $repository->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
+
return true;
}
@@ -108,10 +105,12 @@ class Processor
*/
public function collectOldUploads(): bool
{
- $uploadCollector = app('FireflyIII\Export\Collector\UploadCollector', [$this->job]);
+ /** @var UploadCollector $uploadCollector */
+ $uploadCollector = app(UploadCollector::class, [$this->job]);
$uploadCollector->run();
$this->files = $this->files->merge($uploadCollector->getFiles());
+
return true;
}
@@ -126,7 +125,7 @@ class Processor
$this->exportEntries->push(Entry::fromJournal($journal));
$count++;
}
- Log::debug('Converted ' . $count . ' journals to "Entry" objects.');
+
return true;
}
@@ -135,8 +134,9 @@ class Processor
*/
public function createConfigFile(): bool
{
- $this->configurationMaker = app('FireflyIII\Export\ConfigurationFile', [$this->job]);
+ $this->configurationMaker = app(ConfigurationFile::class, [$this->job]);
$this->files->push($this->configurationMaker->make());
+
return true;
}
@@ -149,7 +149,6 @@ class Processor
$zip = new ZipArchive;
$file = $this->job->key . '.zip';
$fullPath = storage_path('export') . '/' . $file;
- Log::debug('Will create zip file at ' . $fullPath);
if ($zip->open($fullPath, ZipArchive::CREATE) !== true) {
throw new FireflyException('Cannot store zip file.');
@@ -159,20 +158,14 @@ class Processor
foreach ($this->getFiles() as $entry) {
// is part of this job?
$zipFileName = str_replace($this->job->key . '-', '', $entry);
- $result = $zip->addFromString($zipFileName, $disk->get($entry));
- if (!$result) {
- Log::error('Could not add "' . $entry . '" into zip file as "' . $zipFileName . '".');
- }
+ $zip->addFromString($zipFileName, $disk->get($entry));
}
$zip->close();
// delete the files:
- foreach ($this->getFiles() as $file) {
- Log::debug('Will now delete file "' . $file . '".');
- $disk->delete($file);
- }
- Log::debug('Done!');
+ $this->deleteFiles($disk);
+
return true;
}
@@ -181,13 +174,12 @@ class Processor
*/
public function exportJournals(): bool
{
- $exporterClass = Config::get('firefly.export_formats.' . $this->exportFormat);
+ $exporterClass = config('firefly.export_formats.' . $this->exportFormat);
$exporter = app($exporterClass, [$this->job]);
- Log::debug('Going to export ' . $this->exportEntries->count() . ' export entries into ' . $this->exportFormat . ' format.');
$exporter->setEntries($this->exportEntries);
$exporter->run();
$this->files->push($exporter->getFileName());
- Log::debug('Added "' . $exporter->getFileName() . '" to the list of files to include in the zip.');
+
return true;
}
@@ -198,4 +190,14 @@ class Processor
{
return $this->files;
}
+
+ /**
+ * @param FilesystemAdapter $disk
+ */
+ private function deleteFiles(FilesystemAdapter $disk)
+ {
+ foreach ($this->getFiles() as $file) {
+ $disk->delete($file);
+ }
+ }
}
diff --git a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php
index 9e90645c5e..48304d6245 100644
--- a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php
+++ b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php
@@ -1,5 +1,4 @@
[], 'datasets' => [[
'label' => trans('firefly.spent'),
'data' => []]]];
-
- $start->subDay();
- $ids = $this->getIdsFromCollection($accounts);
- $startBalances = Steam::balancesById($ids, $start);
- $endBalances = Steam::balancesById($ids, $end);
-
- $accounts->each(
- function (Account $account) use ($startBalances, $endBalances) {
- $id = $account->id;
- $startBalance = $this->isInArray($startBalances, $id);
- $endBalance = $this->isInArray($endBalances, $id);
- $diff = bcsub($endBalance, $startBalance);
- $account->difference = round($diff, 2);
- }
- );
-
- $accounts = $accounts->sortByDesc(
- function (Account $account) {
- return $account->difference;
- }
- );
-
foreach ($accounts as $account) {
if ($account->difference > 0) {
$data['labels'][] = $account->name;
@@ -80,7 +65,7 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
}
foreach ($accounts as $account) {
- $set = [
+ $data['datasets'][] = [
'label' => $account->name,
'fillColor' => 'rgba(220,220,220,0.2)',
'strokeColor' => 'rgba(220,220,220,1)',
@@ -88,20 +73,8 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
'pointStrokeColor' => '#fff',
'pointHighlightFill' => '#fff',
'pointHighlightStroke' => 'rgba(220,220,220,1)',
- 'data' => [],
+ 'data' => $account->balances,
];
- $current = clone $start;
- $range = Steam::balanceInRange($account, $start, clone $end);
- $previous = round(array_values($range)[0], 2);
- while ($current <= $end) {
- $format = $current->format('Y-m-d');
- $balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
-
- $set['data'][] = $balance;
- $previous = $balance;
- $current->addDay();
- }
- $data['datasets'][] = $set;
}
$data['count'] = count($data['datasets']);
@@ -110,71 +83,25 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
/**
* @param Account $account
- * @param Carbon $start
- * @param Carbon $end
+ * @param array $labels
+ * @param array $dataSet
*
* @return array
*/
- public function single(Account $account, Carbon $start, Carbon $end): array
+ public function single(Account $account, array $labels, array $dataSet): array
{
- // language:
- $format = (string)trans('config.month_and_day');
-
- $data = [
+ $data = [
'count' => 1,
- 'labels' => [],
+ 'labels' => $labels,
'datasets' => [
[
'label' => $account->name,
- 'data' => [],
+ 'data' => $dataSet,
],
],
];
- $range = Steam::balanceInRange($account, $start, $end);
- $current = clone $start;
- $previous = array_values($range)[0];
-
- while ($end >= $current) {
- $theDate = $current->format('Y-m-d');
- $balance = $range[$theDate] ?? $previous;
-
- $data['labels'][] = $current->formatLocalized($format);
- $data['datasets'][0]['data'][] = $balance;
- $previous = $balance;
- $current->addDay();
- }
return $data;
}
- /**
- * @param Collection $collection
- *
- * @return array
- */
- protected function getIdsFromCollection(Collection $collection): array
- {
- $ids = [];
- foreach ($collection as $entry) {
- $ids[] = $entry->id;
- }
-
- return array_unique($ids);
-
- }
-
- /**
- * @param $array
- * @param $entryId
- *
- * @return string
- */
- protected function isInArray($array, $entryId): string
- {
- if (isset($array[$entryId])) {
- return $array[$entryId];
- }
-
- return '0';
- }
}
diff --git a/app/Generator/Chart/Bill/BillChartGeneratorInterface.php b/app/Generator/Chart/Bill/BillChartGeneratorInterface.php
index ba70851f20..6ad00c4606 100644
--- a/app/Generator/Chart/Bill/BillChartGeneratorInterface.php
+++ b/app/Generator/Chart/Bill/BillChartGeneratorInterface.php
@@ -1,5 +1,4 @@
3,
- 'labels' => [],
- 'datasets' => [],
- ];
+ $data = ['count' => 3, 'labels' => [], 'datasets' => [],];
$minAmount = [];
$maxAmount = [];
$actualAmount = [];
@@ -67,9 +64,7 @@ class ChartJsBillChartGenerator implements BillChartGeneratorInterface
$data['labels'][] = $entry->date->formatLocalized($format);
$minAmount[] = round($bill->amount_min, 2);
$maxAmount[] = round($bill->amount_max, 2);
- /*
- * journalAmount has been collected in BillRepository::getJournals
- */
+ // journalAmount has been collected in BillRepository::getJournals
$actualAmount[] = round(TransactionJournal::amountPositive($entry), 2);
}
diff --git a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php
index 58d4e5302f..7e1e8f8d2a 100644
--- a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php
+++ b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php
@@ -1,5 +1,4 @@
data;
- $format = Config::get('firefly.' . $dateFormat . '.' . $language);
-
- $data = [
+ $format = strval(trans('config.' . $dateFormat));
+ $data = [
'labels' => [],
'datasets' => [
[
@@ -49,17 +53,6 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
return $data;
}
- /**
- *
- * @param Collection $entries
- *
- * @return array
- */
- public function budgetLimit(Collection $entries): array
- {
- return $this->budget($entries, 'monthAndDay');
- }
-
/**
* @param Collection $entries
*
@@ -139,6 +132,42 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
}
+ /**
+ * @param Collection $entries
+ * @param string $viewRange
+ *
+ * @return array
+ */
+ public function period(Collection $entries, string $viewRange) : array
+ {
+ $data = [
+ 'labels' => [],
+ 'datasets' => [
+ 0 => [
+ 'label' => trans('firefly.budgeted'),
+ 'data' => [],
+ ],
+ 1 => [
+ 'label' => trans('firefly.spent'),
+ 'data' => [],
+ ],
+ ],
+ 'count' => 2,
+ ];
+ foreach ($entries as $entry) {
+ $label = Navigation::periodShow($entry['date'], $viewRange);
+ $data['labels'][] = $label;
+ // data set 0 is budgeted
+ // data set 1 is spent:
+ $data['datasets'][0]['data'][] = $entry['budgeted'];
+ $data['datasets'][1]['data'][] = round(($entry['spent'] * -1), 2);
+
+ }
+
+ return $data;
+
+ }
+
/**
* @param Collection $budgets
* @param Collection $entries
diff --git a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php b/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php
index 3533eefec3..834f19aa12 100644
--- a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php
+++ b/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php
@@ -1,5 +1,4 @@
2,
'labels' => [],
@@ -42,8 +48,8 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
$spent = $entry[2];
$earned = $entry[3];
- $data['datasets'][0]['data'][] = bccomp($spent, '0') === 0 ? null : bcmul($spent, '-1');
- $data['datasets'][1]['data'][] = bccomp($earned, '0') === 0 ? null : $earned;
+ $data['datasets'][0]['data'][] = bccomp($spent, '0') === 0 ? null : round(bcmul($spent, '-1'), 4);
+ $data['datasets'][1]['data'][] = bccomp($earned, '0') === 0 ? null : round($earned, 4);
}
return $data;
@@ -116,18 +122,9 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
*/
public function multiYear(Collection $entries): array
{
- // dataset:
- $data = [
- 'count' => 0,
- 'labels' => [],
- 'datasets' => [],
- ];
// get labels from one of the categories (assuming there's at least one):
$first = $entries->first();
- $keys = array_keys($first['spent']);
- foreach ($keys as $year) {
- $data['labels'][] = strval($year);
- }
+ $data = ['count' => 0, 'labels' => array_keys($first['spent']), 'datasets' => [],];
// then, loop all entries and create datasets:
foreach ($entries as $entry) {
@@ -144,7 +141,6 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
$data['count'] = count($data['datasets']);
return $data;
-
}
/**
diff --git a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php b/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php
index 086e208039..e31a8a23ec 100644
--- a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php
+++ b/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php
@@ -1,4 +1,12 @@
date);
- $sum = bcadd($sum, $entry->sum);
+ foreach ($set as $key => $value) {
+ $date = new Carbon($key);
+ $sum = bcadd($sum, $value);
$data['labels'][] = $date->formatLocalized($format);
$data['datasets'][0]['data'][] = round($sum, 2);
}
diff --git a/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php b/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php
index d291d16c8f..14bf3888e9 100644
--- a/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php
+++ b/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php
@@ -1,13 +1,14 @@
count() == 1) {
- Log::debug('Will attach role.');
$repository->attachRole($event->user, 'owner');
}
}
diff --git a/app/Handlers/Events/BudgetLimitEventHandler.php b/app/Handlers/Events/BudgetLimitEventHandler.php
new file mode 100644
index 0000000000..5fb0439b2f
--- /dev/null
+++ b/app/Handlers/Events/BudgetLimitEventHandler.php
@@ -0,0 +1,107 @@
+budgetLimit;
+ $end = $event->end;
+ $set = $budgetLimit->limitrepetitions()
+ ->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
+ ->where('enddate', $end->format('Y-m-d 00:00:00'))
+ ->get();
+ if ($set->count() == 0) {
+ $repetition = new LimitRepetition;
+ $repetition->startdate = $budgetLimit->startdate;
+ $repetition->enddate = $end;
+ $repetition->amount = $budgetLimit->amount;
+ $repetition->budgetLimit()->associate($budgetLimit);
+
+ try {
+ $repetition->save();
+ } catch (QueryException $e) {
+ Log::error('Trying to save new LimitRepetition failed: ' . $e->getMessage());
+ }
+ }
+
+ if ($set->count() == 1) {
+ $repetition = $set->first();
+ $repetition->amount = $budgetLimit->amount;
+ $repetition->save();
+
+ }
+
+ }
+
+ /**
+ * @param BudgetLimitUpdated $event
+ */
+ public function update(BudgetLimitUpdated $event)
+ {
+ $budgetLimit = $event->budgetLimit;
+ $end = $event->end;
+ $set = $budgetLimit->limitrepetitions()
+ ->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
+ ->where('enddate', $end->format('Y-m-d 00:00:00'))
+ ->get();
+ if ($set->count() == 0) {
+ $repetition = new LimitRepetition;
+ $repetition->startdate = $budgetLimit->startdate;
+ $repetition->enddate = $end;
+ $repetition->amount = $budgetLimit->amount;
+ $repetition->budgetLimit()->associate($budgetLimit);
+
+ try {
+ $repetition->save();
+ } catch (QueryException $e) {
+ Log::error('Trying to save new LimitRepetition failed: ' . $e->getMessage());
+ }
+ }
+
+ if ($set->count() == 1) {
+ $repetition = $set->first();
+ $repetition->amount = $budgetLimit->amount;
+ $repetition->save();
+
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/Handlers/Events/ConnectJournalToPiggyBank.php b/app/Handlers/Events/ConnectJournalToPiggyBank.php
index 83089674cc..7aed8a222d 100644
--- a/app/Handlers/Events/ConnectJournalToPiggyBank.php
+++ b/app/Handlers/Events/ConnectJournalToPiggyBank.php
@@ -1,4 +1,12 @@
account_id == TransactionJournal::sourceAccount($journal)->id) {
+ $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
+ if (in_array($piggyBank->account_id, $sources)) {
$amount = bcmul($amount, '-1');
}
diff --git a/app/Handlers/Events/ConnectTransactionToPiggyBank.php b/app/Handlers/Events/ConnectTransactionToPiggyBank.php
new file mode 100644
index 0000000000..3e09a6fbcf
--- /dev/null
+++ b/app/Handlers/Events/ConnectTransactionToPiggyBank.php
@@ -0,0 +1,67 @@
+';
+ /** @var PiggyBankRepositoryInterface $repository */
+ $repository = app(PiggyBankRepositoryInterface::class);
+ $transaction = $event->transaction;
+ $piggyBank = $repository->find($transaction['piggy_bank_id']);
+
+ // valid piggy:
+ if (is_null($piggyBank->id)) {
+ return true;
+ }
+ $amount = strval($transaction['amount']);
+ // piggy bank account something with amount:
+ if ($transaction['source_account_id'] == $piggyBank->account_id) {
+ // if the source of this transaction is the same as the piggy bank,
+ // the money is being removed from the piggy bank. So the
+ // amount must be negative:
+ $amount = bcmul($amount, '-1');
+ }
+
+ $repetition = $piggyBank->currentRelevantRep();
+ // add or remove the money from the piggy bank:
+ $newAmount = bcadd(strval($repetition->currentamount), $amount);
+ $repetition->currentamount = $newAmount;
+ $repetition->save();
+
+ // now generate a piggy bank event:
+ PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'date' => $transaction['date'], 'amount' => $newAmount]);
+
+ return true;
+ }
+
+
+}
diff --git a/app/Handlers/Events/FireRulesForStore.php b/app/Handlers/Events/FireRulesForStore.php
index 05ab7006d8..7019b1b660 100644
--- a/app/Handlers/Events/FireRulesForStore.php
+++ b/app/Handlers/Events/FireRulesForStore.php
@@ -1,5 +1,4 @@
title . '".');
$rules = $group->rules()
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
@@ -54,7 +52,6 @@ class FireRulesForStore
/** @var Rule $rule */
foreach ($rules as $rule) {
- Log::debug('Now handling rule #' . $rule->id . ' (' . $rule->title . ')');
$processor = Processor::make($rule);
$processor->handleTransactionJournal($event->journal);
diff --git a/app/Handlers/Events/FireRulesForUpdate.php b/app/Handlers/Events/FireRulesForUpdate.php
index ae1e187d67..bbc6d2ce0d 100644
--- a/app/Handlers/Events/FireRulesForUpdate.php
+++ b/app/Handlers/Events/FireRulesForUpdate.php
@@ -1,5 +1,4 @@
title . '".');
$rules = $group->rules()
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
@@ -51,9 +49,6 @@ class FireRulesForUpdate
->get(['rules.*']);
/** @var Rule $rule */
foreach ($rules as $rule) {
- Log::debug('Now handling rule #' . $rule->id . ' (' . $rule->title . ')');
-
- Log::debug('Now handling rule #' . $rule->id . ' (' . $rule->title . ')');
$processor = Processor::make($rule);
$processor->handleTransactionJournal($event->journal);
diff --git a/app/Handlers/Events/ScanForBillsAfterStore.php b/app/Handlers/Events/ScanForBillsAfterStore.php
index 584e85160b..090fd852ef 100644
--- a/app/Handlers/Events/ScanForBillsAfterStore.php
+++ b/app/Handlers/Events/ScanForBillsAfterStore.php
@@ -1,13 +1,14 @@
getMessage());
}
+
return true;
}
}
diff --git a/app/Handlers/Events/UpdateJournalConnection.php b/app/Handlers/Events/UpdateJournalConnection.php
index bf385a6d50..ae9214c08d 100644
--- a/app/Handlers/Events/UpdateJournalConnection.php
+++ b/app/Handlers/Events/UpdateJournalConnection.php
@@ -1,4 +1,12 @@
user;
$ipAddress = $event->ipAddress;
$this->doConfirm($user, $ipAddress);
+
return true;
}
@@ -62,6 +64,7 @@ class UserConfirmation
$user = $event->user;
$ipAddress = $event->ipAddress;
$this->doConfirm($user, $ipAddress);
+
return true;
}
@@ -71,35 +74,21 @@ class UserConfirmation
*/
private function doConfirm(User $user, string $ipAddress)
{
- Log::debug('Trigger UserConfirmation::doConfirm');
-
- // if user must confirm account, send email
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
-
- // otherwise, auto-confirm:
if ($confirmAccount === false) {
- Log::debug('Confirm account is false, so user will be auto-confirmed.');
Preferences::setForUser($user, 'user_confirmed', true);
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
Preferences::mark();
return;
}
-
- // send email message:
$email = $user->email;
$code = str_random(16);
$route = route('do_confirm_account', [$code]);
-
- // set preferences:
Preferences::setForUser($user, 'user_confirmed', false);
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
Preferences::setForUser($user, 'user_confirmed_code', $code);
- Log::debug('Set preferences for user.');
-
- // send email.
try {
- Log::debug('Now in try block for user email message thing to ' . $email . '.');
Mail::send(
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
function (Message $message) use ($email) {
@@ -107,13 +96,10 @@ class UserConfirmation
}
);
} catch (Swift_TransportException $e) {
-
Log::error($e->getMessage());
} catch (Exception $e) {
- Log::debug('Caught general exception.');
Log::error($e->getMessage());
}
- Log::debug('Finished mail handling for activation.');
return;
}
diff --git a/app/Handlers/Events/UserEventListener.php b/app/Handlers/Events/UserEventListener.php
index 5f4e9b3029..984a9a0929 100644
--- a/app/Handlers/Events/UserEventListener.php
+++ b/app/Handlers/Events/UserEventListener.php
@@ -1,5 +1,4 @@
maxUploadSize = Config::get('firefly.maxUploadSize');
- $this->allowedMimes = Config::get('firefly.allowedMimes');
+ $this->maxUploadSize = config('firefly.maxUploadSize');
+ $this->allowedMimes = config('firefly.allowedMimes');
$this->errors = new MessageBag;
$this->messages = new MessageBag;
$this->uploadDisk = Storage::disk('upload');
@@ -81,26 +88,14 @@ class AttachmentHelper implements AttachmentHelperInterface
*/
public function saveAttachmentsForModel(Model $model): bool
{
- $files = null;
- try {
- if (Input::hasFile('attachments')) {
- $files = Input::file('attachments');
- }
- } catch (TypeError $e) {
- // Log it, do nothing else.
- Log::error($e->getMessage());
+ $files = $this->getFiles();
+
+ if (!is_null($files) && !is_array($files)) {
+ $this->processFile($files, $model);
}
if (is_array($files)) {
- foreach ($files as $entry) {
- if (!is_null($entry)) {
- $this->processFile($entry, $model);
- }
- }
- } else {
- if (!is_null($files)) {
- $this->processFile($files, $model);
- }
+ $this->processFiles($files, $model);
}
return true;
@@ -234,5 +229,39 @@ class AttachmentHelper implements AttachmentHelperInterface
return true;
}
+ /**
+ * @return array|null|UploadedFile
+ */
+ private function getFiles()
+ {
+ $files = null;
+ try {
+ if (Input::hasFile('attachments')) {
+ $files = Input::file('attachments');
+ }
+ } catch (TypeError $e) {
+ // Log it, do nothing else.
+ Log::error($e->getMessage());
+ }
+
+ return $files;
+ }
+
+ /**
+ * @param array $files
+ *
+ * @return bool
+ */
+ private function processFiles(array $files, Model $model): bool
+ {
+ foreach ($files as $entry) {
+ if (!is_null($entry)) {
+ $this->processFile($entry, $model);
+ }
+ }
+
+ return true;
+ }
+
}
diff --git a/app/Helpers/Attachments/AttachmentHelperInterface.php b/app/Helpers/Attachments/AttachmentHelperInterface.php
index 51e7c84628..c6de207c8b 100644
--- a/app/Helpers/Attachments/AttachmentHelperInterface.php
+++ b/app/Helpers/Attachments/AttachmentHelperInterface.php
@@ -1,4 +1,12 @@
balanceLines;
}
+ /**
+ * @param Collection $balanceLines
+ */
+ public function setBalanceLines(Collection $balanceLines)
+ {
+ $this->balanceLines = $balanceLines;
+ }
+
}
diff --git a/app/Helpers/Collection/BalanceEntry.php b/app/Helpers/Collection/BalanceEntry.php
index 3e798189d4..9e1e758022 100644
--- a/app/Helpers/Collection/BalanceEntry.php
+++ b/app/Helpers/Collection/BalanceEntry.php
@@ -1,4 +1,12 @@
startDate;
+ }
+
+ /**
+ * @param Carbon $startDate
+ */
+ public function setStartDate($startDate)
+ {
+ $this->startDate = $startDate;
+ }
+
+ /**
+ * @return Carbon
+ */
+ public function getEndDate()
+ {
+ return $this->endDate;
+ }
+
+ /**
+ * @param Carbon $endDate
+ */
+ public function setEndDate($endDate)
+ {
+ $this->endDate = $endDate;
+ }
+
+
/**
* If a BalanceLine has a budget/repetition, each BalanceEntry in this BalanceLine
* should have a "spent" value, which is the amount of money that has been spent
diff --git a/app/Helpers/Collection/Bill.php b/app/Helpers/Collection/Bill.php
index f535385013..da30164dbf 100644
--- a/app/Helpers/Collection/Bill.php
+++ b/app/Helpers/Collection/Bill.php
@@ -1,4 +1,12 @@
hit = $hit;
}
-
}
diff --git a/app/Helpers/Collection/Budget.php b/app/Helpers/Collection/Budget.php
index eeaec0e782..ce97961c66 100644
--- a/app/Helpers/Collection/Budget.php
+++ b/app/Helpers/Collection/Budget.php
@@ -1,4 +1,12 @@
account_id;
- $amount = strval(round($entry->journalAmount, 2));
- if (bccomp('0', $amount) === -1) {
- $amount = bcmul($amount, '-1');
+ foreach ($destinations as $transaction) {
+ $amount = strval($transaction->amount);
+ $account = $transaction->account;
+ if (bccomp('0', $amount) === -1) {
+ $amount = bcmul($amount, '-1');
+ }
+
+ $object = new stdClass;
+ $object->amount = $amount;
+ $object->name = $account->name;
+ $object->count = 1;
+ $object->id = $account->id;
+
+ // overrule some properties:
+ if ($this->expenses->has($account->id)) {
+ $object = $this->expenses->get($account->id);
+ $object->amount = bcadd($object->amount, $amount);
+ $object->count++;
+ }
+ $this->expenses->put($account->id, $object);
}
- if (!$this->expenses->has($accountId)) {
- $newObject = new stdClass;
- $newObject->amount = $amount;
- $newObject->name = Crypt::decrypt($entry->account_name);
- $newObject->count = 1;
- $newObject->id = $accountId;
- $this->expenses->put($accountId, $newObject);
- } else {
- $existing = $this->expenses->get($accountId);
- $existing->amount = bcadd($existing->amount, $amount);
- $existing->count++;
- $this->expenses->put($accountId, $existing);
- }
+
}
/**
@@ -60,8 +73,6 @@ class Expense
*/
public function addToTotal(string $add)
{
-
-
$add = strval(round($add, 2));
if (bccomp('0', $add) === -1) {
$add = bcmul($add, '-1');
diff --git a/app/Helpers/Collection/Income.php b/app/Helpers/Collection/Income.php
index c5cf254b3d..023b45dad2 100644
--- a/app/Helpers/Collection/Income.php
+++ b/app/Helpers/Collection/Income.php
@@ -1,8 +1,15 @@
account_id;
- if (!$this->incomes->has($accountId)) {
- $newObject = new stdClass;
- $newObject->amount = strval(round($entry->journalAmount, 2));
- $newObject->name = Crypt::decrypt($entry->account_name);
- $newObject->count = 1;
- $newObject->id = $accountId;
- $this->incomes->put($accountId, $newObject);
- } else {
- $existing = $this->incomes->get($accountId);
- $existing->amount = bcadd($existing->amount, $entry->journalAmount);
- $existing->count++;
- $this->incomes->put($accountId, $existing);
+ foreach ($sources as $transaction) {
+ $amount = strval($transaction->amount);
+ $account = $transaction->account;
+ $amount = bcmul($amount, '-1');
+
+ $object = new stdClass;
+ $object->amount = $amount;
+ $object->name = $account->name;
+ $object->count = 1;
+ $object->id = $account->id;
+
+ // overrule some properties:
+ if ($this->incomes->has($account->id)) {
+ $object = $this->incomes->get($account->id);
+ $object->amount = bcadd($object->amount, $amount);
+ $object->count++;
+ }
+ $this->incomes->put($account->id, $object);
}
+
}
/**
diff --git a/app/Helpers/Csv/Converter/AccountId.php b/app/Helpers/Csv/Converter/AccountId.php
index a25a599f1f..908cd132de 100644
--- a/app/Helpers/Csv/Converter/AccountId.php
+++ b/app/Helpers/Csv/Converter/AccountId.php
@@ -1,9 +1,16 @@
mapped[$this->index][$this->value])) {
- /** @var Account $account */
- $account = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- /** @var Account $account */
- $account = $repository->find($this->value);
- }
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $var = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value;
+ $account = $crud->find($var);
return $account;
}
diff --git a/app/Helpers/Csv/Converter/Amount.php b/app/Helpers/Csv/Converter/Amount.php
index 6aade561ab..8e2cfc4d0e 100644
--- a/app/Helpers/Csv/Converter/Amount.php
+++ b/app/Helpers/Csv/Converter/Amount.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
- $account = $repository->find(intval($this->mapped[$this->index][$this->value]));
- Log::debug('Found mapped account for value "' . $this->value . '". It is account #' . $account->id);
+ $account = $crud->find(intval($this->mapped[$this->index][$this->value]));
return $account;
}
+
if (strlen($this->value) > 0) {
- // find or create new account:
- $set = $repository->getAccounts(['Default account', 'Asset account']);
- /** @var Account $entry */
- foreach ($set as $entry) {
- if ($entry->iban == $this->value) {
- Log::debug('Found an account with the same IBAN ("' . $this->value . '"). It is account #' . $entry->id);
-
- return $entry;
- }
- }
-
- Log::debug('Found no account with the same IBAN ("' . $this->value . '"), so will create a new one.');
-
- // create it if doesn't exist.
- $accountData = [
- 'name' => $this->value,
- 'accountType' => 'asset',
- 'virtualBalance' => 0,
- 'virtualBalanceCurrency' => 1, // hard coded.
- 'active' => true,
- 'user' => Auth::user()->id,
- 'iban' => $this->value,
- 'accountNumber' => $this->value,
- 'accountRole' => null,
- 'openingBalance' => 0,
- 'openingBalanceDate' => new Carbon,
- 'openingBalanceCurrency' => 1, // hard coded.
- ];
-
- $account = $repository->store($accountData);
+ $account = $this->searchOrCreate($crud);
return $account;
}
return new Account;
}
+
+ /**
+ * @param AccountCrudInterface $crud
+ *
+ * @return Account
+ */
+ private function searchOrCreate(AccountCrudInterface $crud)
+ {
+ // find or create new account:
+ $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
+ /** @var Account $entry */
+ foreach ($set as $entry) {
+ if ($entry->iban == $this->value) {
+
+ return $entry;
+ }
+ }
+
+
+ // create it if doesn't exist.
+ $accountData = [
+ 'name' => $this->value,
+ 'accountType' => 'asset',
+ 'virtualBalance' => 0,
+ 'virtualBalanceCurrency' => 1, // hard coded.
+ 'active' => true,
+ 'user' => Auth::user()->id,
+ 'iban' => $this->value,
+ 'accountNumber' => $this->value,
+ 'accountRole' => null,
+ 'openingBalance' => 0,
+ 'openingBalanceDate' => new Carbon,
+ 'openingBalanceCurrency' => 1, // hard coded.
+ ];
+
+ $account = $crud->store($accountData);
+
+ return $account;
+ }
}
diff --git a/app/Helpers/Csv/Converter/AssetAccountName.php b/app/Helpers/Csv/Converter/AssetAccountName.php
index 69271e1b22..e56620415e 100644
--- a/app/Helpers/Csv/Converter/AssetAccountName.php
+++ b/app/Helpers/Csv/Converter/AssetAccountName.php
@@ -1,11 +1,19 @@
mapped[$this->index][$this->value])) {
- $account = $repository->find(intval($this->mapped[$this->index][$this->value]));
+ $account = $crud->find(intval($this->mapped[$this->index][$this->value]));
return $account;
}
-
- // find or create new account:
- $set = $repository->getAccounts(['Default account', 'Asset account']);
+ $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
/** @var Account $entry */
foreach ($set as $entry) {
if ($entry->name == $this->value) {
return $entry;
}
}
-
- // create it if doesnt exist.
$accountData = [
'name' => $this->value,
'accountType' => 'asset',
@@ -54,10 +56,9 @@ class AssetAccountName extends BasicConverter implements ConverterInterface
'openingBalance' => 0,
'openingBalanceDate' => new Carbon,
'openingBalanceCurrency' => 1, // hard coded.
-
];
- $account = $repository->store($accountData);
+ $account = $crud->store($accountData);
return $account;
}
diff --git a/app/Helpers/Csv/Converter/AssetAccountNumber.php b/app/Helpers/Csv/Converter/AssetAccountNumber.php
index ece314361c..5b8efed50a 100644
--- a/app/Helpers/Csv/Converter/AssetAccountNumber.php
+++ b/app/Helpers/Csv/Converter/AssetAccountNumber.php
@@ -1,5 +1,4 @@
mapped[$this->index][$this->value])) {
- $account = $repository->find(intval($this->mapped[$this->index][$this->value]));
+ $account = $crud->find(intval($this->mapped[$this->index][$this->value]));
return $account;
}
@@ -42,7 +40,7 @@ class AssetAccountNumber extends BasicConverter implements ConverterInterface
$value = $this->value ?? '';
if (strlen($value) > 0) {
// find or create new account:
- $set = $repository->getAccounts(['Default account', 'Asset account']);
+ $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
/** @var Account $entry */
foreach ($set as $entry) {
$accountNumber = $entry->getMeta('accountNumber');
@@ -68,7 +66,7 @@ class AssetAccountNumber extends BasicConverter implements ConverterInterface
];
- $account = $repository->store($accountData);
+ $account = $crud->store($accountData);
return $account;
}
diff --git a/app/Helpers/Csv/Converter/BasicConverter.php b/app/Helpers/Csv/Converter/BasicConverter.php
index 660202358a..63e5f8bb96 100644
--- a/app/Helpers/Csv/Converter/BasicConverter.php
+++ b/app/Helpers/Csv/Converter/BasicConverter.php
@@ -1,10 +1,20 @@
mapped[$this->index][$this->value])) {
- $bill = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $bill = $repository->find($this->value);
- }
+ $repository = app(BillRepositoryInterface::class);
+ $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value;
+ $bill = $repository->find($value);
return $bill;
}
diff --git a/app/Helpers/Csv/Converter/BillName.php b/app/Helpers/Csv/Converter/BillName.php
index fc462a952a..a1328ef6c1 100644
--- a/app/Helpers/Csv/Converter/BillName.php
+++ b/app/Helpers/Csv/Converter/BillName.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
- $bill = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $bills = $repository->getBills();
- /** @var Bill $bill */
- foreach ($bills as $bill) {
- if ($bill->name == $this->value) {
- return $bill;
- }
+ return $repository->find($this->mapped[$this->index][$this->value]);
+ }
+ $bills = $repository->getBills();
+
+ /** @var Bill $bill */
+ foreach ($bills as $bill) {
+ if ($bill->name == $this->value) {
+ return $bill;
}
}
- return $bill;
+ return new Bill;
}
}
diff --git a/app/Helpers/Csv/Converter/BudgetId.php b/app/Helpers/Csv/Converter/BudgetId.php
index f318464ff6..d9302d7734 100644
--- a/app/Helpers/Csv/Converter/BudgetId.php
+++ b/app/Helpers/Csv/Converter/BudgetId.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
- $budget = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $budget = $repository->find($this->value);
- }
+ $repository = app(BudgetRepositoryInterface::class);
+ $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value;
+ $budget = $repository->find($value);
return $budget;
}
diff --git a/app/Helpers/Csv/Converter/BudgetName.php b/app/Helpers/Csv/Converter/BudgetName.php
index 375442d08b..84f777775b 100644
--- a/app/Helpers/Csv/Converter/BudgetName.php
+++ b/app/Helpers/Csv/Converter/BudgetName.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
$budget = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $budget = $repository->store(['name' => $this->value, 'user' => Auth::user()->id]);
+
+ return $budget;
}
+ $budget = $repository->store(['name' => $this->value, 'user' => Auth::user()->id]);
+
return $budget;
}
diff --git a/app/Helpers/Csv/Converter/CategoryId.php b/app/Helpers/Csv/Converter/CategoryId.php
index f92854e463..8ffedd9ae1 100644
--- a/app/Helpers/Csv/Converter/CategoryId.php
+++ b/app/Helpers/Csv/Converter/CategoryId.php
@@ -1,9 +1,17 @@
mapped[$this->index][$this->value])) {
- $category = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $category = $repository->find($this->value);
- }
+ /** @var CategoryRepositoryInterface $repository */
+ $repository = app(CategoryRepositoryInterface::class);
+ $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value;
+ $category = $repository->find($value);
return $category;
}
diff --git a/app/Helpers/Csv/Converter/CategoryName.php b/app/Helpers/Csv/Converter/CategoryName.php
index dbfcf27e9a..d322f7b02f 100644
--- a/app/Helpers/Csv/Converter/CategoryName.php
+++ b/app/Helpers/Csv/Converter/CategoryName.php
@@ -1,10 +1,18 @@
mapped[$this->index][$this->value])) {
$category = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $data = [
- 'name' => $this->value,
- 'user' => Auth::user()->id,
- ];
-
- $category = $repository->store($data);
+ return $category;
}
+ $data = [
+ 'name' => $this->value,
+ 'user' => Auth::user()->id,
+ ];
+
+ $category = $repository->store($data);
+
return $category;
}
}
diff --git a/app/Helpers/Csv/Converter/ConverterInterface.php b/app/Helpers/Csv/Converter/ConverterInterface.php
index b1b6f2922b..c8fb526b83 100644
--- a/app/Helpers/Csv/Converter/ConverterInterface.php
+++ b/app/Helpers/Csv/Converter/ConverterInterface.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
- $currency = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $currency = $repository->findByCode($this->value);
+ $currency = $repository->find(intval($this->mapped[$this->index][$this->value]));
+
+ return $currency;
}
+ $currency = $repository->findByCode($this->value);
+
+
return $currency;
}
}
diff --git a/app/Helpers/Csv/Converter/CurrencyId.php b/app/Helpers/Csv/Converter/CurrencyId.php
index 4ee65e950b..a18082872a 100644
--- a/app/Helpers/Csv/Converter/CurrencyId.php
+++ b/app/Helpers/Csv/Converter/CurrencyId.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
- $currency = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $currency = $repository->find($this->value);
- }
+ $repository = app(CurrencyRepositoryInterface::class);
+ $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value;
+ $currency = $repository->find($value);
return $currency;
}
diff --git a/app/Helpers/Csv/Converter/CurrencyName.php b/app/Helpers/Csv/Converter/CurrencyName.php
index 29fa7828c3..62876fa9ba 100644
--- a/app/Helpers/Csv/Converter/CurrencyName.php
+++ b/app/Helpers/Csv/Converter/CurrencyName.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
-
$currency = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $currency = $repository->findByName($this->value);
+
+ return $currency;
}
+ $currency = $repository->findByName($this->value);
+
return $currency;
}
diff --git a/app/Helpers/Csv/Converter/CurrencySymbol.php b/app/Helpers/Csv/Converter/CurrencySymbol.php
index 4d0de8d70c..51037ece2e 100644
--- a/app/Helpers/Csv/Converter/CurrencySymbol.php
+++ b/app/Helpers/Csv/Converter/CurrencySymbol.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
$currency = $repository->find($this->mapped[$this->index][$this->value]);
- } else {
- $currency = $repository->findBySymbol($this->value);
+
+ return $currency;
}
+ $currency = $repository->findBySymbol($this->value);
+
+
return $currency;
}
}
diff --git a/app/Helpers/Csv/Converter/Date.php b/app/Helpers/Csv/Converter/Date.php
index e11338246f..b164e9df5f 100644
--- a/app/Helpers/Csv/Converter/Date.php
+++ b/app/Helpers/Csv/Converter/Date.php
@@ -1,4 +1,12 @@
mapped[$this->index][$this->value])) {
- $account = $repository->find($this->mapped[$this->index][$this->value]);
+ $account = $crud->find($this->mapped[$this->index][$this->value]);
return $account;
- } else {
- if (strlen($this->value) > 0) {
+ }
- $set = $repository->getAccounts([]);
- /** @var Account $account */
- foreach ($set as $account) {
- if ($account->iban == $this->value) {
+ return $this->findAccount($crud);
+ }
- return $account;
- }
+ /**
+ * @param AccountCrudInterface $crud
+ *
+ * @return Account|string
+ */
+ private function findAccount(AccountCrudInterface $crud)
+ {
+ if (strlen($this->value) > 0) {
+
+ $set = $crud->getAccountsByType([]);
+ /** @var Account $account */
+ foreach ($set as $account) {
+ if ($account->iban == $this->value) {
+
+ return $account;
}
}
-
- return $this->value;
}
+
+ return $this->value;
}
}
diff --git a/app/Helpers/Csv/Converter/OpposingAccountId.php b/app/Helpers/Csv/Converter/OpposingAccountId.php
index 6b6ec84f60..17836907bf 100644
--- a/app/Helpers/Csv/Converter/OpposingAccountId.php
+++ b/app/Helpers/Csv/Converter/OpposingAccountId.php
@@ -1,9 +1,16 @@
mapped[$this->index][$this->value])) {
- $account = $repository->find($this->mapped[$this->index][$this->value]);
-
- } else {
- $account = $repository->find($this->value);
- }
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value;
+ $account = $crud->find($value);
return $account;
-
}
}
diff --git a/app/Helpers/Csv/Converter/OpposingAccountName.php b/app/Helpers/Csv/Converter/OpposingAccountName.php
index b55c7e1bf9..7c3d392985 100644
--- a/app/Helpers/Csv/Converter/OpposingAccountName.php
+++ b/app/Helpers/Csv/Converter/OpposingAccountName.php
@@ -1,9 +1,16 @@
mapped[$this->index][$this->value])) {
- $account = $repository->find($this->mapped[$this->index][$this->value]);
+ $account = $crud->find($this->mapped[$this->index][$this->value]);
return $account;
- } else {
- return $this->value;
}
+
+ return $this->value;
+
}
}
diff --git a/app/Helpers/Csv/Converter/RabobankDebetCredit.php b/app/Helpers/Csv/Converter/RabobankDebetCredit.php
index ac73d9e206..854f812d45 100644
--- a/app/Helpers/Csv/Converter/RabobankDebetCredit.php
+++ b/app/Helpers/Csv/Converter/RabobankDebetCredit.php
@@ -1,4 +1,12 @@
value);
@@ -33,8 +41,10 @@ class TagsComma extends BasicConverter implements ConverterInterface
'zoomLevel' => null,
'tagMode' => 'nothing',
];
- $tag = $repository->store($data); // should validate first?
- $tags->push($tag);
+ if (strlen($string) > 0) {
+ $tag = $repository->store($data); // should validate first?
+ $tags->push($tag);
+ }
}
$tags = $tags->merge($this->data['tags']);
diff --git a/app/Helpers/Csv/Converter/TagsSpace.php b/app/Helpers/Csv/Converter/TagsSpace.php
index 60eeba8b7d..aa2694a330 100644
--- a/app/Helpers/Csv/Converter/TagsSpace.php
+++ b/app/Helpers/Csv/Converter/TagsSpace.php
@@ -1,4 +1,12 @@
null,
'tagMode' => 'nothing',
];
- $tag = $repository->store($data); // should validate first?
- $tags->push($tag);
+ if (strlen($string) > 0) {
+ $tag = $repository->store($data); // should validate first?
+ $tags->push($tag);
+ }
}
$tags = $tags->merge($this->data['tags']);
diff --git a/app/Helpers/Csv/Data.php b/app/Helpers/Csv/Data.php
index 606ae42bbe..d56f790204 100644
--- a/app/Helpers/Csv/Data.php
+++ b/app/Helpers/Csv/Data.php
@@ -1,4 +1,12 @@
data->getReader() as $index => $row) {
if ($this->parseRow($index)) {
- Log::debug('--- Importing row ' . $index);
$this->rows++;
$result = $this->importRow($row);
if (!($result instanceof TransactionJournal)) {
@@ -120,7 +126,6 @@ class Importer
$this->journals->push($result);
event(new TransactionJournalStored($result, 0));
}
- Log::debug('---');
}
}
}
@@ -226,10 +231,9 @@ class Importer
$data = $this->getFiller(); // These fields are necessary to create a new transaction journal. Some are optional
foreach ($row as $index => $value) {
$role = $this->roles[$index] ?? '_ignore';
- $class = Config::get('csv.roles.' . $role . '.converter');
- $field = Config::get('csv.roles.' . $role . '.field');
+ $class = config('csv.roles.' . $role . '.converter');
+ $field = config('csv.roles.' . $role . '.field');
- Log::debug('Column #' . $index . ' (role: ' . $role . ') : converter ' . $class . ' stores its data into field ' . $field . ':');
// here would be the place where preprocessors would fire.
@@ -283,19 +287,17 @@ class Importer
if ($specifix->getProcessorType() == SpecifixInterface::POST_PROCESSOR) {
$specifix->setData($this->importData);
$specifix->setRow($this->importRow);
- Log::debug('Now post-process specifix named ' . $className . ':');
$this->importData = $specifix->fix();
}
}
- $set = Config::get('csv.post_processors');
+ $set = config('csv.post_processors');
foreach ($set as $className) {
/** @var PostProcessorInterface $postProcessor */
$postProcessor = app('FireflyIII\Helpers\Csv\PostProcessing\\' . $className);
$array = $this->importData ?? [];
$postProcessor->setData($array);
- Log::debug('Now post-process processor named ' . $className . ':');
$this->importData = $postProcessor->process();
}
@@ -363,7 +365,7 @@ class Importer
private function getFiller()
{
$filler = [];
- foreach (Config::get('csv.roles') as $role) {
+ foreach (config('csv.roles') as $role) {
if (isset($role['field'])) {
$fieldName = $role['field'];
$filler[$fieldName] = null;
diff --git a/app/Helpers/Csv/Mapper/AnyAccount.php b/app/Helpers/Csv/Mapper/AnyAccount.php
index f4486d8e8b..05c5bab9ea 100644
--- a/app/Helpers/Csv/Mapper/AnyAccount.php
+++ b/app/Helpers/Csv/Mapper/AnyAccount.php
@@ -1,4 +1,12 @@
accounts()->where('account_type_id', $accountType->id)->get();
foreach ($accounts as $entry) {
if ($entry->name == $this->data['asset-account-name']) {
- Log::debug('Found an asset account with this name (#' . $entry->id . ': ******)');
return $entry;
}
@@ -231,6 +238,9 @@ class AssetAccount implements PostProcessorInterface
*/
private function parseAccountNumberString()
{
+ /** @var AccountCrudInterface $crud */
+ $crud = app(AccountCrudInterface::class);
+
$accountNumber = $this->data['asset-account-number'] ?? '';
$accountType = $this->getAccountType();
$accounts = Auth::user()->accounts()->with(['accountmeta'])->where('account_type_id', $accountType->id)->get();
@@ -238,14 +248,11 @@ class AssetAccount implements PostProcessorInterface
foreach ($accounts as $entry) {
$metaFieldValue = $entry->getMeta('accountNumber');
if ($metaFieldValue === $accountNumber && $metaFieldValue !== '') {
- Log::debug('Found an asset account with this account number (#' . $entry->id . ')');
return $entry;
}
}
// create new if not exists and return that one:
- /** @var \FireflyIII\Repositories\Account\AccountRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
$accountData = [
'name' => $accountNumber,
'accountType' => 'asset',
@@ -260,7 +267,7 @@ class AssetAccount implements PostProcessorInterface
'openingBalanceDate' => new Carbon,
'openingBalanceCurrency' => 1, // hard coded.
];
- $account = $repository->store($accountData);
+ $account = $crud->store($accountData);
return $account;
}
diff --git a/app/Helpers/Csv/PostProcessing/Bill.php b/app/Helpers/Csv/PostProcessing/Bill.php
index 0404c719a7..60392b2446 100644
--- a/app/Helpers/Csv/PostProcessing/Bill.php
+++ b/app/Helpers/Csv/PostProcessing/Bill.php
@@ -1,4 +1,12 @@
data = $data;
}
+ /**
+ * @return array|null
+ */
+ protected function checkIbanString()
+ {
+ $rules = ['iban' => 'iban'];
+ $iban = $this->data['opposing-account-iban'];
+ $check = ['iban' => $iban];
+ $validator = Validator::make($check, $rules);
+ if (is_string($iban) && strlen($iban) > 0 && !$validator->fails()) {
+
+ $this->data['opposing-account-object'] = $this->parseIbanString();
+
+ return $this->data;
+ }
+
+ return null;
+ }
+
/**
* @return array
*/
protected function checkIdNameObject()
{
if ($this->data['opposing-account-id'] instanceof Account) { // first priority. try to find the account based on ID, if any
- Log::debug('OpposingAccountPostProcession: opposing-account-id is an Account.');
$this->data['opposing-account-object'] = $this->data['opposing-account-id'];
return $this->data;
}
if ($this->data['opposing-account-iban'] instanceof Account) { // second: try to find the account based on IBAN, if any.
- Log::debug('OpposingAccountPostProcession: opposing-account-iban is an Account.');
$this->data['opposing-account-object'] = $this->data['opposing-account-iban'];
return $this->data;
@@ -78,16 +102,16 @@ class OpposingAccount implements PostProcessorInterface
/**
* @return array|null
*/
- protected function checkIbanString()
+ protected function checkNameString()
{
- $rules = ['iban' => 'iban'];
- $iban = $this->data['opposing-account-iban'];
- $check = ['iban' => $iban];
- $validator = Validator::make($check, $rules);
- if (is_string($iban) && strlen($iban) > 0 && !$validator->fails()) {
+ if ($this->data['opposing-account-name'] instanceof Account) { // third: try to find account based on name, if any.
+ $this->data['opposing-account-object'] = $this->data['opposing-account-name'];
- Log::debug('OpposingAccountPostProcession: opposing-account-iban is a string (******).');
- $this->data['opposing-account-object'] = $this->parseIbanString();
+ return $this->data;
+ }
+ if (is_string($this->data['opposing-account-name'])) {
+
+ $this->data['opposing-account-object'] = $this->parseNameString();
return $this->data;
}
@@ -95,26 +119,6 @@ class OpposingAccount implements PostProcessorInterface
return null;
}
- /**
- * @return Account|null
- */
- protected function parseIbanString()
- {
- // create by name and/or iban.
- $accounts = Auth::user()->accounts()->get();
- foreach ($accounts as $entry) {
- if ($entry->iban == $this->data['opposing-account-iban']) {
- Log::debug('OpposingAccountPostProcession: opposing-account-iban matches an Account.');
-
- return $entry;
- }
- }
- $account = $this->createAccount();
-
-
- return $account;
- }
-
/**
* @return Account|null
*/
@@ -134,7 +138,6 @@ class OpposingAccount implements PostProcessorInterface
'active' => true,
]
);
- Log::debug('OpposingAccountPostProcession: created a new account.');
return $account;
}
@@ -150,34 +153,32 @@ class OpposingAccount implements PostProcessorInterface
// create expense account:
return AccountType::where('type', 'Expense account')->first();
- } else {
- // create revenue account:
-
- return AccountType::where('type', 'Revenue account')->first();
-
-
}
+
+ // create revenue account:
+
+ return AccountType::where('type', 'Revenue account')->first();
+
+
}
/**
- * @return array|null
+ * @return Account|null
*/
- protected function checkNameString()
+ protected function parseIbanString()
{
- if ($this->data['opposing-account-name'] instanceof Account) { // third: try to find account based on name, if any.
- Log::debug('OpposingAccountPostProcession: opposing-account-name is an Account.');
- $this->data['opposing-account-object'] = $this->data['opposing-account-name'];
+ // create by name and/or iban.
+ $accounts = Auth::user()->accounts()->get();
+ foreach ($accounts as $entry) {
+ if ($entry->iban == $this->data['opposing-account-iban']) {
- return $this->data;
+ return $entry;
+ }
}
- if (is_string($this->data['opposing-account-name'])) {
+ $account = $this->createAccount();
- $this->data['opposing-account-object'] = $this->parseNameString();
- return $this->data;
- }
-
- return null;
+ return $account;
}
/**
@@ -189,7 +190,6 @@ class OpposingAccount implements PostProcessorInterface
$accounts = Auth::user()->accounts()->where('account_type_id', $accountType->id)->get();
foreach ($accounts as $entry) {
if ($entry->name == $this->data['opposing-account-name']) {
- Log::debug('Found an account with this name (#' . $entry->id . ': ******)');
return $entry;
}
diff --git a/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php b/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php
index bc67b7d9cf..58f6ff09d0 100644
--- a/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php
+++ b/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php
@@ -1,4 +1,12 @@
data['description'], $matches)) {
- Log::debug('AbnAmroSpecifix: Description is structured as costs from ABN AMRO itself.');
$this->data['opposing-account-name'] = 'ABN AMRO';
$this->data['description'] = $matches[1];
@@ -91,15 +96,13 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface
{
// See if the current description is formatted in GEA/BEA format
if (preg_match('/([BG]EA) +(NR:[a-zA-Z:0-9]+) +([0-9.\/]+) +([^,]*)/', $this->data['description'], $matches)) {
- Log::debug('AbnAmroSpecifix: Description is structured as GEA or BEA format.');
// description and opposing account will be the same.
$this->data['opposing-account-name'] = $matches[4];
+ $this->data['description'] = $matches[4];
if ($matches[1] == 'GEA') {
$this->data['description'] = 'GEA ' . $matches[4];
- } else {
- $this->data['description'] = $matches[4];
}
return true;
@@ -117,7 +120,6 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface
{
// See if the current description is formatted as a SEPA plain description
if (preg_match('/^SEPA(.{28})/', $this->data['description'], $matches)) {
- Log::debug('AbnAmroSpecifix: Description is structured as SEPA plain description.');
$type = $matches[1];
$reference = '';
@@ -131,13 +133,13 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface
foreach ($matches as $match) {
$key = $match[1];
$value = trim($match[2]);
- Log::debug('SEPA: ' . $key . ' - ' . $value);
switch (strtoupper($key)) {
case 'OMSCHRIJVING':
$newDescription = $value;
break;
case 'NAAM':
- $this->data['opposing-account-name'] = $name = $value;
+ $this->data['opposing-account-name'] = $value;
+ $name = $value;
break;
case 'KENMERK':
$reference = $value;
@@ -153,9 +155,8 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface
// Set a new description for the current transaction. If none was given
// set the description to type, name and reference
- if ($newDescription) {
- $this->data['description'] = $newDescription;
- } else {
+ $this->data['description'] = $newDescription;
+ if (strlen($newDescription) === 0) {
$this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference);
}
@@ -174,7 +175,6 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface
{
// See if the current description is formatted in TRTP format
if (preg_match_all('!\/([A-Z]{3,4})\/([^/]*)!', $this->data['description'], $matches, PREG_SET_ORDER)) {
- Log::debug('AbnAmroSpecifix: Description is structured as TRTP format.');
$type = '';
$name = '';
@@ -211,9 +211,8 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface
// Set a new description for the current transaction. If none was given
// set the description to type, name and reference
- if ($newDescription) {
- $this->data['description'] = $newDescription;
- } else {
+ $this->data['description'] = $newDescription;
+ if (strlen($newDescription) === 0) {
$this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference);
}
}
diff --git a/app/Helpers/Csv/Specifix/Dummy.php b/app/Helpers/Csv/Specifix/Dummy.php
index 5d84545619..447bafadd9 100644
--- a/app/Helpers/Csv/Specifix/Dummy.php
+++ b/app/Helpers/Csv/Specifix/Dummy.php
@@ -1,4 +1,12 @@
data['opposing-account-name']) && strlen($this->data['opposing-account-name']) == 0) {
- Log::debug('RaboSpecifix: opp-name is zero length, changed to: "******"');
$this->data['opposing-account-name'] = $this->row[10];
- Log::debug('Description was: "******".');
$this->data['description'] = trim(str_replace($this->row[10], '', $this->data['description']));
- Log::debug('Description is now: "******".');
}
}
diff --git a/app/Helpers/Csv/Specifix/Specifix.php b/app/Helpers/Csv/Specifix/Specifix.php
index fcceeb3652..8626d38dcb 100644
--- a/app/Helpers/Csv/Specifix/Specifix.php
+++ b/app/Helpers/Csv/Specifix/Specifix.php
@@ -1,5 +1,4 @@
$columnRole) {
- $mapper = Config::get('csv.roles.' . $columnRole . '.mapper');
+ $mapper = config('csv.roles.' . $columnRole . '.mapper');
if (is_null($mapper)) {
throw new FireflyException('Cannot map field of type "' . $columnRole . '".');
}
diff --git a/app/Helpers/Csv/WizardInterface.php b/app/Helpers/Csv/WizardInterface.php
index 815a800c27..cedcfbe5f6 100644
--- a/app/Helpers/Csv/WizardInterface.php
+++ b/app/Helpers/Csv/WizardInterface.php
@@ -1,4 +1,12 @@
data) {
- $this->useCustomFiscalYear = true;
- } else {
- $this->useCustomFiscalYear = false;
- }
+ $this->useCustomFiscalYear = Preferences::get('customFiscalYear', false)->data;
}
/**
@@ -44,9 +48,10 @@ class FiscalHelper implements FiscalHelperInterface
// add 1 year and sub 1 day
$endDate->addYear();
$endDate->subDay();
- } else {
- $endDate->endOfYear();
+
+ return $endDate;
}
+ $endDate->endOfYear();
return $endDate;
@@ -70,9 +75,10 @@ class FiscalHelper implements FiscalHelperInterface
if ($startDate > $date) {
$startDate->subYear();
}
- } else {
- $startDate->startOfYear();
+
+ return $startDate;
}
+ $startDate->startOfYear();
return $startDate;
}
diff --git a/app/Helpers/FiscalHelperInterface.php b/app/Helpers/FiscalHelperInterface.php
index 1d31ad0e18..c8c9d4e1b3 100644
--- a/app/Helpers/FiscalHelperInterface.php
+++ b/app/Helpers/FiscalHelperInterface.php
@@ -1,4 +1,12 @@
$title,
];
- Log::debug('Going to get from Github: ' . $uri);
$result = Requests::get($uri);
- Log::debug('Status code was ' . $result->status_code . '.');
if ($result->status_code === 200) {
$content['text'] = $result->body;
@@ -56,7 +61,6 @@ class Help implements HelpInterface
if (strlen(trim($content['text'])) == 0) {
- Log::debug('No actual help text for this route (even though a page was found).');
$content['text'] = '' . strval(trans('firefly.route_has_no_help')) . '
';
}
$converter = new CommonMarkConverter();
diff --git a/app/Helpers/Help/HelpInterface.php b/app/Helpers/Help/HelpInterface.php
index 28c30e11ea..a7cb671802 100644
--- a/app/Helpers/Help/HelpInterface.php
+++ b/app/Helpers/Help/HelpInterface.php
@@ -1,4 +1,12 @@
startBalance = '0';
+ $account->endBalance = '0';
+ $currentStart = $startSet->filter(
+ function (Account $entry) use ($account) {
+ return $account->id == $entry->id;
+ }
+ );
+
+ $currentBackup = $backupSet->filter( // grab entry from current backup as well:
+ function (Account $entry) use ($account) {
+ return $account->id == $entry->id;
+ }
+ );
+
+ // first try to set from backup
+ if (!is_null($currentBackup->first())) {
+ $account->startBalance = $currentBackup->first()->balance;
+ }
+
+ // overrule with data from start
+ if (!is_null($currentStart->first())) {
+ $account->startBalance = $currentStart->first()->balance;
+ }
+
+ $currentEnd = $endSet->filter(
+ function (Account $entry) use ($account) {
+ return $account->id == $entry->id;
+ }
+ );
+
+ if (!is_null($currentEnd->first())) {
+ $account->endBalance = $currentEnd->first()->balance;
+ }
+
+ return $account;
+ }
+
/**
* This method generates a full report for the given period on all
- * given accounts
+ * given accounts.
+ *
+ * a special consideration for accounts that did exist on this exact day.
+ * we also grab the balance from today just in case, to see if that changes things.
+ * it's a fall back for users who (rightly so) start keeping score at the first of
+ * the month and find the first report lacking / broken.
*
* @param Carbon $start
* @param Carbon $end
@@ -40,88 +94,18 @@ class AccountReportHelper implements AccountReportHelperInterface
$endAmount = '0';
$diff = '0';
$ids = $accounts->pluck('id')->toArray();
-
- $yesterday = clone $start;
+ $yesterday = clone $start;
$yesterday->subDay();
-
-
- // get balances for start.
- $startSet = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->whereIn('accounts.id', $ids)
- ->whereNull('transaction_journals.deleted_at')
- ->whereNull('transactions.deleted_at')
- ->where('transaction_journals.date', '<=', $yesterday->format('Y-m-d'))
- ->groupBy('accounts.id')
- ->get(['accounts.id', DB::raw('SUM(`transactions`.`amount`) as `balance`')]);
-
- // a special consideration for accounts that did exist on this exact day.
- // we also grab the balance from today just in case, to see if that changes things.
- // it's a fall back for users who (rightly so) start keeping score at the first of
- // the month and find the first report lacking / broken.
- $backupSet = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->whereIn('accounts.id', $ids)
- ->whereNull('transaction_journals.deleted_at')
- ->whereNull('transactions.deleted_at')
- ->where('transaction_journals.date', '<=', $start->format('Y-m-d'))
- ->groupBy('accounts.id')
- ->get(['accounts.id', DB::raw('SUM(`transactions`.`amount`) as `balance`')]);
-
- // and end:
- $endSet = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->whereIn('accounts.id', $ids)
- ->whereNull('transaction_journals.deleted_at')
- ->whereNull('transactions.deleted_at')
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->groupBy('accounts.id')
- ->get(['accounts.id', DB::raw('SUM(`transactions`.`amount`) as `balance`')]);
-
+ $startSet = $this->getSet($ids, $yesterday); // get balances for start.
+ $backupSet = $this->getSet($ids, $start);
+ $endSet = $this->getSet($ids, $end);
$accounts->each(
function (Account $account) use ($startSet, $endSet, $backupSet) {
- /**
- * The balance for today always incorporates transactions
- * made on today. So to get todays "start" balance, we sub one
- * day.
- */
- //
- $account->startBalance = '0';
- $account->endBalance = '0';
- $currentStart = $startSet->filter(
- function (Account $entry) use ($account) {
- return $account->id == $entry->id;
- }
- );
- // grab entry from current backup as well:
- $currentBackup = $backupSet->filter(
- function (Account $entry) use ($account) {
- return $account->id == $entry->id;
- }
- );
-
-
- if ($currentStart->first()) {
- $account->startBalance = $currentStart->first()->balance;
- } else {
- if (is_null($currentStart->first()) && !is_null($currentBackup->first())) {
- $account->startBalance = $currentBackup->first()->balance;
- }
- }
-
- $currentEnd = $endSet->filter(
- function (Account $entry) use ($account) {
- return $account->id == $entry->id;
- }
- );
- if ($currentEnd->first()) {
- $account->endBalance = $currentEnd->first()->balance;
- }
+ return self::reportFilter($account, $startSet, $endSet, $backupSet);
}
);
-
// summarize:
foreach ($accounts as $account) {
$startAmount = bcadd($startAmount, $account->startBalance);
@@ -137,4 +121,22 @@ class AccountReportHelper implements AccountReportHelperInterface
return $object;
}
+
+ /**
+ * @param array $ids
+ * @param Carbon $date
+ *
+ * @return Collection
+ */
+ private function getSet(array $ids, Carbon $date): Collection
+ {
+ return Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->whereIn('accounts.id', $ids)
+ ->whereNull('transaction_journals.deleted_at')
+ ->whereNull('transactions.deleted_at')
+ ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
+ ->groupBy('accounts.id')
+ ->get(['accounts.id', DB::raw('SUM(`transactions`.`amount`) as `balance`')]);
+ }
}
diff --git a/app/Helpers/Report/AccountReportHelperInterface.php b/app/Helpers/Report/AccountReportHelperInterface.php
index 673168b18a..8243656853 100644
--- a/app/Helpers/Report/AccountReportHelperInterface.php
+++ b/app/Helpers/Report/AccountReportHelperInterface.php
@@ -1,5 +1,4 @@
budgetRepository = $budgetRepository;
- $this->tagRepository = $tagRepository;
}
@@ -61,56 +59,105 @@ class BalanceReportHelper implements BalanceReportHelperInterface
public function getBalanceReport(Carbon $start, Carbon $end, Collection $accounts): Balance
{
$balance = new Balance;
-
// build a balance header:
- $header = new BalanceHeader;
- $budgets = $this->budgetRepository->getBudgetsAndLimitsInRange($start, $end);
- $spentData = $this->budgetRepository->spentPerBudgetPerAccount($budgets, $accounts, $start, $end);
+ $header = new BalanceHeader;
+ $limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end);
foreach ($accounts as $account) {
$header->addAccount($account);
}
- /** @var BudgetModel $budget */
- foreach ($budgets as $budget) {
- $balance->addBalanceLine($this->createBalanceLine($budget, $accounts, $spentData));
+ /** @var LimitRepetition $repetition */
+ foreach ($limitRepetitions as $repetition) {
+ $budget = $this->budgetRepository->find($repetition->budget_id);
+ $line = $this->createBalanceLine($budget, $repetition, $accounts);
+ $balance->addBalanceLine($line);
}
+ $noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
+ $coveredByTagLine = $this->createTagsBalanceLine($accounts, $start, $end);
+ $leftUnbalancedLine = $this->createLeftUnbalancedLine($noBudgetLine, $coveredByTagLine);
- $balance->addBalanceLine($this->createEmptyBalanceLine($accounts, $spentData));
- $balance->addBalanceLine($this->createTagsBalanceLine($accounts, $start, $end));
- $balance->addBalanceLine($this->createDifferenceBalanceLine($accounts, $spentData, $start, $end));
+ $balance->addBalanceLine($noBudgetLine);
+ $balance->addBalanceLine($coveredByTagLine);
+ $balance->addBalanceLine($leftUnbalancedLine);
$balance->setBalanceHeader($header);
+ // remove budgets without expenses from balance lines:
+ $balance = $this->removeUnusedBudgets($balance);
+
return $balance;
}
+ /**
+ * This method collects all transfers that are part of a "balancing act" tag
+ * and groups the amounts of those transfers by their destination account.
+ *
+ * This is used to indicate which expenses, usually outside of budgets, have been
+ * corrected by transfers from a savings account.
+ *
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ private function allCoveredByBalancingActs(Collection $accounts, Carbon $start, Carbon $end): Collection
+ {
+ $ids = $accounts->pluck('id')->toArray();
+ $set = Auth::user()->tags()
+ ->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id')
+ ->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
+ ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
+ ->leftJoin(
+ 'transactions AS t_source', function (JoinClause $join) {
+ $join->on('transaction_journals.id', '=', 't_source.transaction_journal_id')->where('t_source.amount', '<', 0);
+ }
+ )
+ ->leftJoin(
+ 'transactions AS t_destination', function (JoinClause $join) {
+ $join->on('transaction_journals.id', '=', 't_destination.transaction_journal_id')->where('t_destination.amount', '>', 0);
+ }
+ )
+ ->where('tags.tagMode', 'balancingAct')
+ ->where('transaction_types.type', TransactionType::TRANSFER)
+ ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
+ ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
+ ->whereNull('transaction_journals.deleted_at')
+ ->whereIn('t_source.account_id', $ids)
+ ->whereIn('t_destination.account_id', $ids)
+ ->groupBy('t_destination.account_id')
+ ->get(
+ [
+ 't_destination.account_id',
+ DB::raw('SUM(`t_destination`.`amount`) as `sum`'),
+ ]
+ );
+
+ return $set;
+ }
+
/**
- * @param Budget $budget
- * @param Collection $accounts
- * @param Collection $spentData
+ * @param Budget $budget
+ * @param LimitRepetition $repetition
+ * @param Collection $accounts
*
* @return BalanceLine
*/
- private function createBalanceLine(BudgetModel $budget, Collection $accounts, Collection $spentData): BalanceLine
+ private function createBalanceLine(Budget $budget, LimitRepetition $repetition, Collection $accounts): BalanceLine
{
- $line = new BalanceLine;
+ $line = new BalanceLine;
+ $budget->amount = $repetition->amount;
$line->setBudget($budget);
+ $line->setStartDate($repetition->startdate);
+ $line->setEndDate($repetition->enddate);
// loop accounts:
foreach ($accounts as $account) {
$balanceEntry = new BalanceEntry;
$balanceEntry->setAccount($account);
-
- // get spent:
- $entry = $spentData->filter(
- function (TransactionJournal $model) use ($budget, $account) {
- return $model->account_id == $account->id && $model->budget_id == $budget->id;
- }
+ $spent = $this->budgetRepository->spentInPeriod(
+ new Collection([$budget]), new Collection([$account]), $repetition->startdate, $repetition->enddate
);
- $spent = '0';
- if (!is_null($entry->first())) {
- $spent = $entry->first()->spent;
- }
$balanceEntry->setSpent($spent);
$line->addBalanceEntry($balanceEntry);
}
@@ -119,75 +166,53 @@ class BalanceReportHelper implements BalanceReportHelperInterface
}
/**
- * @param Collection $accounts
- * @param Collection $spentData
- * @param Carbon $start
- * @param Carbon $end
- *
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @param BalanceLine $noBudgetLine
+ * @param BalanceLine $coveredByTagLine
*
* @return BalanceLine
*/
- private function createDifferenceBalanceLine(Collection $accounts, Collection $spentData, Carbon $start, Carbon $end): BalanceLine
+ private function createLeftUnbalancedLine(BalanceLine $noBudgetLine, BalanceLine $coveredByTagLine): BalanceLine
{
- $diff = new BalanceLine;
- $tagsLeft = $this->tagRepository->allCoveredByBalancingActs($accounts, $start, $end);
+ $line = new BalanceLine;
+ $line->setRole(BalanceLine::ROLE_DIFFROLE);
+ $noBudgetEntries = $noBudgetLine->getBalanceEntries();
+ $tagEntries = $coveredByTagLine->getBalanceEntries();
- $diff->setRole(BalanceLine::ROLE_DIFFROLE);
-
- foreach ($accounts as $account) {
- $entry = $spentData->filter(
- function (TransactionJournal $model) use ($account) {
- return $model->account_id == $account->id && is_null($model->budget_id);
+ foreach ($noBudgetEntries as $entry) {
+ $account = $entry->getAccount();
+ $tagEntry = $tagEntries->filter(
+ function (BalanceEntry $current) use ($account) {
+ return $current->getAccount()->id === $account->id;
}
);
- $spent = '0';
- if (!is_null($entry->first())) {
- $spent = $entry->first()->spent;
+ if ($tagEntry->first()) {
+ // found corresponding entry. As we should:
+ $newEntry = new BalanceEntry;
+ $newEntry->setAccount($account);
+ $spent = bcadd($tagEntry->first()->getLeft(), $entry->getSpent());
+ $newEntry->setSpent($spent);
+ $line->addBalanceEntry($newEntry);
}
- $leftEntry = $tagsLeft->filter(
- function (Tag $tag) use ($account) {
- return $tag->account_id == $account->id;
- }
- );
- $left = '0';
- if (!is_null($leftEntry->first())) {
- $left = $leftEntry->first()->sum;
- }
- $diffValue = bcadd($spent, $left);
-
- // difference:
- $diffEntry = new BalanceEntry;
- $diffEntry->setAccount($account);
- $diffEntry->setSpent($diffValue);
- $diff->addBalanceEntry($diffEntry);
-
}
- return $diff;
+ return $line;
+
+
}
/**
* @param Collection $accounts
- * @param Collection $spentData
+ * @param Carbon $start
+ * @param Carbon $end
*
* @return BalanceLine
*/
- private function createEmptyBalanceLine(Collection $accounts, Collection $spentData): BalanceLine
+ private function createNoBudgetLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
{
$empty = new BalanceLine;
foreach ($accounts as $account) {
- $entry = $spentData->filter(
- function (TransactionJournal $model) use ($account) {
- return $model->account_id == $account->id && is_null($model->budget_id);
- }
- );
- $spent = '0';
- if (!is_null($entry->first())) {
- $spent = $entry->first()->spent;
- }
-
+ $spent = $this->budgetRepository->spentInPeriodWithoutBudget(new Collection([$account]), $start, $end);
// budget
$budgetEntry = new BalanceEntry;
$budgetEntry->setAccount($account);
@@ -209,7 +234,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface
private function createTagsBalanceLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
{
$tags = new BalanceLine;
- $tagsLeft = $this->tagRepository->allCoveredByBalancingActs($accounts, $start, $end);
+ $tagsLeft = $this->allCoveredByBalancingActs($accounts, $start, $end);
$tags->setRole(BalanceLine::ROLE_TAGROLE);
@@ -235,4 +260,33 @@ class BalanceReportHelper implements BalanceReportHelperInterface
return $tags;
}
+ /**
+ * @param Balance $balance
+ *
+ * @return Balance
+ */
+ private function removeUnusedBudgets(Balance $balance): Balance
+ {
+ $set = $balance->getBalanceLines();
+ $newSet = new Collection;
+ foreach ($set as $entry) {
+ if (!is_null($entry->getBudget()->id)) {
+ $sum = '0';
+ foreach ($entry->getBalanceEntries() as $balanceEntry) {
+ $sum = bcadd($sum, $balanceEntry->getSpent());
+ }
+ if (bccomp($sum, '0') === -1) {
+ $newSet->push($entry);
+ }
+ continue;
+ }
+ $newSet->push($entry);
+ }
+
+ $balance->setBalanceLines($newSet);
+
+ return $balance;
+
+ }
+
}
diff --git a/app/Helpers/Report/BalanceReportHelperInterface.php b/app/Helpers/Report/BalanceReportHelperInterface.php
index 9cdcdb19b7..45c6822245 100644
--- a/app/Helpers/Report/BalanceReportHelperInterface.php
+++ b/app/Helpers/Report/BalanceReportHelperInterface.php
@@ -1,5 +1,4 @@
repository = $repository;
+ }
/**
* @param Carbon $start
@@ -35,25 +50,17 @@ class BudgetReportHelper implements BudgetReportHelperInterface
public function getBudgetReport(Carbon $start, Carbon $end, Collection $accounts): BudgetCollection
{
$object = new BudgetCollection;
- /** @var \FireflyIII\Repositories\Budget\BudgetRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
- $set = $repository->getBudgets();
- $allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
- $allTotalSpent = $repository->spentAllPerDayForAccounts($accounts, $start, $end);
+ $set = $this->repository->getBudgets();
+ /** @var Budget $budget */
foreach ($set as $budget) {
-
- $repetitions = $allRepetitions->filter(
- function (LimitRepetition $rep) use ($budget) {
- return $rep->budget_id == $budget->id;
- }
- );
- $totalSpent = $allTotalSpent[$budget->id] ?? [];
+ $repetitions = $budget->limitrepetitions()->before($end)->after($start)->get();
// no repetition(s) for this budget:
if ($repetitions->count() == 0) {
+ // spent for budget in time range:
+ $spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
- $spent = array_sum($totalSpent);
if ($spent > 0) {
$budgetLine = new BudgetLine;
$budgetLine->setBudget($budget);
@@ -63,32 +70,23 @@ class BudgetReportHelper implements BudgetReportHelperInterface
}
continue;
}
-
// one or more repetitions for budget:
/** @var LimitRepetition $repetition */
foreach ($repetitions as $repetition) {
+ $data = $this->calculateExpenses($budget, $repetition, $accounts);
+
$budgetLine = new BudgetLine;
$budgetLine->setBudget($budget);
$budgetLine->setRepetition($repetition);
- $expenses = $this->getSumOfRange($start, $end, $totalSpent);
-
- // 200 en -100 is 100, vergeleken met 0 === 1
- // 200 en -200 is 0, vergeleken met 0 === 0
- // 200 en -300 is -100, vergeleken met 0 === -1
-
- $left = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : '0';
- $spent = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0';
- $overspent = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $repetition->amount);
-
- $budgetLine->setLeft($left);
- $budgetLine->setSpent($expenses);
- $budgetLine->setOverspent($overspent);
+ $budgetLine->setLeft($data['left']);
+ $budgetLine->setSpent($data['expenses']);
+ $budgetLine->setOverspent($data['overspent']);
$budgetLine->setBudgeted($repetition->amount);
$object->addBudgeted($repetition->amount);
- $object->addSpent($spent);
- $object->addLeft($left);
- $object->addOverspent($overspent);
+ $object->addSpent($data['spent']);
+ $object->addLeft($data['left']);
+ $object->addOverspent($data['overspent']);
$object->addBudgetLine($budgetLine);
}
@@ -96,7 +94,8 @@ class BudgetReportHelper implements BudgetReportHelperInterface
}
// stuff outside of budgets:
- $noBudget = $repository->getWithoutBudgetSum($accounts, $start, $end);
+
+ $noBudget = $this->repository->spentInPeriodWithoutBudget($accounts, $start, $end);
$budgetLine = new BudgetLine;
$budgetLine->setOverspent($noBudget);
$budgetLine->setSpent($noBudget);
@@ -107,7 +106,37 @@ class BudgetReportHelper implements BudgetReportHelperInterface
}
/**
- * Take the array as returned by SingleCategoryRepositoryInterface::spentPerDay and SingleCategoryRepositoryInterface::earnedByDay
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param Collection $accounts
+ *
+ * @return Collection
+ */
+ public function getBudgetsWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection
+ {
+ /** @var BudgetRepositoryInterface $repository */
+ $repository = app(BudgetRepositoryInterface::class);
+ $budgets = $repository->getActiveBudgets();
+
+ $set = new Collection;
+ /** @var Budget $budget */
+ foreach ($budgets as $budget) {
+ $total = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
+ if (bccomp($total, '0') === -1) {
+ $set->push($budget);
+ }
+ }
+ $set = $set->sortBy(
+ function (Budget $budget) {
+ return $budget->name;
+ }
+ );
+
+ return $set;
+ }
+
+ /**
+ * Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
* and sum up everything in the array in the given range.
*
* @param Carbon $start
@@ -132,4 +161,24 @@ class BudgetReportHelper implements BudgetReportHelperInterface
return $sum;
}
+
+ /**
+ * @param Budget $budget
+ * @param LimitRepetition $repetition
+ * @param Collection $accounts
+ *
+ * @return array
+ */
+ private function calculateExpenses(Budget $budget, LimitRepetition $repetition, Collection $accounts): array
+ {
+ $array = [];
+ $expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate);
+ $array['left'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : '0';
+ $array['spent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0';
+ $array['overspent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $repetition->amount);
+ $array['expenses'] = $expenses;
+
+ return $array;
+
+ }
}
diff --git a/app/Helpers/Report/BudgetReportHelperInterface.php b/app/Helpers/Report/BudgetReportHelperInterface.php
index b1cd8fb955..dd0290f6ae 100644
--- a/app/Helpers/Report/BudgetReportHelperInterface.php
+++ b/app/Helpers/Report/BudgetReportHelperInterface.php
@@ -1,5 +1,4 @@
query = $query;
$this->budgetRepository = $budgetRepository;
$this->tagRepository = $tagRepository;
}
@@ -63,8 +71,8 @@ class ReportHelper implements ReportHelperInterface
*/
public function getBillReport(Carbon $start, Carbon $end, Collection $accounts): BillCollection
{
- /** @var \FireflyIII\Repositories\Bill\BillRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Bill\BillRepositoryInterface');
+ /** @var BillRepositoryInterface $repository */
+ $repository = app(BillRepositoryInterface::class);
$bills = $repository->getBillsForAccounts($accounts);
$journals = $repository->getAllJournalsInRange($bills, $start, $end);
$collection = new BillCollection;
@@ -73,10 +81,10 @@ class ReportHelper implements ReportHelperInterface
foreach ($bills as $bill) {
$billLine = new BillLine;
$billLine->setBill($bill);
- $billLine->setActive(intval($bill->active) == 1);
+ $billLine->setActive(intval($bill->active) === 1);
$billLine->setMin($bill->amount_min);
$billLine->setMax($bill->amount_max);
-
+ $billLine->setHit(false);
// is hit in period?
$entry = $journals->filter(
@@ -89,13 +97,11 @@ class ReportHelper implements ReportHelperInterface
$billLine->setTransactionJournalId($first->id);
$billLine->setAmount($first->journalAmount);
$billLine->setHit(true);
- } else {
- $billLine->setHit(false);
- }
- if (!(!$billLine->isHit() && !$billLine->isActive())) {
- $collection->addBill($billLine);
}
+ if ($billLine->isActive()) {
+ $collection->addBill($billLine);
+ }
}
return $collection;
@@ -111,15 +117,15 @@ class ReportHelper implements ReportHelperInterface
public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts): CategoryCollection
{
$object = new CategoryCollection;
+ /** @var CategoryRepositoryInterface $repository */
+ $repository = app(CategoryRepositoryInterface::class);
+ $categories = $repository->getCategories();
- /**
- * GET CATEGORIES:
- */
- /** @var \FireflyIII\Repositories\Category\CategoryRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface');
-
- $set = $repository->spentForAccountsPerMonth($accounts, $start, $end);
- foreach ($set as $category) {
+ /** @var Category $category */
+ foreach ($categories as $category) {
+ $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
+ // CategoryCollection expects the amount in $spent:
+ $category->spent = $spent;
$object->addCategory($category);
}
@@ -138,10 +144,14 @@ class ReportHelper implements ReportHelperInterface
public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): Expense
{
$object = new Expense;
- $set = $this->query->expense($accounts, $start, $end);
+ /** @var AccountRepositoryInterface $repos */
+ $repos = app(AccountRepositoryInterface::class);
+ $journals = $repos->expensesInPeriod($accounts, $start, $end);
- foreach ($set as $entry) {
- $object->addToTotal($entry->journalAmount); // can be positive, if it's a transfer
+ /** @var TransactionJournal $entry */
+ foreach ($journals as $entry) {
+ $amount = TransactionJournal::amount($entry);
+ $object->addToTotal($amount);
$object->addOrCreateExpense($entry);
}
@@ -160,10 +170,13 @@ class ReportHelper implements ReportHelperInterface
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): Income
{
$object = new Income;
- $set = $this->query->income($accounts, $start, $end);
+ /** @var AccountRepositoryInterface $repos */
+ $repos = app(AccountRepositoryInterface::class);
+ $journals = $repos->incomesInPeriod($accounts, $start, $end);
- foreach ($set as $entry) {
- $object->addToTotal($entry->journalAmount);
+ foreach ($journals as $entry) {
+ $amount = TransactionJournal::amount($entry);
+ $object->addToTotal($amount);
$object->addOrCreateIncome($entry);
}
@@ -178,7 +191,7 @@ class ReportHelper implements ReportHelperInterface
public function listOfMonths(Carbon $date): array
{
/** @var FiscalHelperInterface $fiscalHelper */
- $fiscalHelper = app('FireflyIII\Helpers\FiscalHelperInterface');
+ $fiscalHelper = app(FiscalHelperInterface::class);
$start = clone $date;
$start->startOfMonth();
$end = Carbon::now();
@@ -186,9 +199,7 @@ class ReportHelper implements ReportHelperInterface
$months = [];
while ($start <= $end) {
- // current year:
- $year = $fiscalHelper->endOfFiscalYear($start)->year;
-
+ $year = $fiscalHelper->endOfFiscalYear($start)->year; // current year
if (!isset($months[$year])) {
$months[$year] = [
'fiscal_start' => $fiscalHelper->startOfFiscalYear($start)->format('Y-m-d'),
@@ -201,7 +212,6 @@ class ReportHelper implements ReportHelperInterface
$currentEnd = clone $start;
$currentEnd->endOfMonth();
-
$months[$year]['months'][] = [
'formatted' => $start->formatLocalized('%B %Y'),
'start' => $start->format('Y-m-d'),
@@ -210,8 +220,7 @@ class ReportHelper implements ReportHelperInterface
'year' => $year,
];
- // to make the hop to the next month properly:
- $start = clone $currentEnd;
+ $start = clone $currentEnd; // to make the hop to the next month properly
$start->addDay();
}
@@ -259,17 +268,14 @@ class ReportHelper implements ReportHelperInterface
/** @var Tag $entry */
foreach ($set as $entry) {
// less than zero? multiply to be above zero.
- $amount = $entry->amount;
- $id = intval($entry->id);
- if (!isset($collection[$id])) {
- $collection[$id] = [
- 'id' => $id,
- 'tag' => $entry->tag,
- 'amount' => $amount,
- ];
- } else {
- $collection[$id]['amount'] = bcadd($collection[$id]['amount'], $amount);
- }
+ $amount = $entry->amount;
+ $id = intval($entry->id);
+ $previousAmount = $collection[$id]['amount'] ?? '0';
+ $collection[$id] = [
+ 'id' => $id,
+ 'tag' => $entry->tag,
+ 'amount' => bcadd($previousAmount, $amount),
+ ];
}
// cleanup collection (match "fonts")
@@ -286,7 +292,7 @@ class ReportHelper implements ReportHelperInterface
}
/**
- * Take the array as returned by SingleCategoryRepositoryInterface::spentPerDay and SingleCategoryRepositoryInterface::earnedByDay
+ * Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
* and sum up everything in the array in the given range.
*
* @param Carbon $start
diff --git a/app/Helpers/Report/ReportHelperInterface.php b/app/Helpers/Report/ReportHelperInterface.php
index 33be9fe4e8..d4a887d8a0 100644
--- a/app/Helpers/Report/ReportHelperInterface.php
+++ b/app/Helpers/Report/ReportHelperInterface.php
@@ -1,4 +1,12 @@
'123.45'
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function earnedPerMonth(Collection $accounts, Carbon $start, Carbon $end): array
- {
- $ids = $accounts->pluck('id')->toArray();
- $query = Auth::user()->transactionjournals()
- ->leftJoin(
- 'transactions AS t_from', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions AS t_to', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0);
- }
- )
- ->whereIn('t_to.account_id', $ids)
- ->whereNotIn('t_from.account_id', $ids)
- ->after($start)
- ->before($end)
- ->transactionTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE])
- ->groupBy('dateFormatted')
- ->get(
- [
- DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'),
- DB::raw('SUM(`t_to`.`amount`) AS `sum`'),
- ]
- );
- $array = [];
- foreach ($query as $result) {
- $array[$result->dateFormatted] = $result->sum;
- }
-
- return $array;
- }
-
- /**
- * This method returns all the "out" transaction journals for the given account and given period. The amount
- * is stored in "journalAmount".
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function expense(Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $ids = $accounts->pluck('id')->toArray();
- $set = Auth::user()->transactionjournals()
- ->leftJoin(
- 'transactions as t_from', function (JoinClause $join) {
- $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions as t_to', function (JoinClause $join) {
- $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0);
- }
- )
- ->leftJoin('accounts', 't_to.account_id', '=', 'accounts.id')
- ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
- ->before($end)
- ->after($start)
- ->whereIn('t_from.account_id', $ids)
- ->whereNotIn('t_to.account_id', $ids)
- ->get(['transaction_journals.*', 't_from.amount as journalAmount', 'accounts.id as account_id', 'accounts.name as account_name']);
-
- return $set;
- }
-
- /**
- * This method returns all the "in" transaction journals for the given account and given period. The amount
- * is stored in "journalAmount".
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function income(Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $ids = $accounts->pluck('id')->toArray();
- $set = Auth::user()->transactionjournals()
- ->leftJoin(
- 'transactions as t_from', function (JoinClause $join) {
- $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions as t_to', function (JoinClause $join) {
- $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0);
- }
- )
- ->leftJoin('accounts', 't_from.account_id', '=', 'accounts.id')
- ->transactionTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
- ->before($end)
- ->after($start)
- ->whereIn('t_to.account_id', $ids)
- ->whereNotIn('t_from.account_id', $ids)
- ->get(['transaction_journals.*', 't_to.amount as journalAmount', 'accounts.id as account_id', 'accounts.name as account_name']);
-
- return $set;
- }
-
- /**
- * Returns an array of the amount of money spent in the given accounts (on withdrawals, opening balances and transfers)
- * grouped by month like so: "2015-01" => '123.45'
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentPerMonth(Collection $accounts, Carbon $start, Carbon $end): array
- {
- $ids = $accounts->pluck('id')->toArray();
- $query = Auth::user()->transactionjournals()
- ->leftJoin(
- 'transactions AS t_from', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions AS t_to', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0);
- }
- )
- ->whereIn('t_from.account_id', $ids)
- ->whereNotIn('t_to.account_id', $ids)
- ->after($start)
- ->before($end)
- ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE])
- ->groupBy('dateFormatted')
- ->get(
- [
- DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'),
- DB::raw('SUM(`t_from`.`amount`) AS `sum`'),
- ]
- );
- $array = [];
- foreach ($query as $result) {
- $array[$result->dateFormatted] = $result->sum;
- }
-
- return $array;
-
- }
-}
diff --git a/app/Helpers/Report/ReportQueryInterface.php b/app/Helpers/Report/ReportQueryInterface.php
deleted file mode 100644
index f746710ffb..0000000000
--- a/app/Helpers/Report/ReportQueryInterface.php
+++ /dev/null
@@ -1,66 +0,0 @@
- '123.45'
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function earnedPerMonth(Collection $accounts, Carbon $start, Carbon $end): array;
-
- /**
- * This method returns all the "out" transaction journals for the given account and given period. The amount
- * is stored in "journalAmount".
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function expense(Collection $accounts, Carbon $start, Carbon $end): Collection;
-
- /**
- * This method returns all the "in" transaction journals for the given account and given period. The amount
- * is stored in "journalAmount".
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function income(Collection $accounts, Carbon $start, Carbon $end): Collection;
-
- /**
- * Returns an array of the amount of money spent in the given accounts (on withdrawals, opening balances and transfers)
- * grouped by month like so: "2015-01" => '123.45'
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentPerMonth(Collection $accounts, Carbon $start, Carbon $end): array;
-
-
-}
diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
index 969adeb3c3..71ea51855e 100644
--- a/app/Http/Controllers/AccountController.php
+++ b/app/Http/Controllers/AccountController.php
@@ -1,13 +1,29 @@
-accountType->type);
+ $typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
- $accountList = ExpandedForm::makeSelectList($repository->getAccounts([$account->accountType->type]), true);
+ $accountList = ExpandedForm::makeSelectListWithEmpty($crud->getAccountsByType([$account->accountType->type]));
unset($accountList[$account->id]);
// put previous url in session
@@ -78,19 +92,19 @@ class AccountController extends Controller
}
/**
- * @param ARI $repository
- * @param Account $account
+ * @param AccountCrudInterface $crud
+ * @param Account $account
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
- public function destroy(ARI $repository, Account $account)
+ public function destroy(AccountCrudInterface $crud, Account $account)
{
$type = $account->accountType->type;
- $typeName = Config::get('firefly.shortNamesByFullName.' . $type);
+ $typeName = config('firefly.shortNamesByFullName.' . $type);
$name = $account->name;
- $moveTo = $repository->find(intval(Input::get('move_account_before_delete')));
+ $moveTo = $crud->find(intval(Input::get('move_account_before_delete')));
- $repository->destroy($account, $moveTo);
+ $crud->destroy($account, $moveTo);
Session::flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name])));
Preferences::mark();
@@ -107,9 +121,9 @@ class AccountController extends Controller
public function edit(ARI $repository, Account $account)
{
- $what = Config::get('firefly.shortNamesByFullName')[$account->accountType->type];
+ $what = config('firefly.shortNamesByFullName')[$account->accountType->type];
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
- $subTitleIcon = Config::get('firefly.subIconsByIdentifier.' . $what);
+ $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$openingBalance = $repository->openingBalanceTransaction($account);
// put previous url in session if not redirect from store (not "return_to_edit").
@@ -145,23 +159,21 @@ class AccountController extends Controller
}
/**
- * @param ARI $repository
- * @param $what
+ * @param AccountCrudInterface $crud
+ * @param string $what
*
- * @return \Illuminate\View\View
+ * @return View
*/
- public function index(ARI $repository, string $what)
+ public function index(AccountCrudInterface $crud, string $what)
{
$what = $what ?? 'asset';
$subTitle = trans('firefly.' . $what . '_accounts');
- $subTitleIcon = Config::get('firefly.subIconsByIdentifier.' . $what);
- $types = Config::get('firefly.accountTypesByIdentifier.' . $what);
- $accounts = $repository->getAccounts($types);
- /** @var Carbon $start */
- $start = clone session('start', Carbon::now()->startOfMonth());
- /** @var Carbon $end */
- $end = clone session('end', Carbon::now()->endOfMonth());
+ $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
+ $types = config('firefly.accountTypesByIdentifier.' . $what);
+ $accounts = $crud->getAccountsByType($types);
+ $start = clone session('start', Carbon::now()->startOfMonth());
+ $end = clone session('end', Carbon::now()->endOfMonth());
$start->subDay();
$ids = $accounts->pluck('id')->toArray();
@@ -188,25 +200,94 @@ class AccountController extends Controller
*/
public function show(ARI $repository, Account $account)
{
- $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
+ // show journals from current period only:
+ $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
+ $subTitle = $account->name;
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = session('start', Navigation::startOfPeriod(new Carbon, $range));
+ $end = session('end', Navigation::endOfPeriod(new Carbon, $range));
+ $page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
- $subTitleIcon = Config::get('firefly.subTitlesByIdentifier.' . $account->accountType->type);
- $what = Config::get('firefly.shortNamesByFullName.' . $account->accountType->type);
- $journals = $repository->getJournals($account, $page, $pageSize);
- $subTitle = trans('firefly.details_for_' . $what, ['name' => $account->name]);
+ $offset = ($page - 1) * $pageSize;
+ $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end);
+ $count = $set->count();
+ $subSet = $set->splice($offset, $pageSize);
+ $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
$journals->setPath('accounts/show/' . $account->id);
+ // grouped other months thing:
+ // oldest transaction in account:
+ $start = $repository->firstUseDate($account);
+ if ($start->year == 1900) {
+ $start = new Carbon;
+ }
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = Navigation::startOfPeriod($start, $range);
+ $end = Navigation::endOfX(new Carbon, $range);
+ $entries = new Collection;
- return view('accounts.show', compact('account', 'what', 'subTitleIcon', 'journals', 'subTitle'));
+ // chart properties for cache:
+ $cache = new CacheProperties;
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('account-show');
+ $cache->addProperty($account->id);
+
+
+ if ($cache->has()) {
+ $entries = $cache->get();
+
+ return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
+ }
+
+ while ($end >= $start) {
+ $end = Navigation::startOfPeriod($end, $range);
+ $currentEnd = Navigation::endOfPeriod($end, $range);
+ $spent = $this->spentInPeriod($account, $end, $currentEnd);
+ $earned = $this->earnedInPeriod($account, $end, $currentEnd);
+ $dateStr = $end->format('Y-m-d');
+ $dateName = Navigation::periodShow($end, $range);
+ $entries->push([$dateStr, $dateName, $spent, $earned]);
+ $end = Navigation::subtractPeriod($end, $range, 1);
+
+ }
+
+ return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
}
/**
- * @param AccountFormRequest $request
- * @param ARI $repository
+ * @param ARI $repository
+ * @param Account $account
+ * @param string $date
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return View
*/
- public function store(AccountFormRequest $request, ARI $repository)
+ public function showWithDate(ARI $repository, Account $account, string $date)
+ {
+ $carbon = new Carbon($date);
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = Navigation::startOfPeriod($carbon, $range);
+ $end = Navigation::endOfPeriod($carbon, $range);
+ $subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
+ $page = intval(Input::get('page'));
+ $pageSize = Preferences::get('transactionPageSize', 50)->data;
+ $offset = ($page - 1) * $pageSize;
+ $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end);
+ $count = $set->count();
+ $subSet = $set->splice($offset, $pageSize);
+ $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
+ $journals->setPath('accounts/show/' . $account->id . '/' . $date);
+
+ return view('accounts.show_with_date', compact('category', 'date', 'account', 'journals', 'subTitle', 'carbon'));
+ }
+
+ /**
+ * @param AccountFormRequest $request
+ * @param AccountCrudInterface $crud
+ *
+ * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ */
+ public function store(AccountFormRequest $request, AccountCrudInterface $crud)
{
$accountData = [
'name' => $request->input('name'),
@@ -224,7 +305,7 @@ class AccountController extends Controller
];
- $account = $repository->store($accountData);
+ $account = $crud->store($accountData);
Session::flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
Preferences::mark();
@@ -248,13 +329,13 @@ class AccountController extends Controller
}
/**
- * @param AccountFormRequest $request
- * @param ARI $repository
- * @param Account $account
+ * @param AccountFormRequest $request
+ * @param AccountCrudInterface $crud
+ * @param Account $account
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
- public function update(AccountFormRequest $request, ARI $repository, Account $account)
+ public function update(AccountFormRequest $request, AccountCrudInterface $crud, Account $account)
{
$accountData = [
@@ -271,7 +352,7 @@ class AccountController extends Controller
'ccType' => $request->input('ccType'),
'ccMonthlyPaymentDate' => $request->input('ccMonthlyPaymentDate'),
];
- $repository->update($account, $accountData);
+ $crud->update($account, $accountData);
Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
Preferences::mark();
@@ -304,4 +385,68 @@ class AccountController extends Controller
return '';
}
+ /**
+ * Asset accounts actually earn money by being the destination of a deposit or the destination
+ * of a transfer. The money moves to them.
+ *
+ * A revenue account doesn't really earn money itself. Money is earned "from" the revenue account.
+ * So, the call to find out how many money has been earned by/from a revenue account is slightly different.
+ *
+ *
+ *
+ * @param Account $account
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ private function earnedInPeriod(Account $account, Carbon $start, Carbon $end)
+ {
+ /** @var ARI $repository */
+ $repository = app(ARI::class);
+ $collection = new Collection([$account]);
+ $type = $account->accountType->type;
+ switch ($type) {
+ case AccountType::DEFAULT:
+ case AccountType::ASSET:
+ return $repository->earnedInPeriod($collection, $start, $end);
+ case AccountType::REVENUE:
+ return $repository->earnedFromInPeriod($collection, $start, $end);
+ default:
+ return '0';
+ }
+ }
+
+ /**
+ * Asset accounts actually spend money by being the source of a withdrawal or the source
+ * of a transfer. The money moves away from them.
+ *
+ * An expense account doesn't really spend money itself. Money is spent "at" the expense account.
+ * So, the call to find out how many money has been spent on/at an expense account is slightly different.
+ *
+ *
+ *
+ * @param Account $account
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ private function spentInPeriod(Account $account, Carbon $start, Carbon $end): string
+ {
+ /** @var ARI $repository */
+ $repository = app(ARI::class);
+ $collection = new Collection([$account]);
+ $type = $account->accountType->type;
+ switch ($type) {
+ case AccountType::DEFAULT:
+ case AccountType::ASSET:
+ return $repository->spentInPeriod($collection, $start, $end);
+ case AccountType::EXPENSE:
+ return $repository->spentAtInPeriod($collection, $start, $end);
+ default:
+ return '0';
+ }
+ }
+
}
diff --git a/app/Http/Controllers/Admin/HomeController.php b/app/Http/Controllers/Admin/HomeController.php
index 47dc4eee08..4ca5dafeb9 100644
--- a/app/Http/Controllers/Admin/HomeController.php
+++ b/app/Http/Controllers/Admin/HomeController.php
@@ -7,6 +7,8 @@
* of the MIT license. See the LICENSE file for details.
*/
+declare(strict_types = 1);
+
namespace FireflyIII\Http\Controllers\Admin;
diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php
index 9dbc765d07..996fc4c991 100644
--- a/app/Http/Controllers/Admin/UserController.php
+++ b/app/Http/Controllers/Admin/UserController.php
@@ -7,6 +7,8 @@
* of the MIT license. See the LICENSE file for details.
*/
+declare(strict_types = 1);
+
namespace FireflyIII\Http\Controllers\Admin;
@@ -32,33 +34,25 @@ class UserController extends Controller
$subTitle = strval(trans('firefly.user_administration'));
$subTitleIcon = 'fa-users';
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
-
- // list all users:
- $users = $repository->all();
+ $users = $repository->all();
// add meta stuff.
$users->each(
function (User $user) use ($confirmAccount) {
// is user activated?
- $isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data;
+ $isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data;
+ $user->activated = true;
if ($isConfirmed === false && $confirmAccount === true) {
$user->activated = false;
- } else {
- $user->activated = true;
}
- // is user admin?
$user->isAdmin = $user->hasRole('owner');
-
- // user has 2FA enabled?
- $is2faEnabled = Preferences::getForUser($user, 'twoFactorAuthEnabled', false)->data;
- $has2faSecret = !is_null(Preferences::getForUser($user, 'twoFactorAuthSecret'));
+ $is2faEnabled = Preferences::getForUser($user, 'twoFactorAuthEnabled', false)->data;
+ $has2faSecret = !is_null(Preferences::getForUser($user, 'twoFactorAuthSecret'));
+ $user->has2FA = false;
if ($is2faEnabled && $has2faSecret) {
$user->has2FA = true;
- } else {
- $user->has2FA = false;
}
-
}
);
diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php
index a8f7b9eba5..94763e9327 100644
--- a/app/Http/Controllers/AttachmentController.php
+++ b/app/Http/Controllers/AttachmentController.php
@@ -1,4 +1,12 @@
mime == 'application/pdf') {
- $file = public_path('images/page_white_acrobat.png');
- } else {
- $file = public_path('images/page_green.png');
- }
+ $image = 'images/page_green.png';
+
+ if ($attachment->mime == 'application/pdf') {
+ $image = 'images/page_white_acrobat.png';
+ }
+ $file = public_path($image);
$response = Response::make(File::get($file));
$response->header('Content-Type', 'image/png');
diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php
index fe39b4aedf..7cec83dee4 100644
--- a/app/Http/Controllers/Auth/AuthController.php
+++ b/app/Http/Controllers/Auth/AuthController.php
@@ -1,4 +1,12 @@
data;
$time = Preferences::get('user_confirmed_last_mail', 0)->data;
$now = time();
- $maxDiff = Config::get('firefly.confirmation_age');
+ $maxDiff = config('firefly.confirmation_age');
if ($database === $code && ($now - $time <= $maxDiff)) {
Preferences::setForUser(Auth::user(), 'user_confirmed', true);
@@ -56,9 +56,8 @@ class ConfirmationController extends Controller
Session::flash('success', strval(trans('firefly.account_is_confirmed')));
return redirect(route('home'));
- } else {
- throw new FireflyException(trans('firefly.invalid_activation_code'));
}
+ throw new FireflyException(trans('firefly.invalid_activation_code'));
}
/**
@@ -68,16 +67,15 @@ class ConfirmationController extends Controller
{
$time = Preferences::get('user_confirmed_last_mail', 0)->data;
$now = time();
- $maxDiff = Config::get('firefly.resend_confirmation');
+ $maxDiff = config('firefly.resend_confirmation');
$owner = env('SITE_OWNER', 'mail@example.com');
+ $view = 'auth.confirmation.no-resent';
if ($now - $time > $maxDiff) {
-
event(new ResendConfirmation(Auth::user(), $request->ip()));
-
- return view('auth.confirmation.resent', ['owner' => $owner]);
- } else {
- return view('auth.confirmation.no-resent', ['owner' => $owner]);
+ $view = 'auth.confirmation.resent';
}
+
+ return view($view, ['owner' => $owner]);
}
}
diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php
index e341c46372..6c72dd16c4 100644
--- a/app/Http/Controllers/Auth/PasswordController.php
+++ b/app/Http/Controllers/Auth/PasswordController.php
@@ -1,4 +1,12 @@
validate($request, ['email' => 'required|email']);
- $user = User::whereEmail($request->get('email'))->first();
+ $user = User::whereEmail($request->get('email'))->first();
+ $response = 'passwords.blocked';
- if (!is_null($user) && intval($user->blocked) === 1) {
- $response = 'passwords.blocked';
- } else {
+ if (is_null($user)) {
+ $response = Password::INVALID_USER;
+ }
+
+ if (!is_null($user) && intval($user->blocked) === 0) {
$response = Password::sendResetLink(
$request->only('email'), function (Message $message) {
$message->subject($this->getEmailSubject());
diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php
index eb063155bf..70f5a3e5ae 100644
--- a/app/Http/Controllers/Auth/TwoFactorController.php
+++ b/app/Http/Controllers/Auth/TwoFactorController.php
@@ -1,5 +1,4 @@
$bill->name]);
// put previous url in session if not redirect from store (not "return_to_edit").
@@ -174,9 +190,9 @@ class BillController extends Controller
*/
public function show(BillRepositoryInterface $repository, Bill $bill)
{
- $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
- $pageSize = Preferences::get('transactionPageSize', 50)->data;
- $journals = $repository->getJournals($bill, $page, $pageSize);
+ $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
+ $pageSize = Preferences::get('transactionPageSize', 50)->data;
+ $journals = $repository->getJournals($bill, $page, $pageSize);
$journals->setPath('/bills/show/' . $bill->id);
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
$hideBill = true;
diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php
index 0d79d6a3b8..a858e82252 100644
--- a/app/Http/Controllers/BudgetController.php
+++ b/app/Http/Controllers/BudgetController.php
@@ -1,14 +1,28 @@
-startOfMonth());
- $limitRepetition = $repository->updateLimitAmount($budget, $date, $amount);
+ /** @var Carbon $start */
+ $start = session('start', Carbon::now()->startOfMonth());
+ /** @var Carbon $end */
+ $end = session('end', Carbon::now()->endOfMonth());
+ $viewRange = Preferences::get('viewRange', '1M')->data;
+
+ // is custom view range?
+ if (session('is_custom_range') === true) {
+ $viewRange = 'custom';
+ }
+
+ $limitRepetition = $repository->updateLimitAmount($budget, $start, $end, $viewRange, $amount);
if ($amount == 0) {
$limitRepetition = null;
}
@@ -135,38 +158,60 @@ class BudgetController extends Controller
/**
* @param BudgetRepositoryInterface $repository
+ * @param AccountCrudInterface $crud
*
- * @param ARI $accountRepository
- *
- * @return \Illuminate\View\View
+ * @return View
*/
- public function index(BudgetRepositoryInterface $repository, ARI $accountRepository)
+ public function index(BudgetRepositoryInterface $repository, AccountCrudInterface $crud)
{
- $budgets = $repository->getActiveBudgets();
- $inactive = $repository->getInactiveBudgets();
- $spent = '0';
- $budgeted = '0';
- $range = Preferences::get('viewRange', '1M')->data;
- /** @var Carbon $date */
- $date = session('start', new Carbon);
- $start = Navigation::startOfPeriod($date, $range);
- $end = Navigation::endOfPeriod($start, $range);
+ $budgets = $repository->getActiveBudgets();
+ $inactive = $repository->getInactiveBudgets();
+ $spent = '0';
+ $budgeted = '0';
+ $range = Preferences::get('viewRange', '1M')->data;
+ $repeatFreq = Config::get('firefly.range_to_repeat_freq.' . $range);
+
+ if (session('is_custom_range') === true) {
+ $repeatFreq = 'custom';
+ }
+
+ /** @var Carbon $start */
+ $start = session('start', new Carbon);
+ /** @var Carbon $end */
+ $end = session('end', new Carbon);
$key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd');
$budgetIncomeTotal = Preferences::get($key, 1000)->data;
$period = Navigation::periodShow($start, $range);
- $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
-
- /**
- * Do some cleanup:
- */
- $repository->cleanupBudgets();
+ $periodStart = $start->formatLocalized($this->monthAndDayFormat);
+ $periodEnd = $end->formatLocalized($this->monthAndDayFormat);
+ $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
+ $startAsString = $start->format('Y-m-d');
+ $endAsString = $end->format('Y-m-d');
// loop the budgets:
/** @var Budget $budget */
foreach ($budgets as $budget) {
- $budget->spent = $repository->balanceInPeriod($budget, $start, $end, $accounts);
- $budget->currentRep = $repository->getCurrentRepetition($budget, $start, $end);
- if (!is_null($budget->currentRep->id)) {
+ $budget->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
+ $allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
+ $otherRepetitions = new Collection;
+
+ /** @var LimitRepetition $repetition */
+ foreach ($allRepetitions as $repetition) {
+ if ($repetition->budget_id == $budget->id) {
+ if ($repetition->budgetLimit->repeat_freq == $repeatFreq
+ && $repetition->startdate->format('Y-m-d') == $startAsString
+ && $repetition->enddate->format('Y-m-d') == $endAsString
+ ) {
+ // do something
+ $budget->currentRep = $repetition;
+ continue;
+ }
+ $otherRepetitions->push($repetition);
+ }
+ }
+ $budget->otherRepetitions = $otherRepetitions;
+
+ if (!is_null($budget->currentRep) && !is_null($budget->currentRep->id)) {
$budgeted = bcadd($budgeted, $budget->currentRep->amount);
}
$spent = bcadd($spent, $budget->spent);
@@ -178,7 +223,12 @@ class BudgetController extends Controller
$defaultCurrency = Amount::getDefaultCurrency();
return view(
- 'budgets.index', compact('budgetMaximum', 'period', 'range', 'budgetIncomeTotal', 'defaultCurrency', 'inactive', 'budgets', 'spent', 'budgeted')
+ 'budgets.index', compact(
+ 'budgetMaximum', 'periodStart', 'periodEnd',
+ 'period', 'range', 'budgetIncomeTotal',
+ 'defaultCurrency', 'inactive', 'budgets',
+ 'spent', 'budgeted'
+ )
);
}
@@ -196,7 +246,11 @@ class BudgetController extends Controller
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
- $list = $repository->getWithoutBudget($start, $end, $page, $pageSize);
+ $offset = ($page - 1) * $pageSize;
+ $journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end);
+ $count = $journals->count();
+ $journals = $journals->slice($offset, $pageSize);
+ $list = new LengthAwarePaginator($journals, $count, $pageSize);
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -227,45 +281,71 @@ class BudgetController extends Controller
/**
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
- * @param LimitRepetition|null $repetition
*
* @return View
* @throws FireflyException
*/
- public function show(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition = null)
+ public function show(BudgetRepositoryInterface $repository, Budget $budget)
{
- if (!is_null($repetition->id) && $repetition->budgetLimit->budget->id != $budget->id) {
- throw new FireflyException('This budget limit is not part of this budget.');
- }
-
+ /** @var Carbon $start */
+ $start = session('first', Carbon::create()->startOfYear());
+ $end = new Carbon;
+ $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
- $journals = $repository->getJournals($budget, $repetition, $pageSize);
+ $offset = ($page - 1) * $pageSize;
+ $journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end);
+ $count = $journals->count();
+ $journals = $journals->slice($offset, $pageSize);
+ $journals = new LengthAwarePaginator($journals, $count, $pageSize);
- if (is_null($repetition->id)) {
- $start = $repository->firstActivity($budget);
- $end = new Carbon;
- $set = $budget->limitrepetitions()->orderBy('startdate', 'DESC')->get();
- $subTitle = e($budget->name);
- $journals->setPath('/budgets/show/' . $budget->id);
- } else {
- $start = $repetition->startdate;
- $end = $repetition->enddate;
- $set = new Collection([$repetition]);
- $subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]);
- $journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id);
- }
+ $journals->setPath('/budgets/show/' . $budget->id);
- $spentArray = $repository->spentPerDay($budget, $start, $end);
- $limits = new Collection();
+
+ $set = $budget->limitrepetitions()->orderBy('startdate', 'DESC')->get();
+ $subTitle = e($budget->name);
+ $limits = new Collection();
/** @var LimitRepetition $entry */
foreach ($set as $entry) {
- $entry->spent = $this->getSumOfRange($entry->startdate, $entry->enddate, $spentArray);
+ $entry->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $entry->startdate, $entry->enddate);
$limits->push($entry);
}
+ return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
+ }
+
+ /**
+ * @param BudgetRepositoryInterface $repository
+ * @param Budget $budget
+ * @param LimitRepetition $repetition
+ *
+ * @return View
+ * @throws FireflyException
+ */
+ public function showWithRepetition(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition)
+ {
+ if ($repetition->budgetLimit->budget->id != $budget->id) {
+ throw new FireflyException('This budget limit is not part of this budget.');
+ }
+ $start = $repetition->startdate;
+ $end = $repetition->enddate;
+ $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
+ $pageSize = Preferences::get('transactionPageSize', 50)->data;
+ $offset = ($page - 1) * $pageSize;
+ $journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end);
+ $count = $journals->count();
+ $journals = $journals->slice($offset, $pageSize);
+ $journals = new LengthAwarePaginator($journals, $count, $pageSize);
+ $subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]);
+
+ $journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id);
+
+
+ $repetition->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate);
+ $limits = new Collection([$repetition]);
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
+
}
/**
@@ -333,16 +413,19 @@ class BudgetController extends Controller
*/
public function updateIncome()
{
- $range = Preferences::get('viewRange', '1M')->data;
+ $range = Preferences::get('viewRange', '1M')->data;
+ $format = strval(trans('config.month_and_day'));
/** @var Carbon $date */
- $date = session('start', new Carbon);
- $start = Navigation::startOfPeriod($date, $range);
- $end = Navigation::endOfPeriod($start, $range);
- $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd');
- $amount = Preferences::get($key, 1000);
+ $date = session('start', new Carbon);
+ $start = Navigation::startOfPeriod($date, $range);
+ $end = Navigation::endOfPeriod($start, $range);
+ $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd');
+ $amount = Preferences::get($key, 1000);
+ $displayStart = $start->formatLocalized($format);
+ $displayEnd = $end->formatLocalized($format);
- return view('budgets.income', compact('amount'));
+ return view('budgets.income', compact('amount', 'displayStart', 'displayEnd'));
}
}
diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php
index 6b1456a5fa..1169383eca 100644
--- a/app/Http/Controllers/CategoryController.php
+++ b/app/Http/Controllers/CategoryController.php
@@ -1,11 +1,21 @@
-name;
@@ -108,18 +118,17 @@ class CategoryController extends Controller
}
/**
- * @param CRI $repository
- * @param SCRI $singleRepository
+ * @param CRI $repository
*
* @return \Illuminate\View\View
*/
- public function index(CRI $repository, SCRI $singleRepository)
+ public function index(CRI $repository)
{
$categories = $repository->getCategories();
$categories->each(
- function (Category $category) use ($singleRepository) {
- $category->lastActivity = $singleRepository->getLatestActivity($category);
+ function (Category $category) use ($repository) {
+ $category->lastActivity = $repository->lastUseDate($category, new Collection);
}
);
@@ -137,7 +146,7 @@ class CategoryController extends Controller
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->startOfMonth());
- $list = $repository->listNoCategory($start, $end);
+ $list = $repository->journalsInPeriodWithoutCategory(new Collection(), [], $start, $end);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -147,26 +156,34 @@ class CategoryController extends Controller
}
/**
- * @param SCRI $repository
+ * @param CRI $repository
* @param Category $category
*
* @return \Illuminate\View\View
*/
- public function show(SCRI $repository, Category $category)
+ public function show(CRI $repository, Category $category)
{
+ /** @var Carbon $carbon */
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = session('start', Navigation::startOfPeriod(new Carbon, $range));
+ $end = session('end', Navigation::endOfPeriod(new Carbon, $range));
$hideCategory = true; // used in list.
- $pageSize = Preferences::get('transactionPageSize', 50)->data;
$page = intval(Input::get('page'));
- $set = $repository->getJournals($category, $page, $pageSize);
- $count = $repository->countJournals($category);
+ $pageSize = Preferences::get('transactionPageSize', 50)->data;
+ $offset = ($page - 1) * $pageSize;
+ $set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end);
+ $count = $set->count();
+ $subSet = $set->splice($offset, $pageSize);
$subTitle = $category->name;
- $journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
+ $subTitleIcon = 'fa-bar-chart';
+ $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
$journals->setPath('categories/show/' . $category->id);
- // list of ranges for list of periods:
-
// oldest transaction in category:
- $start = $repository->getFirstActivityDate($category);
+ $start = $repository->firstUseDate($category, new Collection);
+ if ($start->year == 1900) {
+ $start = new Carbon;
+ }
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfX(new Carbon, $range);
@@ -179,58 +196,56 @@ class CategoryController extends Controller
$cache->addProperty('category-show');
$cache->addProperty($category->id);
- // get all spent and earned data:
- // get amount earned in period, grouped by day.
- $spentArray = $repository->spentPerDay($category, $start, $end);
- $earnedArray = $repository->earnedPerDay($category, $start, $end);
if ($cache->has()) {
$entries = $cache->get();
- } else {
- while ($end >= $start) {
- $end = Navigation::startOfPeriod($end, $range);
- $currentEnd = Navigation::endOfPeriod($end, $range);
-
- // get data from spentArray:
- $spent = $this->getSumOfRange($end, $currentEnd, $spentArray);
- $earned = $this->getSumOfRange($end, $currentEnd, $earnedArray);
- $dateStr = $end->format('Y-m-d');
- $dateName = Navigation::periodShow($end, $range);
- $entries->push([$dateStr, $dateName, $spent, $earned]);
-
- $end = Navigation::subtractPeriod($end, $range, 1);
-
- }
- $cache->store($entries);
+ return view('categories.show', compact('category', 'journals', 'entries', 'subTitleIcon', 'hideCategory', 'subTitle'));
}
+
+ $categoryCollection = new Collection([$category]);
+ $empty = new Collection;
+ while ($end >= $start) {
+ $end = Navigation::startOfPeriod($end, $range);
+ $currentEnd = Navigation::endOfPeriod($end, $range);
+ $spent = $repository->spentInPeriod($categoryCollection, $empty, $end, $currentEnd);
+ $earned = $repository->earnedInPeriod($categoryCollection, $empty, $end, $currentEnd);
+ $dateStr = $end->format('Y-m-d');
+ $dateName = Navigation::periodShow($end, $range);
+ $entries->push([$dateStr, $dateName, $spent, $earned]);
+
+ $end = Navigation::subtractPeriod($end, $range, 1);
+
+ }
+ $cache->store($entries);
+
return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle'));
}
/**
- * @param SCRI $repository
+ * @param CRI $repository
* @param Category $category
*
* @param $date
*
* @return \Illuminate\View\View
*/
- public function showWithDate(SCRI $repository, Category $category, string $date)
+ public function showWithDate(CRI $repository, Category $category, string $date)
{
- $carbon = new Carbon($date);
- $range = Preferences::get('viewRange', '1M')->data;
- $start = Navigation::startOfPeriod($carbon, $range);
- $end = Navigation::endOfPeriod($carbon, $range);
- $subTitle = $category->name;
-
+ $carbon = new Carbon($date);
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = Navigation::startOfPeriod($carbon, $range);
+ $end = Navigation::endOfPeriod($carbon, $range);
+ $subTitle = $category->name;
$hideCategory = true; // used in list.
$page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
-
- $set = $repository->getJournalsInRange($category, $start, $end, $page, $pageSize);
- $count = $repository->countJournalsInRange($category, $start, $end);
- $journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
+ $offset = ($page - 1) * $pageSize;
+ $set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end);
+ $count = $set->count();
+ $subSet = $set->splice($offset, $pageSize);
+ $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
$journals->setPath('categories/show/' . $category->id . '/' . $date);
return view('categories.show_with_date', compact('category', 'journals', 'hideCategory', 'subTitle', 'carbon'));
@@ -238,11 +253,11 @@ class CategoryController extends Controller
/**
* @param CategoryFormRequest $request
- * @param SCRI $repository
+ * @param CRI $repository
*
* @return \Illuminate\Http\RedirectResponse
*/
- public function store(CategoryFormRequest $request, SCRI $repository)
+ public function store(CategoryFormRequest $request, CRI $repository)
{
$categoryData = [
'name' => $request->input('name'),
@@ -265,12 +280,12 @@ class CategoryController extends Controller
/**
* @param CategoryFormRequest $request
- * @param SCRI $repository
+ * @param CRI $repository
* @param Category $category
*
* @return \Illuminate\Http\RedirectResponse
*/
- public function update(CategoryFormRequest $request, SCRI $repository, Category $category)
+ public function update(CategoryFormRequest $request, CRI $repository, Category $category)
{
$categoryData = [
'name' => $request->input('name'),
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index 951e9bda4f..10d90f4f31 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -1,18 +1,33 @@
generator = app('FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface');
- }
-
-
- /**
- * Shows the balances for a given set of dates and accounts.
- *
- * @param $reportType
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function report(string $reportType, Carbon $start, Carbon $end, Collection $accounts)
- {
- // chart properties for cache:
- $cache = new CacheProperties();
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty('all');
- $cache->addProperty('accounts');
- $cache->addProperty('default');
- $cache->addProperty($reportType);
- $cache->addProperty($accounts);
- if ($cache->has()) {
- return Response::json($cache->get());
- }
-
- // make chart:
- $data = $this->generator->frontpage($accounts, $start, $end);
- $cache->store($data);
-
- return Response::json($data);
+ $this->generator = app(AccountChartGeneratorInterface::class);
}
/**
* Shows the balances for all the user's expense accounts.
*
- * @param ARI $repository
+ * @param AccountCrudInterface $crud
*
- * @return \Symfony\Component\HttpFoundation\Response
+ * @return \Illuminate\Http\JsonResponse
*/
- public function expenseAccounts(ARI $repository)
+ public function expenseAccounts(AccountCrudInterface $crud)
{
- $start = clone session('start', Carbon::now()->startOfMonth());
- $end = clone session('end', Carbon::now()->endOfMonth());
- $accounts = $repository->getAccounts(['Expense account', 'Beneficiary account']);
-
- // chart properties for cache:
- $cache = new CacheProperties();
+ $start = clone session('start', Carbon::now()->startOfMonth());
+ $end = clone session('end', Carbon::now()->endOfMonth());
+ $cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expenseAccounts');
@@ -88,30 +67,51 @@ class AccountController extends Controller
if ($cache->has()) {
return Response::json($cache->get());
}
+ $accounts = $crud->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
+
+ $start->subDay();
+ $ids = $accounts->pluck('id')->toArray();
+ $startBalances = Steam::balancesById($ids, $start);
+ $endBalances = Steam::balancesById($ids, $end);
+
+ $accounts->each(
+ function (Account $account) use ($startBalances, $endBalances) {
+ $id = $account->id;
+ $startBalance = $startBalances[$id] ?? '0';
+ $endBalance = $endBalances[$id] ?? '0';
+ $diff = bcsub($endBalance, $startBalance);
+ $account->difference = round($diff, 2);
+ }
+ );
+
+
+ $accounts = $accounts->sortByDesc(
+ function (Account $account) {
+ return $account->difference;
+ }
+ );
$data = $this->generator->expenseAccounts($accounts, $start, $end);
$cache->store($data);
return Response::json($data);
-
}
/**
* Shows the balances for all the user's frontpage accounts.
*
- * @param ARI $repository
+ * @param AccountCrudInterface $crud
*
- * @return \Symfony\Component\HttpFoundation\Response
+ * @return \Illuminate\Http\JsonResponse
*/
- public function frontpage(ARI $repository)
+ public function frontpage(AccountCrudInterface $crud)
{
- $frontPage = Preferences::get('frontPageAccounts', []);
- $start = clone session('start', Carbon::now()->startOfMonth());
- $end = clone session('end', Carbon::now()->endOfMonth());
- $accounts = $repository->getFrontpageAccounts($frontPage);
+ $start = clone session('start', Carbon::now()->startOfMonth());
+ $end = clone session('end', Carbon::now()->endOfMonth());
+
// chart properties for cache:
- $cache = new CacheProperties();
+ $cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('frontpage');
@@ -120,11 +120,72 @@ class AccountController extends Controller
return Response::json($cache->get());
}
+ $frontPage = Preferences::get('frontPageAccounts', $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray());
+ $accounts = $crud->getAccountsById($frontPage->data);
+
+ foreach ($accounts as $account) {
+ $balances = [];
+ $current = clone $start;
+ $range = Steam::balanceInRange($account, $start, clone $end);
+ $previous = round(array_values($range)[0], 2);
+ while ($current <= $end) {
+ $format = $current->format('Y-m-d');
+ $balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
+ $previous = $balance;
+ $balances[] = $balance;
+ $current->addDay();
+ }
+ $account->balances = $balances;
+ }
$data = $this->generator->frontpage($accounts, $start, $end);
$cache->store($data);
return Response::json($data);
+ }
+ /**
+ * Shows the balances for a given set of dates and accounts.
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param Collection $accounts
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function report(Carbon $start, Carbon $end, Collection $accounts)
+ {
+ // chart properties for cache:
+ $cache = new CacheProperties();
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('all');
+ $cache->addProperty('accounts');
+ $cache->addProperty('default');
+ $cache->addProperty($accounts);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+
+ foreach ($accounts as $account) {
+ $balances = [];
+ $current = clone $start;
+ $range = Steam::balanceInRange($account, $start, clone $end);
+ $previous = round(array_values($range)[0], 2);
+ while ($current <= $end) {
+ $format = $current->format('Y-m-d');
+ $balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
+ $previous = $balance;
+ $balances[] = $balance;
+ $current->addDay();
+ }
+ $account->balances = $balances;
+ }
+
+ // make chart:
+ $data = $this->generator->frontpage($accounts, $start, $end);
+ $cache->store($data);
+
+ return Response::json($data);
}
/**
@@ -136,10 +197,8 @@ class AccountController extends Controller
*/
public function single(Account $account)
{
-
-
- $start = session('start', Carbon::now()->startOfMonth());
- $end = session('end', Carbon::now()->endOfMonth());
+ $start = clone session('start', Carbon::now()->startOfMonth());
+ $end = clone session('end', Carbon::now()->endOfMonth());
// chart properties for cache:
$cache = new CacheProperties();
@@ -152,9 +211,81 @@ class AccountController extends Controller
return Response::json($cache->get());
}
- $data = $this->generator->single($account, $start, $end);
+ $format = (string)trans('config.month_and_day');
+ $range = Steam::balanceInRange($account, $start, $end);
+ $current = clone $start;
+ $previous = array_values($range)[0];
+ $labels = [];
+ $chartData = [];
+
+ while ($end >= $current) {
+ $theDate = $current->format('Y-m-d');
+ $balance = $range[$theDate] ?? $previous;
+
+ $labels[] = $current->formatLocalized($format);
+ $chartData[] = $balance;
+ $previous = $balance;
+ $current->addDay();
+ }
+
+
+ $data = $this->generator->single($account, $labels, $chartData);
$cache->store($data);
return Response::json($data);
}
+
+
+ /**
+ * @param Account $account
+ * @param string $date
+ *
+ * @return \Illuminate\Http\JsonResponse
+ * @throws FireflyException
+ */
+ public function specificPeriod(Account $account, string $date)
+ {
+ try {
+ $start = new Carbon($date);
+ } catch (Exception $e) {
+ Log::error($e->getMessage());
+ throw new FireflyException('"' . e($date) . '" does not seem to be a valid date. Should be in the format YYYY-MM-DD');
+ }
+ $range = Preferences::get('viewRange', '1M')->data;
+ $end = Navigation::endOfPeriod($start, $range);
+ // chart properties for cache:
+ $cache = new CacheProperties();
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('frontpage');
+ $cache->addProperty('specificPeriod');
+ $cache->addProperty($account->id);
+ if ($cache->has()) {
+ return Response::json($cache->get());
+ }
+
+ $format = (string)trans('config.month_and_day');
+ $range = Steam::balanceInRange($account, $start, $end);
+ $current = clone $start;
+ $previous = array_values($range)[0];
+ $labels = [];
+ $chartData = [];
+
+ while ($end >= $current) {
+ $theDate = $current->format('Y-m-d');
+ $balance = $range[$theDate] ?? $previous;
+
+ $labels[] = $current->formatLocalized($format);
+ $chartData[] = $balance;
+ $previous = $balance;
+ $current->addDay();
+ }
+
+
+ $data = $this->generator->single($account, $labels, $chartData);
+ $cache->store($data);
+
+ return Response::json($data);
+ }
+
}
diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php
index f38af543d2..9a30caa682 100644
--- a/app/Http/Controllers/Chart/BillController.php
+++ b/app/Http/Controllers/Chart/BillController.php
@@ -1,9 +1,18 @@
generator = app('FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface');
+ $this->generator = app(BillChartGeneratorInterface::class);
}
/**
@@ -41,24 +50,11 @@ class BillController extends Controller
*/
public function frontpage(BillRepositoryInterface $repository)
{
- $start = session('start', Carbon::now()->startOfMonth());
- $end = session('end', Carbon::now()->endOfMonth());
- $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
- $unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
- $creditCardDue = $repository->getCreditCardBill($start, $end);
-
- if ($creditCardDue < 0) {
- // expenses are negative (bill not yet paid),
- $creditCardDue = bcmul($creditCardDue, '-1');
- $unpaid = bcadd($unpaid, $creditCardDue);
- } else {
- // if more than zero, the bill has been paid: (transfer = positive).
- // amount must be negative to be added to $paid:
- $paid = bcadd($paid, $creditCardDue);
- }
-
- // build chart:
- $data = $this->generator->frontpage($paid, $unpaid);
+ $start = session('start', Carbon::now()->startOfMonth());
+ $end = session('end', Carbon::now()->endOfMonth());
+ $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
+ $unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
+ $data = $this->generator->frontpage($paid, $unpaid);
return Response::json($data);
}
diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php
index b7025dd279..60cc1931e6 100644
--- a/app/Http/Controllers/Chart/BudgetController.php
+++ b/app/Http/Controllers/Chart/BudgetController.php
@@ -1,13 +1,22 @@
generator = app('FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface');
+ $this->generator = app(BudgetChartGeneratorInterface::class);
}
/**
+ * checked
+ *
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
*
@@ -44,42 +55,39 @@ class BudgetController extends Controller
*/
public function budget(BudgetRepositoryInterface $repository, Budget $budget)
{
-
- // dates and times
- $first = $repository->getFirstBudgetLimitDate($budget);
+ $first = $repository->firstUseDate($budget);
$range = Preferences::get('viewRange', '1M')->data;
$last = session('end', new Carbon);
- // chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($first);
$cache->addProperty($last);
$cache->addProperty('budget');
- if ($cache->has()) {
+ if ($cache->has()) {
return Response::json($cache->get());
}
$final = clone $last;
$final->addYears(2);
- $last = Navigation::endOfX($last, $range, $final);
- $entries = new Collection;
- // get all expenses:
- $spentArray = $repository->spentPerDay($budget, $first, $last);
+ $budgetCollection = new Collection([$budget]);
+ $last = Navigation::endOfX($last, $range, $final); // not to overshoot.
+ $entries = new Collection;
while ($first < $last) {
// periodspecific dates:
$currentStart = Navigation::startOfPeriod($first, $range);
$currentEnd = Navigation::endOfPeriod($first, $range);
- $spent = $this->getSumOfRange($currentStart, $currentEnd, $spentArray);
- $entry = [$first, ($spent * -1)];
-
+ // sub another day because reasons.
+ $currentEnd->subDay();
+ $spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
+ $entry = [$first, ($spent * -1)];
$entries->push($entry);
$first = Navigation::addPeriod($first, $range, 0);
}
- $data = $this->generator->budget($entries);
+ $data = $this->generator->budgetLimit($entries, 'month');
$cache->store($data);
return Response::json($data);
@@ -98,46 +106,31 @@ class BudgetController extends Controller
{
$start = clone $repetition->startdate;
$end = $repetition->enddate;
-
- // chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty('budget');
- $cache->addProperty('limit');
+ $cache->addProperty('budget-limit');
$cache->addProperty($budget->id);
$cache->addProperty($repetition->id);
+
if ($cache->has()) {
return Response::json($cache->get());
}
- $set = $repository->getExpensesPerDay($budget, $start, $end);
- $entries = new Collection;
- $amount = $repetition->amount;
-
- // get sum (har har)!
+ $entries = new Collection;
+ $amount = $repetition->amount;
+ $budgetCollection = new Collection([$budget]);
while ($start <= $end) {
- $formatted = $start->format('Y-m-d');
- $filtered = $set->filter(
- function (Budget $obj) use ($formatted) {
- return $obj->date == $formatted;
- }
- );
- $sum = is_null($filtered->first()) ? '0' : $filtered->first()->dailyAmount;
+ $spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start);
+ $amount = bcadd($amount, $spent);
+ $entries->push([clone $start, round($amount, 2)]);
- /*
- * Sum of expenses on this day:
- */
- $amount = round(bcadd(strval($amount), $sum), 2);
- $entries->push([clone $start, $amount]);
$start->addDay();
}
-
- $data = $this->generator->budgetLimit($entries);
+ $data = $this->generator->budgetLimit($entries, 'month_and_day');
$cache->store($data);
return Response::json($data);
-
}
/**
@@ -145,15 +138,12 @@ class BudgetController extends Controller
*
* @param BudgetRepositoryInterface $repository
*
- * @param ARI $accountRepository
- *
* @return \Symfony\Component\HttpFoundation\Response
*/
- public function frontpage(BudgetRepositoryInterface $repository, ARI $accountRepository)
+ public function frontpage(BudgetRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
-
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
@@ -163,42 +153,54 @@ class BudgetController extends Controller
if ($cache->has()) {
return Response::json($cache->get());
}
-
- $budgets = $repository->getBudgetsAndLimitsInRange($start, $end);
- $allEntries = new Collection;
- $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
-
+ $budgets = $repository->getActiveBudgets();
+ $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
+ $allEntries = new Collection;
+ $format = strval(trans('config.month_and_day'));
/** @var Budget $budget */
foreach ($budgets as $budget) {
- // we already have amount, startdate and enddate.
- // if this "is" a limit repetition (as opposed to a budget without one entirely)
- // depends on whether startdate and enddate are null.
+ // get relevant repetitions:
$name = $budget->name;
- if (is_null($budget->startdate) && is_null($budget->enddate)) {
- $currentStart = clone $start;
- $currentEnd = clone $end;
- $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts);
- $amount = '0';
- $left = '0';
- $spent = $expenses;
- $overspent = '0';
- } else {
- $currentStart = clone $budget->startdate;
- $currentEnd = clone $budget->enddate;
- $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts);
- $amount = $budget->amount;
- // smaller than 1 means spent MORE than budget allows.
- $left = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? '0' : bcadd($budget->amount, $expenses);
- $spent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses;
- $overspent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcadd($budget->amount, $expenses) : '0';
+ $reps = $repetitions->filter(
+ function (LimitRepetition $repetition) use ($budget, $start, $end) {
+ if ($repetition->startdate < $end && $repetition->enddate > $start && $repetition->budget_id === $budget->id) {
+ return $repetition;
+ }
+ }
+ );
+ if ($reps->count() === 0) {
+ $amount = '0';
+ $left = '0';
+ $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
+ $overspent = '0';
+ $allEntries->push([$name, $left, $spent, $overspent, $amount, $spent]);
+ }
+ /** @var LimitRepetition $repetition */
+ foreach ($reps as $repetition) {
+ $expenses = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate);
+ if ($reps->count() > 1) {
+ $name = $budget->name . ' ' . trans(
+ 'firefly.between_dates',
+ ['start' => $repetition->startdate->formatLocalized($format), 'end' => $repetition->enddate->formatLocalized($format)]
+ );
+ }
+ $amount = $repetition->amount;
+ $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses);
+ $spent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses;
+ $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';
+ $allEntries->push([$name, $left, $spent, $overspent, $amount, $spent]);
}
- $allEntries->push([$name, $left, $spent, $overspent, $amount, $expenses]);
}
- $noBudgetExpenses = $repository->getWithoutBudgetSum($accounts, $start, $end);
- $allEntries->push([trans('firefly.no_budget'), '0', '0', $noBudgetExpenses, '0', '0']);
+ $list = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end);
+ $sum = '0';
+ /** @var TransactionJournal $entry */
+ foreach ($list as $entry) {
+ $sum = bcadd(TransactionJournal::amount($entry), $sum);
+ }
+ $allEntries->push([trans('firefly.no_budget'), '0', '0', $sum, '0', '0']);
$data = $this->generator->frontpage($allEntries);
$cache->store($data);
@@ -208,21 +210,18 @@ class BudgetController extends Controller
/**
*
* @param BudgetRepositoryInterface $repository
- * @param $reportType
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Collection $budgets
*
- * @SuppressWarnings(PHPMD.ExcessiveParameterList) // need all parameters
*
* @return \Illuminate\Http\JsonResponse
*/
- public function multiYear(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets)
+ public function multiYear(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets)
{
- // chart properties for cache:
+
$cache = new CacheProperties();
- $cache->addProperty($reportType);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
@@ -232,119 +231,106 @@ class BudgetController extends Controller
if ($cache->has()) {
return Response::json($cache->get());
}
-
- /*
- * Get the budgeted amounts for each budgets in each year.
- */
- $budgetedSet = $repository->getBudgetedPerYear($budgets, $start, $end);
- $budgetedArray = [];
- /** @var Budget $entry */
- foreach ($budgetedSet as $entry) {
- $budgetedArray[$entry->id][$entry->dateFormatted] = $entry->budgeted;
+ $budgetIds = $budgets->pluck('id')->toArray();
+ $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
+ $budgeted = [];
+ $entries = new Collection;
+ // filter budgets once:
+ $repetitions = $repetitions->filter(
+ function (LimitRepetition $repetition) use ($budgetIds) {
+ if (in_array(strval($repetition->budget_id), $budgetIds)) {
+ return $repetition;
+ }
+ }
+ );
+ /** @var LimitRepetition $repetition */
+ foreach ($repetitions as $repetition) {
+ $year = $repetition->startdate->year;
+ if (isset($budgeted[$repetition->budget_id][$year])) {
+ $budgeted[$repetition->budget_id][$year] = bcadd($budgeted[$repetition->budget_id][$year], $repetition->amount);
+ continue;
+ }
+ $budgeted[$repetition->budget_id][$year] = $repetition->amount;
}
- $set = $repository->getBudgetsAndExpensesPerYear($budgets, $accounts, $start, $end);
- $entries = new Collection;
- // go by budget, not by year.
- /** @var Budget $budget */
foreach ($budgets as $budget) {
- $entry = ['name' => '', 'spent' => [], 'budgeted' => []];
- $id = $budget->id;
$currentStart = clone $start;
+ $entry = ['name' => $budget->name, 'spent' => [], 'budgeted' => []];
while ($currentStart < $end) {
// fix the date:
$currentEnd = clone $currentStart;
+ $year = $currentStart->year;
$currentEnd->endOfYear();
- // basic information:
- $year = $currentStart->year;
- $entry['name'] = $budget->name ?? (string)trans('firefly.no_budget');
- $spent = 0;
- // this might be a good moment to collect no budget stuff.
- if (is_null($budget->id)) {
- // get without budget sum in range:
- $spent = $repository->getWithoutBudgetSum($accounts, $currentStart, $currentEnd) * -1;
- } else {
- if (isset($set[$id]['entries'][$year])) {
- $spent = $set[$id]['entries'][$year] * -1;
- }
- }
-
- $budgeted = $budgetedArray[$id][$year] ?? '0';
- $entry['spent'][$year] = $spent;
- $entry['budgeted'][$year] = round($budgeted, 2);
-
+ $spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd);
// jump to next year.
$currentStart = clone $currentEnd;
$currentStart->addDay();
+
+ $entry['spent'][$year] = round($spent * -1, 2);
+ $entry['budgeted'][$year] = isset($budgeted[$budget->id][$year]) ? round($budgeted[$budget->id][$year], 2) : 0;
}
$entries->push($entry);
}
- // generate chart with data:
$data = $this->generator->multiYear($entries);
$cache->store($data);
return Response::json($data);
-
}
/**
- *
* @param BudgetRepositoryInterface $repository
- * @param $reportType
+ * @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
- public function year(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
+ public function period(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
- $cache->addProperty($reportType);
$cache->addProperty($accounts);
+ $cache->addProperty($budget->id);
$cache->addProperty('budget');
- $cache->addProperty('year');
+ $cache->addProperty('period');
if ($cache->has()) {
return Response::json($cache->get());
}
+ // loop over period, add by users range:
+ $current = clone $start;
+ $viewRange = Preferences::get('viewRange', '1M')->data;
+ $set = new Collection;
+ $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
- $budgetInformation = $repository->getBudgetsAndExpensesPerMonth($accounts, $start, $end);
- $budgets = new Collection;
- $entries = new Collection;
- /** @var array $row */
- foreach ($budgetInformation as $row) {
- $budgets->push($row['budget']);
- }
- while ($start < $end) {
- // month is the current end of the period:
- $month = clone $start;
- $month->endOfMonth();
- $row = [clone $start];
- $dateFormatted = $start->format('Y-m');
-
- // each budget, check if there is an entry for this month:
- /** @var array $row */
- foreach ($budgetInformation as $budgetRow) {
- $spent = 0; // nothing spent.
- if (isset($budgetRow['entries'][$dateFormatted])) {
- $spent = $budgetRow['entries'][$dateFormatted] * -1; // to fit array
+ while ($current < $end) {
+ $currentStart = clone $current;
+ $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange);
+ $reps = $repetitions->filter(
+ function (LimitRepetition $repetition) use ($budget, $currentStart) {
+ if ($repetition->budget_id === $budget->id && $repetition->startdate == $currentStart) {
+ return $repetition;
+ }
}
- $row[] = $spent;
- }
+ );
+ $budgeted = $reps->sum('amount');
+ $spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd);
+ $entry = [
+ 'date' => clone $currentStart,
+ 'budgeted' => $budgeted,
+ 'spent' => $spent,
+ ];
+ $set->push($entry);
+ $currentEnd->addDay();
+ $current = clone $currentEnd;
- // add "no budget" thing.
- $row[] = round(bcmul($repository->getWithoutBudgetSum($accounts, $start, $month), '-1'), 4);
-
- $entries->push($row);
- $start->endOfMonth()->addDay();
}
- $data = $this->generator->year($budgets, $entries);
+ $data = $this->generator->period($set, $viewRange);
$cache->store($data);
return Response::json($data);
diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php
index 3eb80cf958..9d1d579bce 100644
--- a/app/Http/Controllers/Chart/CategoryController.php
+++ b/app/Http/Controllers/Chart/CategoryController.php
@@ -1,15 +1,22 @@
generator = app('FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface');
+ $this->generator = app(CategoryChartGeneratorInterface::class);
}
/**
* Show an overview for a category for all time, per month/week/year.
*
- * @param SCRI $repository
+ * @param CRI $repository
* @param Category $category
*
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- *
* @return \Symfony\Component\HttpFoundation\Response
*/
- public function all(SCRI $repository, Category $category)
+ public function all(CRI $repository, Category $category)
{
- // oldest transaction in category:
- $start = $repository->getFirstActivityDate($category);
- $range = Preferences::get('viewRange', '1M')->data;
- $start = Navigation::startOfPeriod($start, $range);
- $end = new Carbon;
- $entries = new Collection;
- // chart properties for cache:
- $cache = new CacheProperties();
+ $start = $repository->firstUseDate($category, new Collection);
+ $range = Preferences::get('viewRange', '1M')->data;
+ $start = Navigation::startOfPeriod($start, $range);
+ $categoryCollection = new Collection([$category]);
+ $end = new Carbon;
+ $entries = new Collection;
+ $cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('all');
@@ -69,13 +69,11 @@ class CategoryController extends Controller
if ($cache->has()) {
return Response::json($cache->get());
}
- $spentArray = $repository->spentPerDay($category, $start, $end);
- $earnedArray = $repository->earnedPerDay($category, $start, $end);
while ($start <= $end) {
$currentEnd = Navigation::endOfPeriod($start, $range);
- $spent = $this->getSumOfRange($start, $currentEnd, $spentArray);
- $earned = $this->getSumOfRange($start, $currentEnd, $earnedArray);
+ $spent = $repository->spentInPeriod($categoryCollection, new Collection, $start, $currentEnd);
+ $earned = $repository->earnedInPeriod($categoryCollection, new Collection, $start, $currentEnd);
$date = Navigation::periodShow($start, $range);
$entries->push([clone $start, $date, $spent, $earned]);
$start = Navigation::addPeriod($start, $range, 0);
@@ -87,15 +85,16 @@ class CategoryController extends Controller
$cache->store($data);
return Response::json($data);
+
}
/**
- * @param SCRI $repository
+ * @param CRI $repository
* @param Category $category
*
* @return \Symfony\Component\HttpFoundation\Response
*/
- public function currentPeriod(SCRI $repository, Category $category)
+ public function currentPeriod(CRI $repository, Category $category)
{
$start = clone session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
@@ -104,64 +103,17 @@ class CategoryController extends Controller
return Response::json($data);
}
- /**
- * Returns a chart of what has been earned in this period in each category
- * grouped by month.
- *
- * @param CRI $repository
- * @param $reportType
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
- *
- * @SuppressWarnings(PHPMD.ExcessiveParameterList) // cant avoid it.
- * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // it's long but ok.
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function earnedInPeriod(CRI $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
- {
- $cache = new CacheProperties; // chart properties for cache:
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty($reportType);
- $cache->addProperty($accounts);
- $cache->addProperty('category');
- $cache->addProperty('earned-in-period');
- if ($cache->has()) {
- return Response::json($cache->get());
- }
-
- $set = $repository->earnedForAccountsPerMonth($accounts, $start, $end);
- $categories = $set->unique('id')->sortBy(
- function (Category $category) {
- return $category->name;
- }
- );
- $entries = $this->filterCollection($start, $end, $set, $categories);
- $data = $this->generator->earnedInPeriod($categories, $entries);
- $cache->store($data);
-
- return $data;
-
- }
-
/**
* Show this month's category overview.
*
* @param CRI $repository
*
- * @param ARI $accountRepository
- *
* @return \Symfony\Component\HttpFoundation\Response
*/
- public function frontpage(CRI $repository, ARI $accountRepository)
+ public function frontpage(CRI $repository)
{
-
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
-
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
@@ -171,16 +123,20 @@ class CategoryController extends Controller
if ($cache->has()) {
return Response::json($cache->get());
}
-
- // get data for categories (and "no category"):
- $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
- $set = $repository->spentForAccountsPerMonth($accounts, $start, $end);
- $outside = $repository->sumSpentNoCategory($accounts, $start, $end);
-
+ $categories = $repository->getCategories();
+ $set = new Collection;
+ /** @var Category $category */
+ foreach ($categories as $category) {
+ $spent = $repository->spentInPeriod(new Collection([$category]), new Collection, $start, $end);
+ if (bccomp($spent, '0') === -1) {
+ $category->spent = $spent;
+ $set->push($category);
+ }
+ }
// this is a "fake" entry for the "no category" entry.
- $entry = new stdClass();
+ $entry = new stdClass;
$entry->name = trans('firefly.no_category');
- $entry->spent = $outside;
+ $entry->spent = $repository->spentInPeriodWithoutCategory(new Collection, $start, $end);
$set->push($entry);
$set = $set->sortBy('spent');
@@ -192,22 +148,21 @@ class CategoryController extends Controller
}
/**
- * @param $reportType
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
- * @param Collection $categories
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param Collection $accounts
+ * @param Collection $categories
*
* @return \Illuminate\Http\JsonResponse
*/
- public function multiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $categories)
+ public function multiYear(Carbon $start, Carbon $end, Collection $accounts, Collection $categories)
{
+
/** @var CRI $repository */
- $repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface');
+ $repository = app(CRI::class);
// chart properties for cache:
$cache = new CacheProperties();
- $cache->addProperty($reportType);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
@@ -219,7 +174,6 @@ class CategoryController extends Controller
}
$entries = new Collection;
- $set = $repository->listMultiYear($categories, $accounts, $start, $end);
/** @var Category $category */
foreach ($categories as $category) {
@@ -232,34 +186,22 @@ class CategoryController extends Controller
$currentEnd = clone $currentStart;
$currentEnd->endOfYear();
-
// get data:
if (is_null($category->id)) {
- $name = trans('firefly.noCategory');
- $spent = $repository->sumSpentNoCategory($accounts, $currentStart, $currentEnd);
- $earned = $repository->sumEarnedNoCategory($accounts, $currentStart, $currentEnd);
- } else {
- // get from set:
- $entrySpent = $set->filter(
- function (Category $cat) use ($year, $category) {
- return ($cat->type == 'Withdrawal' && $cat->dateFormatted == $year && $cat->id == $category->id);
- }
- )->first();
- $entryEarned = $set->filter(
- function (Category $cat) use ($year, $category) {
- return ($cat->type == 'Deposit' && $cat->dateFormatted == $year && $cat->id == $category->id);
- }
- )->first();
+ $entry['name'] = trans('firefly.noCategory');
+ $entry['spent'][$year] = ($repository->spentInPeriodWithoutCategory($accounts, $currentStart, $currentEnd) * -1);
+ $entry['earned'][$year] = $repository->earnedInPeriodWithoutCategory($accounts, $currentStart, $currentEnd);
+
+ // jump to next year.
+ $currentStart = clone $currentEnd;
+ $currentStart->addDay();
+ continue;
- $name = $category->name;
- $spent = !is_null($entrySpent) ? $entrySpent->sum : 0;
- $earned = !is_null($entryEarned) ? $entryEarned->sum : 0;
}
-
- // save to array:
- $entry['name'] = $name;
- $entry['spent'][$year] = ($spent * -1);
- $entry['earned'][$year] = $earned;
+ // alternative is a normal category:
+ $entry['name'] = $category->name;
+ $entry['spent'][$year] = ($repository->spentInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd) * -1);
+ $entry['earned'][$year] = $repository->earnedInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd);
// jump to next year.
$currentStart = clone $currentEnd;
@@ -267,23 +209,25 @@ class CategoryController extends Controller
}
$entries->push($entry);
}
+
// generate chart with data:
$data = $this->generator->multiYear($entries);
$cache->store($data);
return Response::json($data);
+
}
/**
- * @param SCRI $repository
+ * @param CRI $repository
* @param Category $category
*
* @param $date
*
* @return \Symfony\Component\HttpFoundation\Response
*/
- public function specificPeriod(SCRI $repository, Category $category, $date)
+ public function specificPeriod(CRI $repository, Category $category, $date)
{
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data;
@@ -292,145 +236,32 @@ class CategoryController extends Controller
$data = $this->makePeriodChart($repository, $category, $start, $end);
return Response::json($data);
-
-
}
/**
- * Returns a chart of what has been spent in this period in each category
- * grouped by month.
- *
- * @param CRI $repository
- * @param $reportType
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
- *
- * @SuppressWarnings(PHPMD.ExcessiveParameterList) // need all parameters
- * @SuppressWarnings(PHPMD.ExcessuveMethodLength) // need the length
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function spentInPeriod(CRI $repository, $reportType, Carbon $start, Carbon $end, Collection $accounts)
- {
- $cache = new CacheProperties; // chart properties for cache:
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty($reportType);
- $cache->addProperty($accounts);
- $cache->addProperty('category');
- $cache->addProperty('spent-in-period');
- if ($cache->has()) {
- return Response::json($cache->get());
- }
-
-
- $set = $repository->spentForAccountsPerMonth($accounts, $start, $end);
- $categories = $set->unique('id')->sortBy(
- function (Category $category) {
- return $category->name;
- }
- );
- $entries = $this->filterCollection($start, $end, $set, $categories);
- $entries = $this->invertSelection($entries);
- $data = $this->generator->spentInPeriod($categories, $entries);
- $cache->store($data);
-
- return $data;
- }
-
- /**
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $set
- * @param Collection $categories
- *
- * @return Collection
- */
- private function filterCollection(Carbon $start, Carbon $end, Collection $set, Collection $categories): Collection
- {
- $entries = new Collection;
-
- while ($start < $end) { // filter the set:
- $row = [clone $start];
- $currentSet = $set->filter( // get possibly relevant entries from the big $set
- function (Category $category) use ($start) {
- return $category->dateFormatted == $start->format('Y-m');
- }
- );
- /** @var Category $category */
- foreach ($categories as $category) { // check for each category if its in the current set.
- $entry = $currentSet->filter( // if its in there, use the value.
- function (Category $cat) use ($category) {
- return ($cat->id == $category->id);
- }
- )->first();
- if (!is_null($entry)) {
- $row[] = $entry->earned ? round($entry->earned, 2) : round($entry->spent, 2);
- } else {
- $row[] = 0;
- }
- }
- $entries->push($row);
- $start->addMonth();
- }
-
- return $entries;
- }
-
- /**
- * Not the most elegant solution but it works.
- *
- * @param Collection $entries
- *
- * @return Collection
- */
- private function invertSelection(Collection $entries): Collection
- {
- $result = new Collection;
- foreach ($entries as $entry) {
- $new = [$entry[0]];
- $count = count($entry);
- for ($i = 1; $i < $count; $i++) {
- $new[$i] = ($entry[$i] * -1);
- }
- $result->push($new);
- }
-
- return $result;
-
- }
-
- /**
- * @param SCRI $repository
+ * @param CRI $repository
* @param Category $category
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
- private function makePeriodChart(SCRI $repository, Category $category, Carbon $start, Carbon $end)
+ private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end)
{
- // chart properties for cache:
- $cache = new CacheProperties;
+ $categoryCollection = new Collection([$category]);
+ $cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($category->id);
$cache->addProperty('specific-period');
+
if ($cache->has()) {
- return $cache->get(); //
+ return $cache->get();
}
$entries = new Collection;
-
- // get amount earned in period, grouped by day.
- // get amount spent in period, grouped by day.
- $spentArray = $repository->spentPerDay($category, $start, $end);
- $earnedArray = $repository->earnedPerDay($category, $start, $end);
-
while ($start <= $end) {
- $str = $start->format('Y-m-d');
- $spent = $spentArray[$str] ?? '0';
- $earned = $earnedArray[$str] ?? '0';
+ $spent = $repository->spentInPeriod($categoryCollection, new Collection, $start, $start);
+ $earned = $repository->earnedInPeriod($categoryCollection, new Collection, $start, $start);
$date = Navigation::periodShow($start, '1D');
$entries->push([clone $start, $date, $spent, $earned]);
$start->addDay();
@@ -440,6 +271,7 @@ class CategoryController extends Controller
$cache->store($data);
return $data;
+
}
}
diff --git a/app/Http/Controllers/Chart/PiggyBankController.php b/app/Http/Controllers/Chart/PiggyBankController.php
index 1eab1366f0..c80f2ba6ae 100644
--- a/app/Http/Controllers/Chart/PiggyBankController.php
+++ b/app/Http/Controllers/Chart/PiggyBankController.php
@@ -1,10 +1,20 @@
generator = app('FireflyIII\Generator\Chart\PiggyBank\PiggyBankChartGeneratorInterface');
+ $this->generator = app(PiggyBankChartGeneratorInterface::class);
}
/**
@@ -50,8 +60,20 @@ class PiggyBankController extends Controller
return Response::json($cache->get());
}
- $set = $repository->getEventSummarySet($piggyBank);
- $data = $this->generator->history($set);
+ $set = $repository->getEvents($piggyBank);
+ $set = $set->reverse();
+ $collection = [];
+ /** @var PiggyBankEvent $entry */
+ foreach ($set as $entry) {
+ $date = $entry->date->format('Y-m-d');
+ $amount = $entry->amount;
+ if (isset($collection[$date])) {
+ $amount = bcadd($amount, $collection[$date]);
+ }
+ $collection[$date] = $amount;
+ }
+
+ $data = $this->generator->history(new Collection($collection));
$cache->store($data);
return Response::json($data);
diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php
index 8bcff1ec36..3824bfccea 100644
--- a/app/Http/Controllers/Chart/ReportController.php
+++ b/app/Http/Controllers/Chart/ReportController.php
@@ -1,14 +1,24 @@
generator = app('FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface');
+ $this->generator = app(ReportChartGeneratorInterface::class);
}
/**
@@ -80,19 +90,15 @@ class ReportController extends Controller
/**
- * Summarizes all income and expenses, per month, for a given year.
- *
- * @param ReportQueryInterface $query
- * @param $reportType
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
- *
- * @SuppressWarnings(PHPMD.ExcessiveParameterList) // cant avoid it.
+ * @param AccountRepositoryInterface $repository
+ * @param string $reportType
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
- public function yearInOut(ReportQueryInterface $query, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
+ public function yearInOut(AccountRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
@@ -105,37 +111,47 @@ class ReportController extends Controller
return Response::json($cache->get());
}
- // spent per month, and earned per month. For a specific set of accounts
- // grouped by month
- $spentArray = $query->spentPerMonth($accounts, $start, $end);
- $earnedArray = $query->earnedPerMonth($accounts, $start, $end);
+ // always per month.
+ $currentStart = clone $start;
+ $spentArray = [];
+ $earnedArray = [];
+ while ($currentStart <= $end) {
+ $currentEnd = Navigation::endOfPeriod($currentStart, '1M');
+ $date = $currentStart->format('Y-m');
+ $spent = $repository->spentInPeriod($accounts, $currentStart, $currentEnd);
+ $earned = $repository->earnedInPeriod($accounts, $currentStart, $currentEnd);
+ $spentArray[$date] = bcmul($spent, '-1');
+ $earnedArray[$date] = $earned;
+ $currentStart = Navigation::addPeriod($currentStart, '1M', 0);
+ }
if ($start->diffInMonths($end) > 12) {
// data = method X
$data = $this->multiYearInOut($earnedArray, $spentArray, $start, $end);
- } else {
- // data = method Y
- $data = $this->singleYearInOut($earnedArray, $spentArray, $start, $end);
+ $cache->store($data);
+
+ return Response::json($data);
}
+ // data = method Y
+ $data = $this->singleYearInOut($earnedArray, $spentArray, $start, $end);
$cache->store($data);
return Response::json($data);
+
}
/**
- * Summarizes all income and expenses for a given year. Gives a total and an average.
- *
- * @param ReportQueryInterface $query
- * @param $reportType
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
+ * @param AccountRepositoryInterface $repository
+ * @param string $reportType
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
- public function yearInOutSummarized(ReportQueryInterface $query, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
+ public function yearInOutSummarized(AccountRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
@@ -148,20 +164,34 @@ class ReportController extends Controller
if ($cache->has()) {
return Response::json($cache->get());
}
- // spent per month, and earned per month. For a specific set of accounts
- // grouped by month
- $spentArray = $query->spentPerMonth($accounts, $start, $end);
- $earnedArray = $query->earnedPerMonth($accounts, $start, $end);
+
+ // always per month.
+ $currentStart = clone $start;
+ $spentArray = [];
+ $earnedArray = [];
+ while ($currentStart <= $end) {
+ $currentEnd = Navigation::endOfPeriod($currentStart, '1M');
+ $date = $currentStart->format('Y-m');
+ $spent = $repository->spentInPeriod($accounts, $currentStart, $currentEnd);
+ $earned = $repository->earnedInPeriod($accounts, $currentStart, $currentEnd);
+ $spentArray[$date] = bcmul($spent, '-1');
+ $earnedArray[$date] = $earned;
+ $currentStart = Navigation::addPeriod($currentStart, '1M', 0);
+ }
+
if ($start->diffInMonths($end) > 12) {
// per year
$data = $this->multiYearInOutSummarized($earnedArray, $spentArray, $start, $end);
- } else {
- // per month!
- $data = $this->singleYearInOutSummarized($earnedArray, $spentArray, $start, $end);
+ $cache->store($data);
+
+ return Response::json($data);
}
+ // per month!
+ $data = $this->singleYearInOutSummarized($earnedArray, $spentArray, $start, $end);
$cache->store($data);
return Response::json($data);
+
}
/**
@@ -178,7 +208,7 @@ class ReportController extends Controller
while ($start < $end) {
$incomeSum = $this->pluckFromArray($start->year, $earned);
- $expenseSum = $this->pluckFromArray($start->year, $spent) * -1;
+ $expenseSum = $this->pluckFromArray($start->year, $spent);
$entries->push([clone $start, $incomeSum, $expenseSum]);
$start->addYear();
@@ -205,7 +235,7 @@ class ReportController extends Controller
while ($start < $end) {
$currentIncome = $this->pluckFromArray($start->year, $earned);
- $currentExpense = bcmul($this->pluckFromArray($start->year, $spent), '-1');
+ $currentExpense = $this->pluckFromArray($start->year, $spent);
$income = bcadd($income, $currentIncome);
$expense = bcadd($expense, $currentExpense);
@@ -253,8 +283,8 @@ class ReportController extends Controller
while ($start < $end) {
// total income and total expenses:
$date = $start->format('Y-m');
- $incomeSum = $earned[$date] ?? 0;
- $expenseSum = isset($spent[$date]) ? ($spent[$date] * -1) : 0;
+ $incomeSum = isset($earned[$date]) ? $earned[$date] : 0;
+ $expenseSum = isset($spent[$date]) ? $spent[$date] : 0;
$entries->push([clone $start, $incomeSum, $expenseSum]);
$start->addMonth();
@@ -280,8 +310,8 @@ class ReportController extends Controller
$count = 0;
while ($start < $end) {
$date = $start->format('Y-m');
- $currentIncome = $earned[$date] ?? '0';
- $currentExpense = isset($spent[$date]) ? bcmul($spent[$date], '-1') : '0';
+ $currentIncome = isset($earned[$date]) ? $earned[$date] : 0;
+ $currentExpense = isset($spent[$date]) ? $spent[$date] : 0;
$income = bcadd($income, $currentIncome);
$expense = bcadd($expense, $currentExpense);
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 669caf9485..acf8cafde5 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -1,4 +1,12 @@
wizard = app('FireflyIII\Helpers\Csv\WizardInterface');
- $this->data = app('FireflyIII\Helpers\Csv\Data');
+ $this->wizard = app(WizardInterface::class);
+ $this->data = app(Data::class);
}
@@ -81,7 +89,7 @@ class CsvController extends Controller
if ($this->data->hasHeaders()) {
$headers = $firstRow;
}
- $keys = array_keys(Config::get('csv.roles'));
+ $keys = array_keys(config('csv.roles'));
foreach ($keys as $name) {
$availableRoles[$name] = trans('firefly.csv_column_' . $name);
}
@@ -159,11 +167,11 @@ class CsvController extends Controller
*
* STEP ONE
*
- * @param ARI $repository
+ * @param AccountCrudInterface $crud
*
* @return \Illuminate\View\View
*/
- public function index(ARI $repository)
+ public function index(AccountCrudInterface $crud)
{
$subTitle = trans('firefly.csv_import');
@@ -179,7 +187,7 @@ class CsvController extends Controller
// get list of supported specifix
$specifix = [];
- foreach (Config::get('csv.specifix') as $entry) {
+ foreach (config('csv.specifix') as $entry) {
$specifix[$entry] = trans('firefly.csv_specifix_' . $entry);
}
@@ -191,7 +199,7 @@ class CsvController extends Controller
];
// get a list of asset accounts:
- $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Asset account', 'Default account']));
+ $accounts = ExpandedForm::makeSelectList($crud->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]));
// can actually upload?
$uploadPossible = is_writable(storage_path('upload'));
@@ -312,12 +320,10 @@ class CsvController extends Controller
return redirect(route('csv.index'));
}
- Log::debug('Created importer');
/** @var Importer $importer */
- $importer = app('FireflyIII\Helpers\Csv\Importer');
+ $importer = app(Importer::class);
$importer->setData($this->data);
$importer->run();
- Log::debug('Done importing!');
$rows = $importer->getRows();
$errors = $importer->getErrors();
@@ -337,7 +343,6 @@ class CsvController extends Controller
*
* STEP SIX
*
- * @SuppressWarnings(PHPMD.CyclomaticComplexity) it's 6, but it's allright.
*
* @return \Illuminate\Http\RedirectResponse
*/
@@ -383,9 +388,6 @@ class CsvController extends Controller
*
* STEP TWO
*
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // need the length.
- * @SuppressWarnings(PHPMD.CyclomaticComplexity) // its exactly 5, its ok
- *
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse
diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php
index 80280c94ca..0681d3633f 100644
--- a/app/Http/Controllers/CurrencyController.php
+++ b/app/Http/Controllers/CurrencyController.php
@@ -1,4 +1,15 @@
-getCurrencyData();
- if (Auth::user()->hasRole('owner')) {
- $currency = $repository->store($data);
- Session::flash('success', trans('firefly.created_currency', ['name' => $currency->name]));
- } else {
+ if (!Auth::user()->hasRole('owner')) {
Log::error('User ' . Auth::user()->id . ' is not admin, but tried to store a currency.');
+
+ return redirect(session('currency.create.url'));
}
+ $data = $request->getCurrencyData();
+ $currency = $repository->store($data);
+ Session::flash('success', trans('firefly.created_currency', ['name' => $currency->name]));
+
if (intval(Input::get('create_another')) === 1) {
Session::put('currency.create.fromStore', true);
diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php
index 9dcafa77a0..265be2f85b 100644
--- a/app/Http/Controllers/ExportController.php
+++ b/app/Http/Controllers/ExportController.php
@@ -1,5 +1,4 @@
change('export_downloaded');
- Log::debug('Will send user file "' . $file . '".');
return response($disk->get($file), 200)
->header('Content-Description', 'File Transfer')
@@ -89,13 +91,12 @@ class ExportController extends Controller
}
/**
- * @param ARI $repository
- *
- * @param EJRI $jobs
+ * @param AccountCrudInterface $crud
+ * @param EJRI $jobs
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
- public function index(ARI $repository, EJRI $jobs)
+ public function index(AccountCrudInterface $crud, EJRI $jobs)
{
// create new export job.
$job = $jobs->create();
@@ -103,11 +104,11 @@ class ExportController extends Controller
$jobs->cleanup();
// does the user have shared accounts?
- $accounts = $repository->getAccounts(['Default account', 'Asset account']);
+ $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$accountList = ExpandedForm::makeSelectList($accounts);
$checked = array_keys($accountList);
- $formats = array_keys(Config::get('firefly.export_formats'));
- $defaultFormat = Preferences::get('export_format', Config::get('firefly.default_export_format'))->data;
+ $formats = array_keys(config('firefly.export_formats'));
+ $defaultFormat = Preferences::get('export_format', config('firefly.default_export_format'))->data;
$first = session('first')->format('Y-m-d');
$today = Carbon::create()->format('Y-m-d');
diff --git a/app/Http/Controllers/HelpController.php b/app/Http/Controllers/HelpController.php
index 9f7b58ff78..6cbca71ba5 100644
--- a/app/Http/Controllers/HelpController.php
+++ b/app/Http/Controllers/HelpController.php
@@ -1,4 +1,15 @@
-getFromGithub($language, $route);
$help->putInCache($route, $language, $content);
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index 89ec33df7b..172b4c6695 100644
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -1,13 +1,24 @@
-diffInDays($end);
@@ -42,6 +61,7 @@ class HomeController extends Controller
Session::flash('warning', strval(trans('firefly.warning_much_data', ['days' => $diff])));
}
+ Session::put('is_custom_range', $isCustomRange);
Session::put('start', $start);
Session::put('end', $end);
}
@@ -85,13 +105,14 @@ class HomeController extends Controller
}
/**
- * @param ARI $repository
+ * @param ARI $repository
+ * @param AccountCrudInterface $crud
*
- * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
+ * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*/
- public function index(ARI $repository)
+ public function index(ARI $repository, AccountCrudInterface $crud)
{
- $types = Config::get('firefly.accountTypesByIdentifier.asset');
+ $types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->countAccounts($types);
if ($count == 0) {
@@ -102,30 +123,27 @@ class HomeController extends Controller
$subTitle = trans('firefly.welcomeBack');
$mainTitleIcon = 'fa-fire';
$transactions = [];
- $frontPage = Preferences::get('frontPageAccounts', []);
+ $frontPage = Preferences::get(
+ 'frontPageAccounts', $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray()
+ );
/** @var Carbon $start */
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$showTour = Preferences::get('tour', true)->data;
- $accounts = $repository->getFrontpageAccounts($frontPage);
- $savings = $repository->getSavingsAccounts();
- $piggyBankAccounts = $repository->getPiggyBankAccounts();
+ $accounts = $crud->getAccountsById($frontPage->data);
+ $savings = $repository->getSavingsAccounts($start, $end);
+ $piggyBankAccounts = $repository->getPiggyBankAccounts($start, $end);
- $savingsTotal = 0;
+ $savingsTotal = '0';
foreach ($savings as $savingAccount) {
$savingsTotal = bcadd($savingsTotal, Steam::balance($savingAccount, $end));
}
- $sum = $repository->sumOfEverything();
-
- if (bccomp($sum, '0') !== 0) {
- Session::flash('error', strval(trans('firefly.unbalanced_error', ['amount' => Amount::format($sum, false)])));
- }
-
foreach ($accounts as $account) {
- $set = $repository->getFrontpageTransactions($account, $start, $end);
+ $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end);
+ $set = $set->splice(0, 10);
if (count($set) > 0) {
$transactions[] = [$set, $account];
@@ -145,10 +163,6 @@ class HomeController extends Controller
{
// these routes are not relevant for the help pages:
$ignore = [
- 'logout', 'register', 'bills.rescan', 'attachments.download', 'attachments.preview',
- 'budgets.income', 'csv.download-config', 'currency.default', 'export.status', 'export.download',
- 'json.', 'help.', 'piggy-banks.addMoney', 'piggy-banks.removeMoney', 'rules.rule.up', 'rules.rule.down',
- 'rules.rule-group.up', 'rules.rule-group.down', 'debugbar',
];
$routes = Route::getRoutes();
/** @var \Illuminate\Routing\Route $route */
@@ -156,11 +170,23 @@ class HomeController extends Controller
$name = $route->getName();
$methods = $route->getMethods();
+ $search = [
+ '{account}', '{what}', '{rule}', '{tj}', '{category}', '{budget}', '{code}', '{date}', '{attachment}', '{bill}', '{limitrepetition}',
+ '{currency}', '{jobKey}', '{piggyBank}', '{ruleGroup}', '{rule}', '{route}', '{unfinishedJournal}',
+ '{reportType}', '{start_date}', '{end_date}', '{accountList}','{tag}','{journalList}'
+
+ ];
+ $replace = [1, 'asset', 1, 1, 1, 1, 'abc', '2016-01-01', 1, 1, 1, 1, 1, 1, 1, 1, 'index', 1,
+ 'default', '20160101', '20160131', '1,2',1,'1,2'
+ ];
+ if (count($search) != count($replace)) {
+ echo 'count';
+ exit;
+ }
+ $url = str_replace($search, $replace, $route->getUri());
if (!is_null($name) && in_array('GET', $methods) && !$this->startsWithAny($ignore, $name)) {
- foreach (array_keys(Config::get('firefly.languages')) as $lang) {
- echo 'touch ' . $lang . '/' . $name . '.md
';
- }
+ echo '' . $name . '
' . "\n";
}
}
@@ -175,7 +201,10 @@ class HomeController extends Controller
*
* @return bool
*/
- private function startsWithAny(array $array, string $needle): bool
+ private
+ function startsWithAny(
+ array $array, string $needle
+ ): bool
{
foreach ($array as $entry) {
if ((substr($needle, 0, strlen($entry)) === $entry)) {
diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php
index 1df37061df..7897d1cf83 100644
--- a/app/Http/Controllers/JsonController.php
+++ b/app/Http/Controllers/JsonController.php
@@ -1,10 +1,20 @@
- 0 ? intval(Input::get('count')) : 1;
- $keys = array_keys(Config::get('firefly.rule-actions'));
+ $keys = array_keys(config('firefly.rule-actions'));
$actions = [];
foreach ($keys as $key) {
$actions[$key] = trans('firefly.rule_action_' . $key . '_choice');
@@ -61,11 +71,7 @@ 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.
- $creditCardDue = $repository->getCreditCardBill($start, $end);
- if ($creditCardDue >= 0) {
- $amount = bcadd($amount, $creditCardDue);
- }
+ $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
$amount = bcmul($amount, '-1');
$data = ['box' => 'bills-paid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
@@ -80,30 +86,21 @@ 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.
- $creditCardDue = $repository->getCreditCardBill($start, $end);
-
- if ($creditCardDue < 0) {
- // expenses are negative (bill not yet paid),
- $creditCardDue = bcmul($creditCardDue, '-1');
- $amount = bcadd($amount, $creditCardDue);
- }
-
- $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.
+ $data = ['box' => 'bills-unpaid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
return Response::json($data);
}
/**
- * @param ReportQueryInterface $reportQuery
- *
* @param ARI $accountRepository
+ * @param AccountCrudInterface $crud
*
- * @return \Symfony\Component\HttpFoundation\Response
+ * @return \Illuminate\Http\JsonResponse
*/
- public function boxIn(ReportQueryInterface $reportQuery, ARI $accountRepository)
+ public function boxIn(ARI $accountRepository, AccountCrudInterface $crud)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
@@ -116,29 +113,25 @@ class JsonController extends Controller
if ($cache->has()) {
return Response::json($cache->get());
}
- $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
- $amount = $reportQuery->income($accounts, $start, $end)->sum('journalAmount');
-
- $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
+ $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
+ $amount = $accountRepository->earnedInPeriod($accounts, $start, $end);
+ $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
$cache->store($data);
return Response::json($data);
}
/**
- * @param ReportQueryInterface $reportQuery
- *
* @param ARI $accountRepository
+ * @param AccountCrudInterface $crud
*
* @return \Symfony\Component\HttpFoundation\Response
*/
- public function boxOut(ReportQueryInterface $reportQuery, ARI $accountRepository)
+ public function boxOut(ARI $accountRepository, AccountCrudInterface $crud)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
- $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
-
// works for json too!
$cache = new CacheProperties;
$cache->addProperty($start);
@@ -148,7 +141,8 @@ class JsonController extends Controller
return Response::json($cache->get());
}
- $amount = $reportQuery->expense($accounts, $start, $end)->sum('journalAmount');
+ $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
+ $amount = $accountRepository->spentInPeriod($accounts, $start, $end);
$data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
$cache->store($data);
@@ -187,13 +181,13 @@ class JsonController extends Controller
/**
* Returns a JSON list of all beneficiaries.
*
- * @param ARI $accountRepository
+ * @param AccountCrudInterface $crud
*
* @return \Illuminate\Http\JsonResponse
*/
- public function expenseAccounts(ARI $accountRepository)
+ public function expenseAccounts(AccountCrudInterface $crud)
{
- $list = $accountRepository->getAccounts(['Expense account', 'Beneficiary account']);
+ $list = $crud->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
$return = [];
foreach ($list as $entry) {
$return[] = $entry->name;
@@ -204,13 +198,13 @@ class JsonController extends Controller
}
/**
- * @param ARI $accountRepository
+ * @param AccountCrudInterface $crud
*
* @return \Illuminate\Http\JsonResponse
*/
- public function revenueAccounts(ARI $accountRepository)
+ public function revenueAccounts(AccountCrudInterface $crud)
{
- $list = $accountRepository->getAccounts(['Revenue account']);
+ $list = $crud->getAccountsByType([AccountType::REVENUE]);
$return = [];
foreach ($list as $entry) {
$return[] = $entry->name;
@@ -276,9 +270,9 @@ class JsonController extends Controller
public function transactionJournals(JournalRepositoryInterface $repository, $what)
{
$descriptions = [];
- $dbType = $repository->getTransactionType($what);
-
- $journals = $repository->getJournalsOfType($dbType);
+ $type = config('firefly.transactionTypesByWhat.' . $what);
+ $types = [$type];
+ $journals = $repository->getJournals($types, 1, 50);
foreach ($journals as $j) {
$descriptions[] = $j->description;
}
@@ -297,7 +291,7 @@ class JsonController extends Controller
public function trigger()
{
$count = intval(Input::get('count')) > 0 ? intval(Input::get('count')) : 1;
- $keys = array_keys(Config::get('firefly.rule-triggers'));
+ $keys = array_keys(config('firefly.rule-triggers'));
$triggers = [];
foreach ($keys as $key) {
if ($key != 'user_action') {
diff --git a/app/Http/Controllers/NewUserController.php b/app/Http/Controllers/NewUserController.php
index 05532e9071..3c7dcd4102 100644
--- a/app/Http/Controllers/NewUserController.php
+++ b/app/Http/Controllers/NewUserController.php
@@ -1,8 +1,18 @@
-countAccounts($types);
if ($count > 0) {
@@ -48,15 +58,48 @@ class NewUserController extends Controller
}
/**
- * @param NewUserFormRequest $request
- * @param ARI $repository
+ * @param NewUserFormRequest $request
+ * @param AccountCrudInterface $crud
*
* @return \Illuminate\Http\RedirectResponse
*/
- public function submit(NewUserFormRequest $request, ARI $repository)
+ public function submit(NewUserFormRequest $request, AccountCrudInterface $crud)
{
$count = 1;
// create normal asset account:
+ $this->createAssetAccount($request, $crud);
+
+ // create savings account
+ if (strlen($request->get('savings_balance')) > 0) {
+ $this->createSavingsAccount($request, $crud);
+ $count++;
+ }
+
+
+ // create credit card.
+ if (strlen($request->get('credit_card_limit')) > 0) {
+ $this->storeCreditCard($request, $crud);
+ $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);
+ Preferences::mark();
+
+ return redirect(route('index'));
+ }
+
+ /**
+ * @param NewUserFormRequest $request
+ * @param AccountCrudInterface $crud
+ *
+ * @return bool
+ */
+ private function createAssetAccount(NewUserFormRequest $request, AccountCrudInterface $crud): bool
+ {
$assetAccount = [
'name' => $request->get('bank_name'),
'iban' => null,
@@ -70,56 +113,62 @@ class NewUserController extends Controller
'openingBalanceCurrency' => intval($request->input('amount_currency_id_bank_balance')),
];
- $repository->store($assetAccount);
+ $crud->store($assetAccount);
- // create savings account
- if (strlen($request->get('savings_balance') > 0)) {
- $savingsAccount = [
- 'name' => $request->get('bank_name') . ' savings account',
- 'iban' => null,
- 'accountType' => 'asset',
- 'virtualBalance' => 0,
- 'active' => true,
- 'user' => Auth::user()->id,
- 'accountRole' => 'savingAsset',
- 'openingBalance' => round($request->input('savings_balance'), 2),
- 'openingBalanceDate' => new Carbon,
- 'openingBalanceCurrency' => intval($request->input('amount_currency_id_savings_balance')),
- ];
- $repository->store($savingsAccount);
- $count++;
- }
+ return true;
+ }
+ /**
+ * @param NewUserFormRequest $request
+ * @param AccountCrudInterface $crud
+ *
+ * @return bool
+ */
+ private function createSavingsAccount(NewUserFormRequest $request, AccountCrudInterface $crud): bool
+ {
+ $savingsAccount = [
+ 'name' => $request->get('bank_name') . ' savings account',
+ 'iban' => null,
+ 'accountType' => 'asset',
+ 'virtualBalance' => 0,
+ 'active' => true,
+ 'user' => Auth::user()->id,
+ 'accountRole' => 'savingAsset',
+ 'openingBalance' => round($request->input('savings_balance'), 2),
+ 'openingBalanceDate' => new Carbon,
+ 'openingBalanceCurrency' => intval($request->input('amount_currency_id_savings_balance')),
+ ];
+ $crud->store($savingsAccount);
- // create credit card.
- if (strlen($request->get('credit_card_limit') > 0)) {
- $creditAccount = [
- 'name' => 'Credit card',
- 'iban' => null,
- 'accountType' => 'asset',
- 'virtualBalance' => round($request->get('credit_card_limit'), 2),
- 'active' => true,
- 'user' => Auth::user()->id,
- 'accountRole' => 'ccAsset',
- 'openingBalance' => null,
- 'openingBalanceDate' => null,
- 'openingBalanceCurrency' => intval($request->input('amount_currency_id_credit_card_limit')),
- ];
- $creditCard = $repository->store($creditAccount);
+ return true;
+ }
- // store meta for CC:
- $repository->storeMeta($creditCard, 'ccType', 'monthlyFull');
- $repository->storeMeta($creditCard, 'ccMonthlyPaymentDate', Carbon::now()->year . '-01-01');
- $count++;
- }
- if ($count == 1) {
- Session::flash('success', strval(trans('firefly.stored_new_account_new_user')));
- } else {
- Session::flash('success', strval(trans('firefly.stored_new_accounts_new_user')));
- }
+ /**
+ * @param NewUserFormRequest $request
+ * @param AccountCrudInterface $crud
+ *
+ * @return bool
+ */
+ private function storeCreditCard(NewUserFormRequest $request, AccountCrudInterface $crud): bool
+ {
+ $creditAccount = [
+ 'name' => 'Credit card',
+ 'iban' => null,
+ 'accountType' => 'asset',
+ 'virtualBalance' => round($request->get('credit_card_limit'), 2),
+ 'active' => true,
+ 'user' => Auth::user()->id,
+ 'accountRole' => 'ccAsset',
+ 'openingBalance' => null,
+ 'openingBalanceDate' => null,
+ 'openingBalanceCurrency' => intval($request->input('amount_currency_id_credit_card_limit')),
+ ];
+ $creditCard = $crud->store($creditAccount);
- Preferences::mark();
+ // store meta for CC:
+ $crud->storeMeta($creditCard, 'ccType', 'monthlyFull');
+ $crud->storeMeta($creditCard, 'ccMonthlyPaymentDate', Carbon::now()->year . '-01-01');
- return redirect(route('index'));
+ return true;
}
}
diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php
index c7dfb69ec9..898adc8fb7 100644
--- a/app/Http/Controllers/PiggyBankController.php
+++ b/app/Http/Controllers/PiggyBankController.php
@@ -1,10 +1,21 @@
-getAccounts(['Default account', 'Asset account']));
+ $accounts = ExpandedForm::makeSelectList($crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
$subTitle = trans('firefly.new_piggy_bank');
$subTitleIcon = 'fa-plus';
@@ -78,7 +87,7 @@ class PiggyBankController extends Controller
Session::flash('gaEventCategory', 'piggy-banks');
Session::flash('gaEventAction', 'create');
- return view('piggy-banks.create', compact('accounts', 'periods', 'subTitle', 'subTitleIcon'));
+ return view('piggy-banks.create', compact('accounts', 'subTitle', 'subTitleIcon'));
}
/**
@@ -116,28 +125,25 @@ class PiggyBankController extends Controller
}
/**
- * @param ARI $repository
- * @param PiggyBank $piggyBank
+ * @param AccountCrudInterface $crud
+ * @param PiggyBank $piggyBank
*
* @return View
*/
- public function edit(ARI $repository, PiggyBank $piggyBank)
+ public function edit(AccountCrudInterface $crud, PiggyBank $piggyBank)
{
- $periods = Config::get('firefly.piggy_bank_periods');
- $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account']));
+ $accounts = ExpandedForm::makeSelectList($crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
$subTitle = trans('firefly.update_piggy_title', ['name' => $piggyBank->name]);
$subTitleIcon = 'fa-pencil';
-
+ $targetDate = null;
/*
* Flash some data to fill the form.
*/
- if (is_null($piggyBank->targetdate) || $piggyBank->targetdate == '') {
- $targetDate = null;
- } else {
- $targetDate = new Carbon($piggyBank->targetdate);
- $targetDate = $targetDate->format('Y-m-d');
+ if (!is_null($piggyBank->targetdate)) {
+ $targetDate = $piggyBank->targetdate->format('Y-m-d');
}
+
$preFilled = ['name' => $piggyBank->name,
'account_id' => $piggyBank->account_id,
'targetamount' => $piggyBank->targetamount,
@@ -153,7 +159,7 @@ class PiggyBankController extends Controller
}
Session::forget('piggy-banks.edit.fromUpdate');
- return view('piggy-banks.edit', compact('subTitle', 'subTitleIcon', 'piggyBank', 'accounts', 'periods', 'preFilled'));
+ return view('piggy-banks.edit', compact('subTitle', 'subTitleIcon', 'piggyBank', 'accounts', 'preFilled'));
}
/**
@@ -174,7 +180,7 @@ class PiggyBankController extends Controller
foreach ($piggyBanks as $piggyBank) {
$piggyBank->savedSoFar = round($piggyBank->currentRelevantRep()->currentamount, 2);
$piggyBank->percentage = $piggyBank->savedSoFar != 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
- $piggyBank->leftToSave = bcsub($piggyBank->targetamount, $piggyBank->savedSoFar);
+ $piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar));
$piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage;
/*
@@ -184,14 +190,14 @@ class PiggyBankController extends Controller
if (!isset($accounts[$account->id])) {
$accounts[$account->id] = [
'name' => $account->name,
- 'balance' => Steam::balance($account, $end, true),
+ 'balance' => Steam::balanceIgnoreVirtual($account, $end),
'leftForPiggyBanks' => $repository->leftOnAccount($account, $end),
'sumOfSaved' => strval($piggyBank->savedSoFar),
'sumOfTargets' => strval(round($piggyBank->targetamount, 2)),
'leftToSave' => $piggyBank->leftToSave,
];
} else {
- $accounts[$account->id]['sumOfSaved'] = bcadd($accounts[$account->id]['sumOfSaved'], $piggyBank->savedSoFar);
+ $accounts[$account->id]['sumOfSaved'] = bcadd($accounts[$account->id]['sumOfSaved'], strval($piggyBank->savedSoFar));
$accounts[$account->id]['sumOfTargets'] = bcadd($accounts[$account->id]['sumOfTargets'], $piggyBank->targetamount);
$accounts[$account->id]['leftToSave'] = bcadd($accounts[$account->id]['leftToSave'], $piggyBank->leftToSave);
}
@@ -227,11 +233,10 @@ class PiggyBankController extends Controller
*/
public function postAdd(PiggyBankRepositoryInterface $repository, ARI $accounts, PiggyBank $piggyBank)
{
- $amount = round(Input::get('amount'), 2);
- /** @var Carbon $date */
+ $amount = strval(round(Input::get('amount'), 2));
$date = session('end', Carbon::now()->endOfMonth());
$leftOnAccount = $accounts->leftOnAccount($piggyBank->account, $date);
- $savedSoFar = $piggyBank->currentRelevantRep()->currentamount;
+ $savedSoFar = strval($piggyBank->currentRelevantRep()->currentamount);
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
$maxAmount = round(min($leftOnAccount, $leftToSave), 2);
@@ -247,13 +252,13 @@ class PiggyBankController extends Controller
'success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))
);
Preferences::mark();
- } else {
- Log::error('Cannot add ' . $amount . ' because max amount is ' . $maxAmount . ' (left on account is ' . $leftOnAccount . ')');
- Session::flash(
- 'error', strval(trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))
- );
+
+ return redirect(route('piggy-banks.index'));
}
+ Log::error('Cannot add ' . $amount . ' because max amount is ' . $maxAmount . ' (left on account is ' . $leftOnAccount . ')');
+ Session::flash('error', strval(trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
+
return redirect(route('piggy-banks.index'));
}
@@ -281,12 +286,12 @@ class PiggyBankController extends Controller
'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))
);
Preferences::mark();
- } else {
- Session::flash(
- 'error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))
- );
+
+ return redirect(route('piggy-banks.index'));
}
+ Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
+
return redirect(route('piggy-banks.index'));
}
diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php
index a0a0055b0d..d24b9be3ba 100644
--- a/app/Http/Controllers/Popup/ReportController.php
+++ b/app/Http/Controllers/Popup/ReportController.php
@@ -1,5 +1,4 @@
get('attributes');
+ $attributes = $request->get('attributes') ?? [];
$attributes = $this->parseAttributes($attributes);
View::share('start', $attributes['startDate']);
@@ -83,26 +87,26 @@ class ReportController extends Controller
$role = intval($attributes['role']);
/** @var BudgetRepositoryInterface $budgetRepository */
- $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+ $budgetRepository = app(BudgetRepositoryInterface::class);
$budget = $budgetRepository->find(intval($attributes['budgetId']));
-
- /** @var AccountRepositoryInterface $accountRepository */
- $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
- $account = $accountRepository->find(intval($attributes['accountId']));
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $account = $crud->find(intval($attributes['accountId']));
switch (true) {
case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)):
- $journals = $budgetRepository->expensesSplit($budget, $account, $attributes['startDate'], $attributes['endDate']);
+ $journals = $budgetRepository->journalsInPeriod(
+ new Collection([$budget]), new Collection([$account]), $attributes['startDate'], $attributes['endDate']
+ );
break;
case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)):
$budget->name = strval(trans('firefly.no_budget'));
- $journals = $budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
+ $journals = $budgetRepository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
break;
case ($role === BalanceLine::ROLE_DIFFROLE):
// journals no budget, not corrected by a tag.
- $journals = $budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
+ $journals = $budgetRepository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
$budget->name = strval(trans('firefly.leftUnbalanced'));
- $journals = $journals->filter(
+ $journals = $journals->filter(
function (TransactionJournal $journal) {
$tags = $journal->tags()->where('tagMode', 'balancingAct')->count();
if ($tags === 0) {
@@ -133,13 +137,13 @@ class ReportController extends Controller
// then search for expenses in the given period
// list them in some table format.
/** @var BudgetRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+ $repository = app(BudgetRepositoryInterface::class);
$budget = $repository->find(intval($attributes['budgetId']));
if (is_null($budget->id)) {
- $journals = $repository->getWithoutBudgetForAccounts($attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
+ $journals = $repository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
} else {
// get all expenses in budget in period:
- $journals = $repository->getExpenses($budget, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
+ $journals = $repository->journalsInPeriod(new Collection([$budget]), $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
}
$view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render();
@@ -157,10 +161,10 @@ class ReportController extends Controller
*/
private function categoryEntry(array $attributes): string
{
- /** @var SingleCategoryRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface');
+ /** @var CategoryRepositoryInterface $repository */
+ $repository = app(CategoryRepositoryInterface::class);
$category = $repository->find(intval($attributes['categoryId']));
- $journals = $repository->getJournalsForAccountsInRange($category, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
+ $journals = $repository->journalsInPeriod(new Collection([$category]), $attributes['accounts'], [], $attributes['startDate'], $attributes['endDate']);
$view = view('popup.report.category-entry', compact('journals', 'category'))->render();
return $view;
@@ -177,10 +181,22 @@ class ReportController extends Controller
private function expenseEntry(array $attributes): string
{
/** @var AccountRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
- $account = $repository->find(intval($attributes['accountId']));
- $journals = $repository->getExpensesByDestination($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
- $view = view('popup.report.expense-entry', compact('journals', 'account'))->render();
+ $repository = app(AccountRepositoryInterface::class);
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $account = $crud->find(intval($attributes['accountId']));
+ $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
+ $journals = $repository->journalsInPeriod($attributes['accounts'], $types, $attributes['startDate'], $attributes['endDate']);
+
+ // filter for transfers and withdrawals TO the given $account
+ $journals = $journals->filter(
+ function (TransactionJournal $journal) use ($account) {
+ if ($journal->destination_account_id === $account->id) {
+ return $journal;
+ }
+ }
+ );
+
+ $view = view('popup.report.expense-entry', compact('journals', 'account'))->render();
return $view;
}
@@ -196,11 +212,25 @@ class ReportController extends Controller
private function incomeEntry(array $attributes): string
{
/** @var AccountRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
- $account = $repository->find(intval($attributes['accountId']));
- $journals = $repository->getIncomeByDestination($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
- $view = view('popup.report.income-entry', compact('journals', 'account'))->render();
+ $repository = app(AccountRepositoryInterface::class);
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $account = $crud->find(intval($attributes['accountId']));
+ $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
+ $journals = $repository->journalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']);
+ $destinations = $attributes['accounts']->pluck('id')->toArray();
+ // filter for transfers and withdrawals FROM the given $account
+ $journals = $journals->filter(
+ function (TransactionJournal $journal) use ($account, $destinations) {
+ if (
+ $journal->source_account_id === $account->id
+ && in_array($journal->destination_account_id, $destinations)
+ ) {
+ return $journal;
+ }
+ }
+ );
+ $view = view('popup.report.income-entry', compact('journals', 'account'))->render();
return $view;
}
diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php
index 8a04819529..595a7aac34 100644
--- a/app/Http/Controllers/PreferencesController.php
+++ b/app/Http/Controllers/PreferencesController.php
@@ -1,9 +1,19 @@
-getAccounts(['Default account', 'Asset account']);
+ $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$viewRangePref = Preferences::get('viewRange', '1M');
$viewRange = $viewRangePref->data;
$frontPageAccounts = Preferences::get('frontPageAccounts', []);
@@ -131,14 +141,14 @@ class PreferencesController extends Controller
Preferences::set('budgetMaximum', $budgetMaximum);
// custom fiscal year
- $customFiscalYear = (int)Input::get('customFiscalYear');
+ $customFiscalYear = intval(Input::get('customFiscalYear')) === 1;
$fiscalYearStart = date('m-d', strtotime(Input::get('fiscalYearStart')));
Preferences::set('customFiscalYear', $customFiscalYear);
Preferences::set('fiscalYearStart', $fiscalYearStart);
// save page size:
$transactionPageSize = intval(Input::get('transactionPageSize'));
- if($transactionPageSize > 0 && $transactionPageSize < 1337) {
+ if ($transactionPageSize > 0 && $transactionPageSize < 1337) {
Preferences::set('transactionPageSize', $transactionPageSize);
} else {
Preferences::set('transactionPageSize', 50);
@@ -155,7 +165,7 @@ class PreferencesController extends Controller
// language:
$lang = Input::get('language');
- if (in_array($lang, array_keys(Config::get('firefly.languages')))) {
+ if (in_array($lang, array_keys(config('firefly.languages')))) {
Preferences::set('language', $lang);
}
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index f26023f3ec..95cf5fad7d 100644
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -1,4 +1,15 @@
-helper = $helper;
- $this->accountHelper = app('FireflyIII\Helpers\Report\AccountReportHelperInterface');
- $this->budgetHelper = app('FireflyIII\Helpers\Report\BudgetReportHelperInterface');
- $this->balanceHelper = app('FireflyIII\Helpers\Report\BalanceReportHelperInterface');
+ $this->accountHelper = app(AccountReportHelperInterface::class);
+ $this->budgetHelper = app(BudgetReportHelperInterface::class);
+ $this->balanceHelper = app(BalanceReportHelperInterface::class);
View::share('title', trans('firefly.reports'));
View::share('mainTitleIcon', 'fa-line-chart');
@@ -56,12 +68,11 @@ class ReportController extends Controller
}
/**
- * @param ARI $repository
+ * @param AccountCrudInterface $crud
*
* @return View
- * @internal param ReportHelperInterface $helper
*/
- public function index(ARI $repository)
+ public function index(AccountCrudInterface $crud)
{
/** @var Carbon $start */
$start = clone session('first');
@@ -69,7 +80,7 @@ class ReportController extends Controller
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
// does the user have shared accounts?
- $accounts = $repository->getAccounts(['Default account', 'Asset account']);
+ $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// get id's for quick links:
$accountIds = [];
/** @var Account $account */
@@ -100,7 +111,6 @@ class ReportController extends Controller
// lower threshold
if ($start < session('first')) {
- Log::debug('Start is ' . $start . ' but sessionfirst is ' . session('first'));
$start = session('first');
}
@@ -149,7 +159,7 @@ class ReportController extends Controller
private function auditReport(Carbon $start, Carbon $end, Collection $accounts)
{
/** @var ARI $repos */
- $repos = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
+ $repos = app(ARI::class);
$auditData = [];
$dayBefore = clone $start;
$dayBefore->subDay();
@@ -163,13 +173,12 @@ class ReportController extends Controller
$exists = false;
$journals = new Collection;
$dayBeforeBalance = Steam::balance($account, $dayBefore);
-
/*
* Is there even activity on this account between the requested dates?
*/
if ($start->between($first, $last) || $end->between($first, $last)) {
$exists = true;
- $journals = $repos->getJournalsInRange($account, $start, $end);
+ $journals = $repos->journalsInPeriod(new Collection([$account]), [], $start, $end);
}
/*
@@ -197,13 +206,12 @@ class ReportController extends Controller
*/
$auditData[$id]['journals'] = $journals->reverse();
$auditData[$id]['exists'] = $exists;
- $auditData[$id]['end'] = $end->formatLocalized(trans('config.month_and_day'));
+ $auditData[$id]['end'] = $end->formatLocalized(strval(trans('config.month_and_day')));
$auditData[$id]['endBalance'] = Steam::balance($account, $end);
- $auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(trans('config.month_and_day'));
+ $auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day')));
$auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance;
}
-
$reportType = 'audit';
$accountIds = join(',', $accounts->pluck('id')->toArray());
@@ -271,8 +279,8 @@ class ReportController extends Controller
$incomeTopLength = 8;
$expenseTopLength = 8;
// list of users stuff:
- $budgets = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface')->getActiveBudgets();
- $categories = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface')->getCategories();
+ $budgets = app(BudgetRepositoryInterface::class)->getActiveBudgets();
+ $categories = app(CategoryRepositoryInterface::class)->getCategories();
$accountReport = $this->accountHelper->getAccountReport($start, $end, $accounts);
$incomes = $this->helper->getIncomeReport($start, $end, $accounts);
$expenses = $this->helper->getExpenseReport($start, $end, $accounts);
@@ -313,6 +321,9 @@ class ReportController extends Controller
$expenses = $this->helper->getExpenseReport($start, $end, $accounts);
$tags = $this->helper->tagReport($start, $end, $accounts);
+ // find the budgets we've spent money on this period with these accounts:
+ $budgets = $this->budgetHelper->getBudgetsWithExpenses($start, $end, $accounts);
+
Session::flash('gaEventCategory', 'report');
Session::flash('gaEventAction', 'year');
Session::flash('gaEventLabel', $start->format('Y'));
@@ -329,7 +340,7 @@ class ReportController extends Controller
'reports.default.year',
compact(
'start', 'accountReport', 'incomes', 'reportType', 'accountIds', 'end',
- 'expenses', 'incomeTopLength', 'expenseTopLength', 'tags'
+ 'expenses', 'incomeTopLength', 'expenseTopLength', 'tags', 'budgets'
)
);
}
diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php
index bc3d228969..0b908806cd 100644
--- a/app/Http/Controllers/RuleController.php
+++ b/app/Http/Controllers/RuleController.php
@@ -1,5 +1,4 @@
getCurrentTriggers($rule);
+ $triggerCount = count($oldTriggers);
+ $oldActions = $this->getCurrentActions($rule);
+ $actionCount = count($oldActions);
+
// has old input?
if (Input::old()) {
$oldTriggers = $this->getPreviousTriggers();
$triggerCount = count($oldTriggers);
$oldActions = $this->getPreviousActions();
$actionCount = count($oldActions);
- } else {
- $oldTriggers = $this->getCurrentTriggers($rule);
- $triggerCount = count($oldTriggers);
- $oldActions = $this->getCurrentActions($rule);
- $actionCount = count($oldActions);
}
// get rule trigger for update / store-journal:
@@ -292,8 +292,8 @@ class RuleController extends Controller
return Response::json(['html' => '', 'warning' => trans('firefly.warning_no_valid_triggers')]);
}
- $limit = Config::get('firefly.test-triggers.limit');
- $range = Config::get('firefly.test-triggers.range');
+ $limit = config('firefly.test-triggers.limit');
+ $range = config('firefly.test-triggers.range');
/** @var TransactionMatcher $matcher */
$matcher = app('FireflyIII\Rules\TransactionMatcher');
@@ -306,10 +306,9 @@ class RuleController extends Controller
$warning = '';
if (count($matchingTransactions) == $limit) {
$warning = trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]);
- } else {
- if (count($matchingTransactions) == 0) {
- $warning = trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]);
- }
+ }
+ if (count($matchingTransactions) == 0) {
+ $warning = trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]);
}
// Return json response
@@ -549,12 +548,14 @@ class RuleController extends Controller
'rule-trigger-values' => $request->get('rule-trigger-value'),
'rule-trigger-stop' => $request->get('rule-trigger-stop'),
];
- foreach ($data['rule-triggers'] as $index => $triggerType) {
- $triggers[] = [
- 'type' => $triggerType,
- 'value' => $data['rule-trigger-values'][$index],
- 'stopProcessing' => intval($data['rule-trigger-stop'][$index]) === 1 ? true : false,
- ];
+ if (is_array($data['rule-triggers'])) {
+ foreach ($data['rule-triggers'] as $index => $triggerType) {
+ $triggers[] = [
+ 'type' => $triggerType,
+ 'value' => $data['rule-trigger-values'][$index],
+ 'stopProcessing' => intval($data['rule-trigger-stop'][$index]) === 1 ? true : false,
+ ];
+ }
}
return $triggers;
diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php
index 9b4afec0f3..6c014adaef 100644
--- a/app/Http/Controllers/RuleGroupController.php
+++ b/app/Http/Controllers/RuleGroupController.php
@@ -1,4 +1,12 @@
$ruleGroup->title]);
- $ruleGroupList = ExpandedForm::makeSelectList($repository->get(), true);
+ $ruleGroupList = ExpandedForm::makeSelectListWithEmpty($repository->get());
unset($ruleGroupList[$ruleGroup->id]);
// put previous url in session
@@ -168,17 +178,15 @@ class RuleGroupController extends Controller
}
/**
- * Shows a form for the user to select a range of transactions to execute this rulegroup for
- *
- * @param AccountRepositoryInterface $repository
- * @param RuleGroup $ruleGroup
+ * @param AccountCrudInterface $crud
+ * @param RuleGroup $ruleGroup
*
* @return View
*/
- public function selectTransactions(AccountRepositoryInterface $repository, RuleGroup $ruleGroup)
+ public function selectTransactions(AccountCrudInterface $crud, RuleGroup $ruleGroup)
{
// does the user have shared accounts?
- $accounts = $repository->getAccounts(['Default account', 'Asset account']);
+ $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$accountList = ExpandedForm::makeSelectList($accounts);
$checkedAccounts = array_keys($accountList);
$first = session('first')->format('Y-m-d');
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
index b634a85379..3f0b21041b 100644
--- a/app/Http/Controllers/SearchController.php
+++ b/app/Http/Controllers/SearchController.php
@@ -1,4 +1,15 @@
- $tag->tag]);
+ $subTitle = trans('breadcrumbs.delete_tag', ['tag' => e($tag->tag)]);
// put previous url in session
Session::put('tags.delete.url', URL::previous());
@@ -113,13 +121,11 @@ class TagController extends Controller
}
/**
- * @param Tag $tag
- *
- * @param TagRepositoryInterface $repository
+ * @param Tag $tag
*
* @return \Illuminate\View\View
*/
- public function edit(Tag $tag, TagRepositoryInterface $repository)
+ public function edit(Tag $tag)
{
$subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]);
$subTitleIcon = 'fa-tag';
@@ -132,8 +138,8 @@ class TagController extends Controller
/*
* Can this tag become another type?
*/
- $allowAdvance = $repository->tagAllowAdvance($tag);
- $allowToBalancingAct = $repository->tagAllowBalancing($tag);
+ $allowAdvance = Tag::tagAllowAdvance($tag);
+ $allowToBalancingAct = Tag::tagAllowBalancing($tag);
// edit tag options:
if ($allowAdvance === false) {
@@ -221,7 +227,7 @@ class TagController extends Controller
$subTitle = $tag->tag;
$subTitleIcon = 'fa-tag';
/** @var Collection $journals */
- $journals = $tag->transactionjournals()->expanded()->get(TransactionJournal::QUERYFIELDS);
+ $journals = $tag->transactionjournals()->sortCorrectly()->expanded()->get(TransactionJournal::queryFields());
$sum = $journals->sum(
function (TransactionJournal $journal) {
diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php
new file mode 100644
index 0000000000..a8b0ccc636
--- /dev/null
+++ b/app/Http/Controllers/Transaction/MassController.php
@@ -0,0 +1,207 @@
+get('confirm_mass_delete');
+ $set = new Collection;
+ if (is_array($ids)) {
+ /** @var int $journalId */
+ foreach ($ids as $journalId) {
+ /** @var TransactionJournal $journal */
+ $journal = $repository->find($journalId);
+ if (!is_null($journal->id) && $journalId == $journal->id) {
+ $set->push($journal);
+ }
+ }
+ }
+ unset($journal);
+ $count = 0;
+
+ /** @var TransactionJournal $journal */
+ foreach ($set as $journal) {
+ $repository->delete($journal);
+ $count++;
+ }
+
+ Preferences::mark();
+ Session::flash('success', trans('firefly.mass_deleted_transactions_success', ['amount' => $count]));
+
+ // redirect to previous URL:
+ return redirect(session('transactions.mass-delete.url'));
+
+ }
+
+
+ /**
+ * @param Collection $journals
+ *
+ * @return View
+ */
+ public function massEdit(Collection $journals)
+ {
+ $subTitle = trans('firefly.mass_edit_journals');
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $accountList = ExpandedForm::makeSelectList($crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
+
+ // put previous url in session
+ Session::put('transactions.mass-edit.url', URL::previous());
+ Session::flash('gaEventCategory', 'transactions');
+ Session::flash('gaEventAction', 'mass-edit');
+
+ // set some values to be used in the edit routine:
+ $journals->each(
+ function (TransactionJournal $journal) {
+ $journal->amount = TransactionJournal::amountPositive($journal);
+ $sources = TransactionJournal::sourceAccountList($journal);
+ $destinations = TransactionJournal::destinationAccountList($journal);
+ $journal->transaction_count = $journal->transactions()->count();
+ if (!is_null($sources->first())) {
+ $journal->source_account_id = $sources->first()->id;
+ $journal->source_account_name = $sources->first()->name;
+ }
+ if (!is_null($destinations->first())) {
+ $journal->destination_account_id = $destinations->first()->id;
+ $journal->destination_account_name = $destinations->first()->name;
+ }
+ }
+ );
+
+ return view('transactions.mass-edit', compact('journals', 'subTitle', 'accountList'));
+ }
+
+ /**
+ * @param MassEditJournalRequest $request
+ * @param JournalRepositoryInterface $repository
+ *
+ * @return mixed
+ */
+ public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository)
+ {
+ $journalIds = $request->get('journals');
+ $count = 0;
+ if (is_array($journalIds)) {
+ foreach ($journalIds as $journalId) {
+ $journal = $repository->find(intval($journalId));
+ if ($journal) {
+ // get optional fields:
+ $what = strtolower(TransactionJournal::transactionTypeStr($journal));
+ $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0;
+ $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0;
+ $expenseAccount = $request->get('expense_account')[$journal->id] ?? '';
+ $revenueAccount = $request->get('revenue_account')[$journal->id] ?? '';
+ $budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0;
+ $category = $journal->categories->first() ? $journal->categories->first()->name : '';
+ $tags = $journal->tags->pluck('tag')->toArray();
+
+ // for a deposit, the 'account_id' is the account the money is deposited on.
+ // needs a better way of handling.
+ // more uniform source/destination field names
+ $accountId = $sourceAccountId;
+ if ($what == 'deposit') {
+ $accountId = $destAccountId;
+ }
+
+ // build data array
+ $data = [
+ 'id' => $journal->id,
+ 'what' => $what,
+ 'description' => $request->get('description')[$journal->id],
+ 'account_id' => intval($accountId),
+ 'account_from_id' => intval($sourceAccountId),
+ 'account_to_id' => intval($destAccountId),
+ 'expense_account' => $expenseAccount,
+ 'revenue_account' => $revenueAccount,
+ 'amount' => round($request->get('amount')[$journal->id], 4),
+ 'user' => Auth::user()->id,
+ 'amount_currency_id_amount' => intval($request->get('amount_currency_id_amount_' . $journal->id)),
+ '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' => $budgetId,
+ 'category' => $category,
+ 'tags' => $tags,
+
+ ];
+ // call repository update function.
+ $repository->update($journal, $data);
+
+ $count++;
+ }
+ }
+ }
+ Preferences::mark();
+ Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
+
+ // redirect to previous URL:
+ return redirect(session('transactions.mass-edit.url'));
+
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php
new file mode 100644
index 0000000000..ee63c6a2ed
--- /dev/null
+++ b/app/Http/Controllers/Transaction/SplitController.php
@@ -0,0 +1,361 @@
+getAccountsByType(['Default account', 'Asset account']));
+ $sessionData = session('journal-data', []);
+ $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
+ $currencies = ExpandedForm::makeSelectList($currencyRepository->get());
+ $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
+ $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanksWithAmount());
+ $subTitle = trans('form.add_new_' . $sessionData['what']);
+ $subTitleIcon = 'fa-plus';
+ $preFilled = [
+ 'what' => $sessionData['what'] ?? 'withdrawal',
+ 'journal_amount' => $sessionData['amount'] ?? 0,
+ 'journal_source_account_id' => $sessionData['source_account_id'] ?? 0,
+ 'journal_source_account_name' => $sessionData['source_account_name'] ?? '',
+ 'description' => [$journal->description],
+ 'destination_account_name' => [$sessionData['destination_account_name']],
+ 'destination_account_id' => [$sessionData['destination_account_id']],
+ 'amount' => [$sessionData['amount']],
+ 'budget_id' => [$sessionData['budget_id']],
+ 'category' => [$sessionData['category']],
+ ];
+
+ return view(
+ 'split.journals.create',
+ compact('journal', 'piggyBanks', 'subTitle', 'subTitleIcon', 'preFilled', 'assetAccounts', 'currencies', 'budgets', 'uploadSize')
+ );
+ }
+
+ /**
+ * @param Request $request
+ * @param TransactionJournal $journal
+ *
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
+ */
+ public function edit(Request $request, TransactionJournal $journal)
+ {
+ $currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface');
+ $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
+ $currencies = ExpandedForm::makeSelectList($currencyRepository->get());
+ $assetAccounts = ExpandedForm::makeSelectList($crud->getAccountsByType(['Default account', 'Asset account']));
+ $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
+ $preFilled = $this->arrayFromJournal($request, $journal);
+ $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
+ $subTitleIcon = 'fa-pencil';
+
+ Session::flash('gaEventCategory', 'transactions');
+ Session::flash('gaEventAction', 'edit-split-' . $preFilled['what']);
+
+ // put previous url in session if not redirect from store (not "return_to_edit").
+ if (session('transactions.edit-split.fromUpdate') !== true) {
+ Session::put('transactions.edit-split.url', URL::previous());
+ }
+ Session::forget('transactions.edit-split.fromUpdate');
+
+ return view(
+ 'split.journals.edit',
+ compact(
+ 'subTitleIcon', 'currencies', 'preFilled', 'subTitle', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts',
+ 'budgets', 'journal'
+ )
+ );
+ }
+
+ /**
+ * @param JournalInterface $repository
+ * @param SplitJournalFormRequest $request
+ * @param TransactionJournal $journal
+ *
+ * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ */
+ public function store(JournalInterface $repository, SplitJournalFormRequest $request, TransactionJournal $journal)
+ {
+ $data = $request->getSplitData();
+ foreach ($data['transactions'] as $transaction) {
+ $repository->storeTransaction($journal, $transaction);
+ }
+
+ $repository->markAsComplete($journal);
+
+ Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
+ Preferences::mark();
+
+ if (intval($request->get('create_another')) === 1) {
+ // set value so create routine will not overwrite URL:
+ Session::put('transactions.create.fromStore', true);
+
+ return redirect(route('transactions.create', [$request->input('what')]))->withInput();
+ }
+
+ // redirect to previous URL.
+ return redirect(session('transactions.create.url'));
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param SplitJournalFormRequest $request
+ * @param JournalInterface $repository
+ * @param AttachmentHelperInterface $att
+ *
+ * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+ */
+ public function update(TransactionJournal $journal, SplitJournalFormRequest $request, JournalInterface $repository, AttachmentHelperInterface $att)
+ {
+
+ $data = $request->getSplitData();
+ $journal = $repository->updateJournal($journal, $data);
+
+ // save attachments:
+ $att->saveAttachmentsForModel($journal);
+
+ event(new TransactionJournalUpdated($journal));
+ // update, get events by date and sort DESC
+
+ // flash messages
+ if (count($att->getMessages()->get('attachments')) > 0) {
+ Session::flash('info', $att->getMessages()->get('attachments'));
+ }
+
+
+ $type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal));
+ Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])])));
+ Preferences::mark();
+
+ if (intval($request->get('return_to_edit')) === 1) {
+ // set value so edit routine will not overwrite URL:
+ Session::put('transactions.edit-split.fromUpdate', true);
+
+ return redirect(route('split.journal.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
+ }
+
+ // redirect to previous URL.
+ return redirect(session('transactions.edit-split.url'));
+
+ }
+
+ /**
+ * @param Request $request
+ * @param TransactionJournal $journal
+ *
+ * @return array
+ */
+ private function arrayFromJournal(Request $request, TransactionJournal $journal): array
+ {
+ $sourceAccounts = TransactionJournal::sourceAccountList($journal);
+ $destinationAccounts = TransactionJournal::destinationAccountList($journal);
+ $array = [
+ 'journal_description' => $request->old('journal_description', $journal->description),
+ 'journal_amount' => TransactionJournal::amountPositive($journal),
+ 'sourceAccounts' => $sourceAccounts,
+ 'journal_source_account_id' => $sourceAccounts->first()->id,
+ 'journal_source_account_name' => $sourceAccounts->first()->name,
+ 'journal_destination_account_id' => $destinationAccounts->first()->id,
+ 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id),
+ 'destinationAccounts' => $destinationAccounts,
+ 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)),
+ 'date' => $request->old('date', $journal->date),
+ 'interest_date' => $request->old('interest_date', $journal->interest_date),
+ 'book_date' => $request->old('book_date', $journal->book_date),
+ 'process_date' => $request->old('process_date', $journal->process_date),
+ 'description' => [],
+ 'source_account_id' => [],
+ 'source_account_name' => [],
+ 'destination_account_id' => [],
+ 'destination_account_name' => [],
+ 'amount' => [],
+ 'budget_id' => [],
+ 'category' => [],
+ ];
+
+ // number of transactions present in old input:
+ $previousCount = count($request->old('description'));
+
+ if ($previousCount === 0) {
+ // build from scratch
+ $transactions = $this->transactionsFromJournal($request, $journal);
+ $array['description'] = $transactions['description'];
+ $array['source_account_id'] = $transactions['source_account_id'];
+ $array['source_account_name'] = $transactions['source_account_name'];
+ $array['destination_account_id'] = $transactions['destination_account_id'];
+ $array['destination_account_name'] = $transactions['destination_account_name'];
+ $array['amount'] = $transactions['amount'];
+ $array['budget_id'] = $transactions['budget_id'];
+ $array['category'] = $transactions['category'];
+
+ return $array;
+ }
+
+ $index = 0;
+ while ($index < $previousCount) {
+ $description = $request->old('description')[$index] ?? '';
+ $destinationId = $request->old('destination_account_id')[$index] ?? 0;
+ $destinationName = $request->old('destination_account_name')[$index] ?? '';
+ $sourceId = $request->old('source_account_id')[$index] ?? 0;
+ $sourceName = $request->old('source_account_name')[$index] ?? '';
+ $amount = $request->old('amount')[$index] ?? '';
+ $budgetId = $request->old('budget_id')[$index] ?? 0;
+ $categoryName = $request->old('category')[$index] ?? '';
+
+
+ // any transfer not from the source:
+ $array['description'][] = $description;
+ $array['source_account_id'][] = $sourceId;
+ $array['source_account_name'][] = $sourceName;
+ $array['destination_account_id'][] = $destinationId;
+ $array['destination_account_name'][] = $destinationName;
+ $array['amount'][] = $amount;
+ $array['budget_id'][] = intval($budgetId);
+ $array['category'][] = $categoryName;
+ $index++;
+ }
+
+ return $array;
+ }
+
+ /**
+ * @param Request $request
+ * @param TransactionJournal $journal
+ *
+ * @return array
+ */
+ private function transactionsFromJournal(Request $request, TransactionJournal $journal): array
+ {
+ /** @var Collection $transactions */
+ $transactions = $journal->transactions()->get();
+
+ /*
+ * Splitted journals always have ONE source OR ONE destination.
+ * Withdrawals have ONE source (asset account)
+ * Deposits have ONE destination (asset account)
+ * Transfers have ONE of both (asset account)
+ */
+ /** @var Account $singular */
+ $singular = TransactionJournal::sourceAccountList($journal)->first();
+ if ($journal->transactionType->type == TransactionType::DEPOSIT) {
+ /** @var Account $singular */
+ $singular = TransactionJournal::destinationAccountList($journal)->first();
+ }
+
+ /*
+ * Loop all transactions. Collect info ONLY from the transaction that is NOT related to
+ * the singular account.
+ */
+ $index = 0;
+ $return = [
+ 'description' => [],
+ 'source_account_id' => [],
+ 'source_account_name' => [],
+ 'destination_account_id' => [],
+ 'destination_account_name' => [],
+ 'amount' => [],
+ 'budget_id' => [],
+ 'category' => [],
+ ];
+
+ Log::debug('now at transactionsFromJournal');
+
+ /**
+ * @var int $current
+ * @var Transaction $transaction
+ */
+ foreach ($transactions as $current => $transaction) {
+ $budget = $transaction->budgets()->first();
+ $category = $transaction->categories()->first();
+ $budgetId = 0;
+ $categoryName = '';
+ if (!is_null($budget)) {
+ $budgetId = $budget->id;
+ }
+
+ if (!is_null($category)) {
+ $categoryName = $category->name;
+ }
+
+ $budgetId = $request->old('budget_id')[$index] ?? $budgetId;
+ $categoryName = $request->old('category')[$index] ?? $categoryName;
+ $amount = $request->old('amount')[$index] ?? $transaction->amount;
+ $description = $request->old('description')[$index] ?? $transaction->description;
+ $destinationName = $request->old('destination_account_name')[$index] ?? $transaction->account->name;
+ $sourceName = $request->old('source_account_name')[$index] ?? $transaction->account->name;
+ $amount = bccomp($amount, '0') === -1 ? bcmul($amount, '-1') : $amount;
+
+ if ($transaction->account_id !== $singular->id) {
+ $return['description'][] = $description;
+ $return['destination_account_id'][] = $transaction->account_id;
+ $return['destination_account_name'][] = $destinationName;
+ $return['source_account_name'][] = $sourceName;
+ $return['amount'][] = $amount;
+ $return['budget_id'][] = intval($budgetId);
+ $return['category'][] = $categoryName;
+ // only add one when "valid" transaction
+ $index++;
+ }
+ }
+
+ return $return;
+ }
+
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php
index 1a34d604c5..3250147493 100644
--- a/app/Http/Controllers/TransactionController.php
+++ b/app/Http/Controllers/TransactionController.php
@@ -1,28 +1,27 @@
-getAccounts(['Default account', 'Asset account']));
- $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get());
- $budgets[0] = trans('firefly.no_budget');
- $piggyBanks = Auth::user()->piggyBanks()->orderBy('order', 'ASC')->get();
- /** @var PiggyBank $piggy */
- foreach ($piggyBanks as $piggy) {
- $piggy->name = $piggy->name . ' (' . Amount::format($piggy->currentRelevantRep()->currentamount, false) . ')';
- }
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+ $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
+ $what = strtolower($what);
+ $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
+ $assetAccounts = ExpandedForm::makeSelectList($crud->getAccountsByType(['Default account', 'Asset account']));
+ $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
+ $piggyBanks = $piggyRepository->getPiggyBanksWithAmount();
+ $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
+ $preFilled = Session::has('preFilled') ? session('preFilled') : [];
+ $subTitle = trans('form.add_new_' . $what);
+ $subTitleIcon = 'fa-plus';
- $piggies = ExpandedForm::makeSelectList($piggyBanks);
- $piggies[0] = trans('form.noPiggybank');
- $preFilled = Session::has('preFilled') ? session('preFilled') : [];
- $respondTo = ['account_id', 'account_from_id'];
- $subTitle = trans('form.add_new_' . $what);
-
- foreach ($respondTo as $r) {
- $preFilled[$r] = Input::get($r);
- }
Session::put('preFilled', $preFilled);
// put previous url in session if not redirect from store (not "create another").
if (session('transactions.create.fromStore') !== true) {
- Session::put('transactions.create.url', URL::previous());
+ $url = URL::previous();
+ Session::put('transactions.create.url', $url);
}
Session::forget('transactions.create.fromStore');
Session::flash('gaEventCategory', 'transactions');
@@ -90,7 +80,7 @@ class TransactionController extends Controller
asort($piggies);
- return view('transactions.create', compact('accounts', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle'));
+ return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle'));
}
/**
@@ -123,7 +113,7 @@ class TransactionController extends Controller
*/
public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal)
{
- $type = strtolower($transactionJournal->transaction_type_type ?? TransactionJournal::transactionTypeStr($transactionJournal));
+ $type = TransactionJournal::transactionTypeStr($transactionJournal);
Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)])));
$repository->delete($transactionJournal);
@@ -141,49 +131,45 @@ class TransactionController extends Controller
*/
public function edit(TransactionJournal $journal)
{
- /** @var ARI $accountRepository */
- $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
- /** @var BudgetRepositoryInterface $budgetRepository */
- $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
- /** @var PiggyBankRepositoryInterface $piggyRepository */
- $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
-
- $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account']));
- $budgetList = ExpandedForm::makeSelectList($budgetRepository->getActiveBudgets());
- $piggyBankList = ExpandedForm::makeSelectList($piggyRepository->getPiggyBanks());
- $budgetList[0] = trans('firefly.no_budget');
- $piggyBankList[0] = trans('form.noPiggybank');
- $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
- $maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
- $uploadSize = min($maxFileSize, $maxPostSize);
- $what = strtolower(TransactionJournal::transactionTypeStr($journal));
- $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
-
-
- $preFilled = [
- 'date' => TransactionJournal::dateAsString($journal),
- 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'),
- 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'),
- 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'),
- 'category' => TransactionJournal::categoryAsString($journal),
- 'budget_id' => TransactionJournal::budgetId($journal),
- 'piggy_bank_id' => TransactionJournal::piggyBankId($journal),
- 'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
- 'account_from_id' => TransactionJournal::sourceAccount($journal)->id,
- 'account_to_id' => TransactionJournal::destinationAccount($journal)->id,
- 'amount' => TransactionJournal::amountPositive($journal),
+ $count = $journal->transactions()->count();
+ if ($count > 2) {
+ return redirect(route('split.journal.edit', [$journal->id]));
+ }
+ $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+ $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
+ $crud = app('FireflyIII\Crud\Account\AccountCrudInterface');
+ $assetAccounts = ExpandedForm::makeSelectList($crud->getAccountsByType(['Default account', 'Asset account']));
+ $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
+ $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks());
+ $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
+ $maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
+ $uploadSize = min($maxFileSize, $maxPostSize);
+ $what = strtolower(TransactionJournal::transactionTypeStr($journal));
+ $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
+ $sourceAccounts = TransactionJournal::sourceAccountList($journal);
+ $destinationAccounts = TransactionJournal::destinationAccountList($journal);
+ $preFilled = [
+ 'date' => TransactionJournal::dateAsString($journal),
+ 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'),
+ 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'),
+ 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'),
+ 'category' => TransactionJournal::categoryAsString($journal),
+ 'budget_id' => TransactionJournal::budgetId($journal),
+ 'piggy_bank_id' => TransactionJournal::piggyBankId($journal),
+ 'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
+ 'source_account_id' => $sourceAccounts->first()->id,
+ 'source_account_name' => $sourceAccounts->first()->name,
+ 'destination_account_id' => $destinationAccounts->first()->id,
+ 'destination_account_name' => $destinationAccounts->first()->name,
+ 'amount' => TransactionJournal::amountPositive($journal),
];
- if ($journal->isWithdrawal()) {
- $preFilled['account_id'] = TransactionJournal::sourceAccount($journal)->id;
- if (TransactionJournal::destinationAccountTypeStr($journal) != 'Cash account') {
- $preFilled['expense_account'] = TransactionJournal::destinationAccount($journal)->name;
- }
- } else {
- $preFilled['account_id'] = TransactionJournal::destinationAccount($journal)->id;
- if (TransactionJournal::sourceAccountTypeStr($journal) != 'Cash account') {
- $preFilled['revenue_account'] = TransactionJournal::sourceAccount($journal)->name;
- }
+ if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
+ $preFilled['destination_account_name'] = '';
+ }
+
+ if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) {
+ $preFilled['source_account_name'] = '';
}
@@ -197,26 +183,26 @@ class TransactionController extends Controller
}
Session::forget('transactions.edit.fromUpdate');
-
- return view('transactions.edit', compact('journal', 'uploadSize', 'accountList', 'what', 'budgetList', 'piggyBankList', 'subTitle'))->with(
+ return view('transactions.edit', compact('journal', 'uploadSize', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle'))->with(
'data', $preFilled
);
}
/**
+ * @param Request $request
* @param JournalRepositoryInterface $repository
- * @param $what
+ * @param string $what
*
- * @return \Illuminate\View\View
+ * @return View
*/
- public function index(JournalRepositoryInterface $repository, string $what)
+ public function index(Request $request, JournalRepositoryInterface $repository, string $what)
{
$pageSize = Preferences::get('transactionPageSize', 50)->data;
- $subTitleIcon = Config::get('firefly.transactionIconsByWhat.' . $what);
- $types = Config::get('firefly.transactionTypesByWhat.' . $what);
+ $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
+ $types = config('firefly.transactionTypesByWhat.' . $what);
$subTitle = trans('firefly.title_' . $what);
- $page = intval(Input::get('page'));
- $journals = $repository->getJournalsOfTypes($types, $page, $pageSize);
+ $page = intval($request->get('page'));
+ $journals = $repository->getJournals($types, $page, $pageSize);
$journals->setPath('transactions/' . $what);
@@ -225,161 +211,20 @@ class TransactionController extends Controller
}
/**
- * @param Collection $journals
- *
- * @return View
- */
- public function massDelete(Collection $journals)
- {
- $subTitle = trans('firefly.mass_delete_journals');
-
- // put previous url in session
- Session::put('transactions.mass-delete.url', URL::previous());
- Session::flash('gaEventCategory', 'transactions');
- Session::flash('gaEventAction', 'mass-delete');
-
- return view('transactions.mass-delete', compact('journals', 'subTitle'));
-
- }
-
- /**
- * @param MassDeleteJournalRequest $request
+ * @param Request $request
* @param JournalRepositoryInterface $repository
*
- * @return mixed
+ * @return \Illuminate\Http\JsonResponse
*/
- public function massDestroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository)
+ public function reorder(Request $request, JournalRepositoryInterface $repository)
{
- $ids = $request->get('confirm_mass_delete');
- $set = new Collection;
- if (is_array($ids)) {
- /** @var int $journalId */
- foreach ($ids as $journalId) {
- /** @var TransactionJournal $journal */
- $journal = $repository->find($journalId);
- if (!is_null($journal->id) && $journalId == $journal->id) {
- $set->push($journal);
- }
- }
- }
- unset($journal);
- $count = 0;
-
- /** @var TransactionJournal $journal */
- foreach ($set as $journal) {
- $repository->delete($journal);
- $count++;
- }
-
- Preferences::mark();
- Session::flash('success', trans('firefly.mass_deleted_transactions_success', ['amount' => $count]));
-
- // redirect to previous URL:
- return redirect(session('transactions.mass-delete.url'));
-
- }
-
- /**
- * @param Collection $journals
- */
- public function massEdit(Collection $journals)
- {
- $subTitle = trans('firefly.mass_edit_journals');
- /** @var ARI $accountRepository */
- $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
- $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account']));
-
- // put previous url in session
- Session::put('transactions.mass-edit.url', URL::previous());
- Session::flash('gaEventCategory', 'transactions');
- Session::flash('gaEventAction', 'mass-edit');
-
- return view('transactions.mass-edit', compact('journals', 'subTitle', 'accountList'));
- }
-
- /**
- *
- */
- public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository)
- {
- $journalIds = Input::get('journals');
- $count = 0;
- if (is_array($journalIds)) {
- foreach ($journalIds as $journalId) {
- $journal = $repository->find(intval($journalId));
- if ($journal) {
- // do update.
-
- // get optional fields:
- $what = strtolower(TransactionJournal::transactionTypeStr($journal));
- $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0;
- $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0;
- $expenseAccount = $request->get('expense_account')[$journal->id] ?? '';
- $revenueAccount = $request->get('revenue_account')[$journal->id] ?? '';
- $budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0;
- $category = $journal->categories->first() ? $journal->categories->first()->name : '';
- $tags = $journal->tags->pluck('tag')->toArray();
-
- // for a deposit, the 'account_id' is the account the money is deposited on.
- // needs a better way of handling.
- // more uniform source/destination field names
- $accountId = $sourceAccountId;
- if ($what == 'deposit') {
- $accountId = $destAccountId;
- }
-
- // build data array
- $data = [
- 'id' => $journal->id,
- 'what' => $what,
- 'description' => $request->get('description')[$journal->id],
- 'account_id' => intval($accountId),
- 'account_from_id' => intval($sourceAccountId),
- 'account_to_id' => intval($destAccountId),
- 'expense_account' => $expenseAccount,
- 'revenue_account' => $revenueAccount,
- 'amount' => round($request->get('amount')[$journal->id], 4),
- 'user' => Auth::user()->id,
- 'amount_currency_id_amount' => intval($request->get('amount_currency_id_amount_' . $journal->id)),
- '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' => $budgetId,
- 'category' => $category,
- 'tags' => $tags,
-
- ];
- // call repository update function.
- $repository->update($journal, $data);
-
- $count++;
- }
- }
- }
- Preferences::mark();
- Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
-
- // redirect to previous URL:
- return redirect(session('transactions.mass-edit.url'));
-
- }
-
- /**
- * @param JournalRepositoryInterface $repository
- *
- * @return \Symfony\Component\HttpFoundation\Response
- */
- public function reorder(JournalRepositoryInterface $repository)
- {
- $ids = Input::get('items');
- $date = new Carbon(Input::get('date'));
+ $ids = $request->get('items');
+ $date = new Carbon($request->get('date'));
if (count($ids) > 0) {
$order = 0;
foreach ($ids as $id) {
-
- $journal = $repository->getWithDate($id, $date);
- if ($journal) {
+ $journal = $repository->find($id);
+ if ($journal && $journal->date->format('Y-m-d') == $date->format('Y-m-d')) {
$journal->order = $order;
$order++;
$journal->save();
@@ -393,53 +238,67 @@ class TransactionController extends Controller
}
/**
- * @param JournalRepositoryInterface $repository
* @param TransactionJournal $journal
+ * @param JournalRepositoryInterface $repository
*
- * @return \Illuminate\View\View
+ * @return View
*/
- public function show(JournalRepositoryInterface $repository, TransactionJournal $journal)
+ public function show(TransactionJournal $journal, JournalRepositoryInterface $repository)
{
+ $events = $repository->getPiggyBankEvents($journal);
+ $transactions = $repository->getTransactions($journal);
+ $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
+ $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
- /** @var Collection $set */
- $events = $journal->piggyBankEvents()->get();
- $events->each(
- function (PiggyBankEvent $event) {
- $event->piggyBank = $event->piggyBank()->withTrashed()->first();
- }
- );
+ if ($transactions->count() > 2) {
+ return view('split.journals.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
+ }
+
+ return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
- $journal->transactions->each(
- function (Transaction $t) use ($journal, $repository) {
- $t->before = $repository->getAmountBefore($journal, $t);
- $t->after = bcadd($t->before, $t->amount);
- }
- );
- $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
- $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
- return view('transactions.show', compact('journal', 'events', 'subTitle', 'what'));
}
/**
* @param JournalFormRequest $request
* @param JournalRepositoryInterface $repository
*
- * @param AttachmentHelperInterface $att
- *
* @return \Illuminate\Http\RedirectResponse
*/
- public function store(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att)
+ public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
{
+ $att = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface');
+ $doSplit = intval($request->get('split_journal')) === 1;
$journalData = $request->getJournalData();
+ // store the journal only, flash the rest.
+ if ($doSplit) {
+ $journal = $repository->storeJournal($journalData);
+
+ // store attachments:
+ $att->saveAttachmentsForModel($journal);
+
+ // flash errors
+ if (count($att->getErrors()->get('attachments')) > 0) {
+ Session::flash('error', $att->getErrors()->get('attachments'));
+ }
+ // flash messages
+ if (count($att->getMessages()->get('attachments')) > 0) {
+ Session::flash('info', $att->getMessages()->get('attachments'));
+ }
+
+ Session::put('journal-data', $journalData);
+
+ return redirect(route('split.journal.create', [$journal->id]));
+ }
+
+
// if not withdrawal, unset budgetid.
if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) {
$journalData['budget_id'] = 0;
}
$journal = $repository->store($journalData);
-
$att->saveAttachmentsForModel($journal);
// flash errors
@@ -451,13 +310,12 @@ class TransactionController extends Controller
Session::flash('info', $att->getMessages()->get('attachments'));
}
- Log::debug('Triggered TransactionJournalStored with transaction journal #' . $journal->id . ' and piggy #' . intval($request->get('piggy_bank_id')));
- event(new TransactionJournalStored($journal, intval($request->get('piggy_bank_id'))));
+ event(new TransactionJournalStored($journal, intval($journalData['piggy_bank_id'])));
Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
Preferences::mark();
- if (intval(Input::get('create_another')) === 1) {
+ if (intval($request->get('create_another')) === 1) {
// set value so create routine will not overwrite URL:
Session::put('transactions.create.fromStore', true);
@@ -480,7 +338,6 @@ class TransactionController extends Controller
*/
public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal)
{
-
$journalData = $request->getJournalData();
$repository->update($journal, $journalData);
@@ -503,7 +360,7 @@ class TransactionController extends Controller
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($journalData['description'])])));
Preferences::mark();
- if (intval(Input::get('return_to_edit')) === 1) {
+ if (intval($request->get('return_to_edit')) === 1) {
// set value so edit routine will not overwrite URL:
Session::put('transactions.edit.fromUpdate', true);
@@ -514,5 +371,4 @@ class TransactionController extends Controller
return redirect(session('transactions.edit.url'));
}
-
}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index c7d8e2bb63..2036bab0d6 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -1,4 +1,12 @@
[
+ 'admin' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
index 483cacaae4..bf71bb329b 100644
--- a/app/Http/Middleware/Authenticate.php
+++ b/app/Http/Middleware/Authenticate.php
@@ -1,4 +1,12 @@
guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
- } else {
- return redirect()->guest('login');
}
- } else {
- if (intval(Auth::user()->blocked) === 1) {
- Auth::guard($guard)->logout();
- Session::flash('logoutMessage', trans('firefly.block_account_logout'));
- return redirect()->guest('login');
- }
+ return redirect()->guest('login');
+ }
+ if (intval(Auth::user()->blocked) === 1) {
+ Auth::guard($guard)->logout();
+ Session::flash('logoutMessage', trans('firefly.block_account_logout'));
+
+ return redirect()->guest('login');
}
return $next($request);
diff --git a/app/Http/Middleware/AuthenticateTwoFactor.php b/app/Http/Middleware/AuthenticateTwoFactor.php
index 0c966add4c..265b55e8ef 100644
--- a/app/Http/Middleware/AuthenticateTwoFactor.php
+++ b/app/Http/Middleware/AuthenticateTwoFactor.php
@@ -1,5 +1,4 @@
guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
- } else {
- return redirect()->guest('login');
}
- } else {
- if (intval(Auth::user()->blocked) === 1) {
- Auth::guard($guard)->logout();
- Session::flash('logoutMessage', trans('firefly.block_account_logout'));
+ return redirect()->guest('login');
+ }
- return redirect()->guest('login');
- }
+ if (intval(Auth::user()->blocked) === 1) {
+ Auth::guard($guard)->logout();
+ Session::flash('logoutMessage', trans('firefly.block_account_logout'));
+
+ return redirect()->guest('login');
}
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', false)->data;
$has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret'));
diff --git a/app/Http/Middleware/Binder.php b/app/Http/Middleware/Binder.php
index a7d2c1d278..c4ae645a99 100644
--- a/app/Http/Middleware/Binder.php
+++ b/app/Http/Middleware/Binder.php
@@ -1,4 +1,12 @@
guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
- } else {
- return redirect()->guest('login');
- }
- } else {
- /** @var User $user */
- $user = Auth::user();
- if (!$user->hasRole('owner')) {
- return redirect(route('home'));
}
+
+ return redirect()->guest('login');
+ }
+ /** @var User $user */
+ $user = Auth::user();
+ if (!$user->hasRole('owner')) {
+ return redirect(route('home'));
}
return $next($request);
diff --git a/app/Http/Middleware/IsConfirmed.php b/app/Http/Middleware/IsConfirmed.php
index 92dd04bfb4..df7638cbcb 100644
--- a/app/Http/Middleware/IsConfirmed.php
+++ b/app/Http/Middleware/IsConfirmed.php
@@ -1,5 +1,4 @@
guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
- } else {
- return redirect()->guest('login');
}
- } else {
- // must the user be confirmed in the first place?
- $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
- // user must be logged in, then continue:
- $isConfirmed = Preferences::get('user_confirmed', false)->data;
- if ($isConfirmed === false && $confirmAccount === true) {
+ return redirect()->guest('login');
+ }
+ // must the user be confirmed in the first place?
+ $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
+ // user must be logged in, then continue:
+ $isConfirmed = Preferences::get('user_confirmed', false)->data;
- // user account is not confirmed, redirect to
- // confirmation page:
- return redirect(route('confirmation_error'));
- }
+ if ($isConfirmed === false && $confirmAccount === true) {
+
+ // user account is not confirmed, redirect to
+ // confirmation page:
+ return redirect(route('confirmation_error'));
}
return $next($request);
diff --git a/app/Http/Middleware/IsNotConfirmed.php b/app/Http/Middleware/IsNotConfirmed.php
index 75f14d6264..e0fb82464f 100644
--- a/app/Http/Middleware/IsNotConfirmed.php
+++ b/app/Http/Middleware/IsNotConfirmed.php
@@ -1,5 +1,4 @@
guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
- } else {
- return redirect()->guest('login');
- }
- } else {
- // must the user be confirmed in the first place?
- $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
- // user must be logged in, then continue:
- $isConfirmed = Preferences::get('user_confirmed', false)->data;
- if ($isConfirmed || $confirmAccount === false) {
- // user account is confirmed, simply send them home.
- return redirect(route('home'));
}
+
+ return redirect()->guest('login');
+ }
+ // must the user be confirmed in the first place?
+ $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
+ // user must be logged in, then continue:
+ $isConfirmed = Preferences::get('user_confirmed', false)->data;
+ if ($isConfirmed || $confirmAccount === false) {
+ // user account is confirmed, simply send them home.
+ return redirect(route('home'));
}
return $next($request);
diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php
index 43b8d5420f..2c9638ec97 100644
--- a/app/Http/Middleware/Range.php
+++ b/app/Http/Middleware/Range.php
@@ -1,11 +1,19 @@
first();
+ $first = Carbon::now()->startOfYear();
+
if (!is_null($journal->id)) {
- Session::put('first', $journal->date);
- } else {
- Session::put('first', Carbon::now()->startOfYear());
+ $first = $journal->date;
}
+ Session::put('first', $first);
}
-
- // check "sum of everything".
-
-
$current = Carbon::now()->formatLocalized('%B %Y');
$next = Carbon::now()->endOfMonth()->addDay()->formatLocalized('%B %Y');
$prev = Carbon::now()->startOfMonth()->subDay()->formatLocalized('%B %Y');
diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php
index 63462dea3d..ef0014b45d 100644
--- a/app/Http/Middleware/RedirectIfAuthenticated.php
+++ b/app/Http/Middleware/RedirectIfAuthenticated.php
@@ -1,4 +1,12 @@
$this->get('what'),
'description' => $this->get('description'),
- 'account_id' => intval($this->get('account_id')),
- 'account_from_id' => intval($this->get('account_from_id')),
- 'account_to_id' => intval($this->get('account_to_id')),
- 'expense_account' => $this->get('expense_account') ?? '',
- 'revenue_account' => $this->get('revenue_account') ?? '',
+ 'source_account_id' => intval($this->get('source_account_id')),
+ 'source_account_name' => $this->get('source_account_name') ?? '',
+ 'destination_account_id' => intval($this->get('destination_account_id')),
+ 'destination_account_name' => $this->get('destination_account_name') ?? '',
'amount' => round($this->get('amount'), 2),
'user' => Auth::user()->id,
'amount_currency_id_amount' => intval($this->get('amount_currency_id_amount')),
@@ -52,6 +59,7 @@ class JournalFormRequest extends Request
'budget_id' => intval($this->get('budget_id')),
'category' => $this->get('category') ?? '',
'tags' => explode(',', $tags),
+ 'piggy_bank_id' => $this->get('piggy_bank_id') ? intval($this->get('piggy_bank_id')) : 0,
];
}
@@ -70,28 +78,27 @@ class JournalFormRequest extends Request
'process_date' => 'date',
'book_date' => 'date',
'interest_date' => 'date',
+ 'category' => 'between:1,255',
'amount_currency_id_amount' => 'required|exists:transaction_currencies,id',
-
+ 'piggy_bank_id' => 'numeric',
];
switch ($what) {
case strtolower(TransactionType::WITHDRAWAL):
- $rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
- $rules['expense_account'] = 'between:1,255';
- $rules['category'] = 'between:1,255';
+ $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
+ $rules['destination_account_name'] = 'between:1,255';
if (intval(Input::get('budget_id')) != 0) {
$rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets';
}
break;
case strtolower(TransactionType::DEPOSIT):
- $rules['category'] = 'between:1,255';
- $rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
- $rules['revenue_account'] = 'between:1,255';
+ $rules['source_account_name'] = 'between:1,255';
+ $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
break;
case strtolower(TransactionType::TRANSFER):
- $rules['account_from_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_to_id';
- $rules['account_to_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_from_id';
- $rules['category'] = 'between:1,255';
+ $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id';
+ $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id';
+
break;
default:
throw new FireflyException('Cannot handle transaction type of type ' . e($what) . '.');
diff --git a/app/Http/Requests/MassDeleteJournalRequest.php b/app/Http/Requests/MassDeleteJournalRequest.php
index 8426a96ab8..967bd9e031 100644
--- a/app/Http/Requests/MassDeleteJournalRequest.php
+++ b/app/Http/Requests/MassDeleteJournalRequest.php
@@ -8,13 +8,6 @@
*/
declare(strict_types = 1);
-/**
- * MassJournalRequest.php
- * Copyright (C) 2016 thegrumpydictator@gmail.com
- *
- * This software may be modified and distributed under the terms
- * of the MIT license. See the LICENSE file for details.
- */
namespace FireflyIII\Http\Requests;
diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php
index 360cd71437..fcd9905221 100644
--- a/app/Http/Requests/MassEditJournalRequest.php
+++ b/app/Http/Requests/MassEditJournalRequest.php
@@ -8,13 +8,6 @@
*/
declare(strict_types = 1);
-/**
- * MassJournalRequest.php
- * Copyright (C) 2016 thegrumpydictator@gmail.com
- *
- * This software may be modified and distributed under the terms
- * of the MIT license. See the LICENSE file for details.
- */
namespace FireflyIII\Http\Requests;
diff --git a/app/Http/Requests/NewUserFormRequest.php b/app/Http/Requests/NewUserFormRequest.php
index d707b02caa..8ddc64fa7d 100644
--- a/app/Http/Requests/NewUserFormRequest.php
+++ b/app/Http/Requests/NewUserFormRequest.php
@@ -1,4 +1,12 @@
$this->get('id') ?? 0,
+ 'journal_description' => $this->get('journal_description'),
+ 'journal_currency_id' => intval($this->get('journal_currency_id')),
+ 'journal_source_account_id' => intval($this->get('journal_source_account_id')),
+ 'journal_source_account_name' => $this->get('journal_source_account_name'),
+ 'journal_destination_account_id' => intval($this->get('journal_destination_account_id')),
+ 'journal_destination_account_name' => $this->get('journal_source_destination_name'),
+ 'date' => new Carbon($this->get('date')),
+ 'what' => $this->get('what'),
+ 'interest_date' => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null,
+ 'book_date' => $this->get('book_date') ? new Carbon($this->get('book_date')) : null,
+ 'process_date' => $this->get('process_date') ? new Carbon($this->get('process_date')) : null,
+ 'transactions' => [],
+ ];
+
+ // description is leading because it is one of the mandatory fields.
+ foreach ($this->get('description') as $index => $description) {
+ $transaction = [
+ 'description' => $description,
+ 'amount' => round($this->get('amount')[$index], 2),
+ 'budget_id' => $this->get('budget_id')[$index] ? intval($this->get('budget_id')[$index]) : 0,
+ 'category' => $this->get('category')[$index] ?? '',
+ 'source_account_id' => intval($this->get('journal_source_account_id')),
+ 'source_account_name' => $this->get('journal_source_account_name'),
+ 'piggy_bank_id' => isset($this->get('piggy_bank_id')[$index])
+ ? intval($this->get('piggy_bank_id')[$index])
+ : 0,
+ 'destination_account_id' => isset($this->get('destination_account_id')[$index])
+ ? intval($this->get('destination_account_id')[$index])
+ : intval($this->get('journal_destination_account_id')),
+ 'destination_account_name' => $this->get('destination_account_name')[$index] ?? '',
+ ];
+ $data['transactions'][] = $transaction;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules(): array
+ {
+ return [
+ 'what' => 'required|in:withdrawal,deposit,transfer',
+ 'journal_description' => 'required|between:1,255',
+ 'id' => 'numeric|belongsToUser:transaction_journals,id',
+ 'journal_source_account_id' => 'numeric|belongsToUser:accounts,id',
+ 'journal_source_account_name.*' => 'between:1,255',
+ 'journal_currency_id' => 'required|exists:transaction_currencies,id',
+ 'date' => 'required|date',
+ 'interest_date' => 'date',
+ 'book_date' => 'date',
+ 'process_date' => 'date',
+
+ 'description.*' => 'required|between:1,255',
+ 'destination_account_id.*' => 'numeric|belongsToUser:accounts,id',
+ 'destination_account_name.*' => 'between:1,255',
+ 'amount.*' => 'required|numeric',
+ 'budget_id.*' => 'belongsToUser:budgets,id',
+ 'category.*' => 'between:1,255',
+ 'piggy_bank_id.*' => 'between:1,255',
+ ];
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Requests/TagFormRequest.php b/app/Http/Requests/TagFormRequest.php
index 531770b38f..9719cd6a94 100644
--- a/app/Http/Requests/TagFormRequest.php
+++ b/app/Http/Requests/TagFormRequest.php
@@ -1,4 +1,12 @@
'required|min:1|in:' . join(',', $validTriggers),
'rule-trigger-value.*' => 'required|min:1|ruleTriggerValue',
diff --git a/app/Http/Requests/TokenFormRequest.php b/app/Http/Requests/TokenFormRequest.php
index 6bfacf9737..b971878f1e 100644
--- a/app/Http/Requests/TokenFormRequest.php
+++ b/app/Http/Requests/TokenFormRequest.php
@@ -1,4 +1,12 @@
accountType->type);
-
-
+ $what = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$breadcrumbs->parent('accounts.index', $what);
$breadcrumbs->push(e($account->name), route('accounts.show', [$account->id]));
}
);
+
+Breadcrumbs::register(
+ 'accounts.show.date', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $date) {
+ $breadcrumbs->parent('accounts.show', $account);
+
+ $range = Preferences::get('viewRange', '1M')->data;
+ $title = $account->name . ' (' . Navigation::periodShow($date, $range) . ')';
+
+ $breadcrumbs->push($title, route('accounts.show.date', [$account->id, $date->format('Y-m-d')]));
+}
+);
+
Breadcrumbs::register(
'accounts.delete', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
$breadcrumbs->parent('accounts.show', $account);
@@ -73,7 +90,7 @@ Breadcrumbs::register(
Breadcrumbs::register(
'accounts.edit', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
$breadcrumbs->parent('accounts.show', $account);
- $what = Config::get('firefly.shortNamesByFullName.' . $account->accountType->type);
+ $what = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => e($account->name)]), route('accounts.edit', [$account->id]));
}
@@ -502,6 +519,7 @@ Breadcrumbs::register(
}
);
+
/**
* TAGS
*/
@@ -579,3 +597,20 @@ Breadcrumbs::register(
}
);
+
+/**
+ * SPLIT
+ */
+Breadcrumbs::register(
+ 'split.journal.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
+ $breadcrumbs->parent('transactions.show', $journal);
+ $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('split.journal.edit', [$journal->id]));
+}
+);
+
+Breadcrumbs::register(
+ 'split.journal.create', function (BreadCrumbGenerator $breadcrumbs, string $what) {
+ $breadcrumbs->parent('transactions.index', $what);
+ $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('split.journal.create', [$what]));
+}
+);
\ No newline at end of file
diff --git a/app/Http/routes.php b/app/Http/routes.php
index c9caa03944..977c3fb18c 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -1,4 +1,12 @@
'AccountController@create', 'as' => 'accounts.create'])->where('what', 'revenue|asset|expense');
Route::get('/accounts/edit/{account}', ['uses' => 'AccountController@edit', 'as' => 'accounts.edit']);
Route::get('/accounts/delete/{account}', ['uses' => 'AccountController@delete', 'as' => 'accounts.delete']);
- Route::get('/accounts/show/{account}/{view?}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']);
+ Route::get('/accounts/show/{account}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']);
+ Route::get('/accounts/show/{account}/{date}', ['uses' => 'AccountController@showWithDate', 'as' => 'accounts.show.date']);
+
Route::post('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']);
Route::post('/accounts/update/{account}', ['uses' => 'AccountController@update', 'as' => 'accounts.update']);
@@ -124,7 +134,8 @@ Route::group(
Route::get('/budgets/create', ['uses' => 'BudgetController@create', 'as' => 'budgets.create']);
Route::get('/budgets/edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'budgets.edit']);
Route::get('/budgets/delete/{budget}', ['uses' => 'BudgetController@delete', 'as' => 'budgets.delete']);
- Route::get('/budgets/show/{budget}/{limitrepetition?}', ['uses' => 'BudgetController@show', 'as' => 'budgets.show']);
+ Route::get('/budgets/show/{budget}', ['uses' => 'BudgetController@show', 'as' => 'budgets.show']);
+ Route::get('/budgets/show/{budget}/{limitrepetition}', ['uses' => 'BudgetController@showWithRepetition', 'as' => 'budgets.showWithRepetition']);
Route::get('/budgets/list/noBudget', ['uses' => 'BudgetController@noBudget', 'as' => 'budgets.noBudget']);
Route::post('/budgets/income', ['uses' => 'BudgetController@postUpdateIncome', 'as' => 'budgets.postIncome']);
Route::post('/budgets/store', ['uses' => 'BudgetController@store', 'as' => 'budgets.store']);
@@ -188,8 +199,9 @@ Route::group(
// accounts:
Route::get('/chart/account/frontpage', ['uses' => 'Chart\AccountController@frontpage']);
Route::get('/chart/account/expense', ['uses' => 'Chart\AccountController@expenseAccounts']);
- Route::get('/chart/account/report/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\AccountController@report']);
+ Route::get('/chart/account/report/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\AccountController@report']);
Route::get('/chart/account/{account}', ['uses' => 'Chart\AccountController@single']);
+ Route::get('/chart/account/{account}/{date}', ['uses' => 'Chart\AccountController@specificPeriod']);
// bills:
@@ -200,9 +212,8 @@ Route::group(
Route::get('/chart/budget/frontpage', ['uses' => 'Chart\BudgetController@frontpage']);
// this chart is used in reports:
- Route::get('/chart/budget/year/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@year']);
- Route::get('/chart/budget/multi-year/{reportType}/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']);
-
+ Route::get('/chart/budget/multi-year/default/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']);
+ Route::get('/chart/budget/period/{budget}/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@period']);
Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'Chart\BudgetController@budgetLimit']);
Route::get('/chart/budget/{budget}', ['uses' => 'Chart\BudgetController@budget']);
@@ -210,11 +221,7 @@ Route::group(
Route::get('/chart/category/frontpage', ['uses' => 'Chart\CategoryController@frontpage']);
// these three charts are for reports:
- Route::get('/chart/category/earned-in-period/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@earnedInPeriod']);
- Route::get('/chart/category/spent-in-period/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@spentInPeriod']);
- Route::get(
- '/chart/category/multi-year/{reportType}/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear']
- );
+ Route::get('/chart/category/multi-year/default/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear']);
Route::get('/chart/category/{category}/period', ['uses' => 'Chart\CategoryController@currentPeriod']);
Route::get('/chart/category/{category}/period/{date}', ['uses' => 'Chart\CategoryController@specificPeriod']);
@@ -341,6 +348,14 @@ Route::group(
*/
Route::get('/search', ['uses' => 'SearchController@index', 'as' => 'search']);
+ /**
+ * Split controller
+ */
+
+ Route::get('/transaction/create-split/{unfinishedJournal}', ['uses' => 'Transaction\SplitController@create', 'as' => 'split.journal.create']);
+ Route::post('/transaction/store-split/{unfinishedJournal}', ['uses' => 'Transaction\SplitController@store', 'as' => 'split.journal.store']);
+ Route::get('/transaction/edit-split/{tj}', ['uses' => 'Transaction\SplitController@edit', 'as' => 'split.journal.edit']);
+ Route::post('/transaction/edit-split/{tj}', ['uses' => 'Transaction\SplitController@update', 'as' => 'split.journal.update']);
/**
* Tag Controller
*/
@@ -378,10 +393,10 @@ Route::group(
Route::post('/transaction/reorder', ['uses' => 'TransactionController@reorder', 'as' => 'transactions.reorder']);
// mass edit and mass delete.
- Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'TransactionController@massEdit', 'as' => 'transactions.mass-edit']);
- Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'TransactionController@massDelete', 'as' => 'transactions.mass-delete']);
- Route::post('/transactions/mass-update', ['uses' => 'TransactionController@massUpdate', 'as' => 'transactions.mass-update']);
- Route::post('/transactions/mass-destroy', ['uses' => 'TransactionController@massDestroy', 'as' => 'transactions.mass-destroy']);
+ Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'Transaction\MassController@massEdit', 'as' => 'transactions.mass-edit']);
+ Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'Transaction\MassController@massDelete', 'as' => 'transactions.mass-delete']);
+ Route::post('/transactions/mass-update', ['uses' => 'Transaction\MassController@massUpdate', 'as' => 'transactions.mass-update']);
+ Route::post('/transactions/mass-destroy', ['uses' => 'Transaction\MassController@massDestroy', 'as' => 'transactions.mass-destroy']);
/**
* POPUP Controllers
diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php
index 20b33230e9..3c791ddeaf 100644
--- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php
+++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php
@@ -1,9 +1,19 @@
accounts, $this->user, $this->startDate, $this->endDate];
- $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args);
+ /** @var JournalRepositoryInterface $repository */
+ $repository = app(JournalRepositoryInterface::class);
- return $journalCollector->collect();
+ return $repository->getJournalsInRange($this->accounts, $this->startDate, $this->endDate);
}
/**
diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php
index 97a3858ccc..65794cc160 100644
--- a/app/Jobs/Job.php
+++ b/app/Jobs/Job.php
@@ -1,4 +1,12 @@
user = $user;
+ $this->userData = $userData;
$this->destination = $destination;
$this->ipAddress = $ipAddress;
$this->exception = $exceptionData;
- Log::debug('In mail job constructor for error handler.');
- Log::error('Exception is: ' . json_encode($exceptionData));
+ $debug = $exceptionData;
+ unset($debug['stackTrace']);
+ Log::error('Exception is: ' . json_encode($debug));
}
/**
@@ -57,14 +66,13 @@ class MailError extends Job implements ShouldQueue
*/
public function handle()
{
- Log::debug('Start of handle()');
if ($this->attempts() < 3) {
// mail?
try {
$email = env('SITE_OWNER');
$args = $this->exception;
- $args['loggedIn'] = !is_null($this->user->id);
- $args['user'] = $this->user;
+ $args['loggedIn'] = $this->userData['id'] > 0;
+ $args['user'] = $this->userData;
$args['ip'] = $this->ipAddress;
Mail::send(
@@ -81,7 +89,6 @@ class MailError extends Job implements ShouldQueue
} catch (ErrorException $e) {
Log::error('ErrorException ' . $e->getMessage());
}
- Log::debug('Successfully handled error.');
}
}
}
diff --git a/app/Models/Account.php b/app/Models/Account.php
index eaddfd0637..9070be24e4 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -1,4 +1,15 @@
-belongsToMany('FireflyIII\Models\TransactionJournal', 'budget_transaction_journal', 'budget_id');
}
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function transactions()
+ {
+ return $this->belongsToMany('FireflyIII\Models\Transaction', 'budget_transaction', 'budget_id');
+ }
+
/**
* @return BelongsTo
*/
diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php
index 884c439e4a..40229622bf 100644
--- a/app/Models/BudgetLimit.php
+++ b/app/Models/BudgetLimit.php
@@ -1,4 +1,15 @@
-belongsToMany('FireflyIII\Models\TransactionJournal', 'category_transaction_journal', 'category_id');
}
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function transactions()
+ {
+ return $this->belongsToMany('FireflyIII\Models\Transaction', 'category_transaction', 'category_id');
+ }
+
/**
* @return BelongsTo
*/
diff --git a/app/Models/Component.php b/app/Models/Component.php
index f4794eb3f2..720f3a53a9 100644
--- a/app/Models/Component.php
+++ b/app/Models/Component.php
@@ -1,4 +1,15 @@
-key . ' to status "' . $status . '".');
$this->status = $status;
$this->save();
}
diff --git a/app/Models/LimitRepetition.php b/app/Models/LimitRepetition.php
index 7257b67689..fb0a77ac40 100644
--- a/app/Models/LimitRepetition.php
+++ b/app/Models/LimitRepetition.php
@@ -1,6 +1,19 @@
-belongsTo('FireflyIII\Models\BudgetLimit');
}
+ /**
+ *
+ * @param Builder $query
+ * @param Carbon $date
+ *
+ */
+ public function scopeAfter(Builder $query, Carbon $date)
+ {
+ $query->where('limit_repetitions.startdate', '>=', $date->format('Y-m-d 00:00:00'));
+ }
+
+ /**
+ *
+ * @param Builder $query
+ * @param Carbon $date
+ *
+ */
+ public function scopeBefore(Builder $query, Carbon $date)
+ {
+ $query->where('limit_repetitions.enddate', '<=', $date->format('Y-m-d 00:00:00'));
+ }
+
/**
* @param $value
*/
diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php
index 7d9b753900..78749a8e5e 100644
--- a/app/Models/PiggyBank.php
+++ b/app/Models/PiggyBank.php
@@ -1,4 +1,15 @@
- 'between:1,255',
'amount' => 'required|numeric',
];
- protected $dates = ['created_at', 'updated_at', 'deleted_at'];
use SoftDeletes, ValidatingTrait;
+ /**
+ * @param Builder $query
+ * @param string $table
+ *
+ * @return bool
+ */
+ public static function isJoined(Builder $query, string $table):bool
+ {
+ $joins = $query->getQuery()->joins;
+ if (is_null($joins)) {
+ return false;
+ }
+ foreach ($joins as $join) {
+ if ($join->table === $table) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
@@ -57,6 +92,22 @@ class Transaction extends Model
return $this->belongsTo('FireflyIII\Models\Account');
}
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function budgets()
+ {
+ return $this->belongsToMany('FireflyIII\Models\Budget');
+ }
+
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function categories()
+ {
+ return $this->belongsToMany('FireflyIII\Models\Category');
+ }
+
/**
* @param $value
*
@@ -68,25 +119,47 @@ class Transaction extends Model
}
/**
- * @param EloquentBuilder $query
- * @param Carbon $date
*
- * @return mixed
+ * @param Builder $query
+ * @param Carbon $date
*/
- public function scopeAfter(EloquentBuilder $query, Carbon $date)
+ public function scopeAfter(Builder $query, Carbon $date)
{
- return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00'));
+ if (!self::isJoined($query, 'transaction_journals')) {
+ $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
+ }
+ $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00'));
}
/**
- * @param EloquentBuilder $query
- * @param Carbon $date
*
- * @return mixed
+ * @param Builder $query
+ * @param Carbon $date
+ *
*/
- public function scopeBefore(EloquentBuilder $query, Carbon $date)
+ public function scopeBefore(Builder $query, Carbon $date)
{
- return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00'));
+ if (!self::isJoined($query, 'transaction_journals')) {
+ $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
+ }
+ $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'));
+ }
+
+ /**
+ *
+ * @param Builder $query
+ * @param array $types
+ */
+ public function scopeTransactionTypes(Builder $query, array $types)
+ {
+ if (!self::isJoined($query, 'transaction_journals')) {
+ $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
+ }
+
+ if (!self::isJoined($query, 'transaction_types')) {
+ $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
+ }
+ $query->whereIn('transaction_types.type', $types);
}
/**
diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php
index 736f7fdb09..69d33955b6 100644
--- a/app/Models/TransactionCurrency.php
+++ b/app/Models/TransactionCurrency.php
@@ -1,4 +1,15 @@
- 0) {
- return Crypt::decrypt($value);
- }
-
- return null;
- }
-
/**
*
* @param string $fieldName
@@ -227,21 +203,6 @@ class TransactionJournal extends TransactionJournalSupport
return '';
}
- /**
- * @param $value
- *
- * @return string
- */
- public function getSourceAccountNameAttribute($value)
- {
- if (!is_null($value) && strlen(strval($value)) > 0) {
- return Crypt::decrypt($value);
- }
-
- return null;
-
- }
-
/**
* @return bool
*/
@@ -354,32 +315,18 @@ class TransactionJournal extends TransactionJournalSupport
$query->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id');
// left join destination (for amount and account info).
- $query->leftJoin(
- 'transactions as destination', function (JoinClause $join) {
- $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('destination.amount', '>', 0);
- }
- );
- // join destination account
- $query->leftJoin('accounts as destination_account', 'destination_account.id', '=', 'destination.account_id');
- // join destination account type
- $query->leftJoin('account_types as destination_acct_type', 'destination_account.account_type_id', '=', 'destination_acct_type.id');
-
- // left join source (for amount and account info).
- $query->leftJoin(
- 'transactions as source', function (JoinClause $join) {
- $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('source.amount', '<', 0);
- }
- );
- // join destination account
- $query->leftJoin('accounts as source_account', 'source_account.id', '=', 'source.account_id');
- // join destination account type
- $query->leftJoin('account_types as source_acct_type', 'source_account.account_type_id', '=', 'source_acct_type.id')
- ->orderBy('transaction_journals.date', 'DESC')->orderBy('transaction_journals.order', 'ASC')->orderBy('transaction_journals.id', 'DESC');
-
- $query->with(['categories', 'budgets', 'attachments', 'bill']);
+ $query->groupBy('transaction_journals.id');
+ $query->with(['categories', 'budgets', 'attachments', 'bill', 'transactions']);
+ }
+ /**
+ * @param EloquentBuilder $query
+ */
+ public function scopeSortCorrectly(EloquentBuilder $query)
+ {
+ $query->orderBy('transaction_journals.date', 'DESC');
+ $query->orderBy('transaction_journals.order', 'ASC');
+ $query->orderBy('transaction_journals.id', 'DESC');
}
diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php
index 19687815cf..801ab0e3df 100644
--- a/app/Models/TransactionJournalMeta.php
+++ b/app/Models/TransactionJournalMeta.php
@@ -1,5 +1,4 @@
app->bind(
'FireflyIII\Repositories\Account\AccountRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Account\AccountRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Account\AccountRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Account\AccountRepository', $arguments);
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 4475d28529..e2ab998575 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -1,11 +1,19 @@
getHandlers() as $handler) {
- $handler->setLevel(Config::get('app.log-level'));
+ $handler->setLevel(config('app.log-level'));
}
}
}
diff --git a/app/Providers/AttachmentServiceProvider.php b/app/Providers/AttachmentServiceProvider.php
index eb35505e2f..bee83cf88c 100644
--- a/app/Providers/AttachmentServiceProvider.php
+++ b/app/Providers/AttachmentServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Attachment\AttachmentRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Attachment\AttachmentRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Attachment\AttachmentRepository', $arguments);
diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php
index db0aa37305..b24e9e542a 100644
--- a/app/Providers/AuthServiceProvider.php
+++ b/app/Providers/AuthServiceProvider.php
@@ -1,4 +1,12 @@
app->bind(
'FireflyIII\Repositories\Bill\BillRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Bill\BillRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Bill\BillRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Bill\BillRepository', $arguments);
diff --git a/app/Providers/BudgetServiceProvider.php b/app/Providers/BudgetServiceProvider.php
index 02364b9eb5..afd73a846b 100644
--- a/app/Providers/BudgetServiceProvider.php
+++ b/app/Providers/BudgetServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\Budget\BudgetRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Budget\BudgetRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Budget\BudgetRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Budget\BudgetRepository', $arguments);
diff --git a/app/Providers/CategoryServiceProvider.php b/app/Providers/CategoryServiceProvider.php
index 6e8ce7decd..5adab5c229 100644
--- a/app/Providers/CategoryServiceProvider.php
+++ b/app/Providers/CategoryServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\Category\CategoryRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Category\CategoryRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Category\CategoryRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Category\CategoryRepository', $arguments);
}
);
- $this->app->bind(
- 'FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface',
- function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Category\SingleCategoryRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
- }
-
- return app('FireflyIII\Repositories\Category\SingleCategoryRepository', $arguments);
- }
- );
-
}
}
diff --git a/app/Providers/CrudServiceProvider.php b/app/Providers/CrudServiceProvider.php
new file mode 100644
index 0000000000..0117cf78a5
--- /dev/null
+++ b/app/Providers/CrudServiceProvider.php
@@ -0,0 +1,81 @@
+registerJournal();
+ $this->registerAccount();
+ }
+
+ private function registerAccount()
+ {
+
+ $this->app->bind(
+ 'FireflyIII\Crud\Account\AccountCrudInterface',
+ function (Application $app, array $arguments) {
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Crud\Account\AccountCrud', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
+ }
+
+ return app('FireflyIII\Crud\Account\AccountCrud', $arguments);
+ }
+ );
+ }
+
+ private function registerJournal()
+ {
+ $this->app->bind(
+ 'FireflyIII\Crud\Split\JournalInterface',
+ function (Application $app, array $arguments) {
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Crud\Split\Journal', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
+ }
+
+ return app('FireflyIII\Crud\Split\Journal', $arguments);
+ }
+ );
+
+ }
+}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index 929c1d5a3a..2b91f62b09 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -1,20 +1,22 @@
[
+
+ 'FireflyIII\Events\BudgetLimitStored' => [
+ 'FireflyIII\Handlers\Events\BudgetLimitEventHandler@store',
+ ],
+ 'FireflyIII\Events\BudgetLimitUpdated' => [
+ 'FireflyIII\Handlers\Events\BudgetLimitEventHandler@update',
+ ],
+ 'FireflyIII\Events\TransactionStored' => [
+ 'FireflyIII\Handlers\Events\ConnectTransactionToPiggyBank',
+ ],
+ 'FireflyIII\Events\TransactionJournalStored' => [
'FireflyIII\Handlers\Events\ScanForBillsAfterStore',
'FireflyIII\Handlers\Events\ConnectJournalToPiggyBank',
'FireflyIII\Handlers\Events\FireRulesForStore',
],
- 'Illuminate\Auth\Events\Logout' => [
+ 'Illuminate\Auth\Events\Logout' => [
'FireflyIII\Handlers\Events\UserEventListener@onUserLogout',
],
- 'FireflyIII\Events\UserRegistration' => [
+ 'FireflyIII\Events\UserRegistration' => [
'FireflyIII\Handlers\Events\SendRegistrationMail',
'FireflyIII\Handlers\Events\AttachUserRole',
'FireflyIII\Handlers\Events\UserConfirmation@sendConfirmation',
],
- 'FireflyIII\Events\ResendConfirmation' => [
+ 'FireflyIII\Events\ResendConfirmation' => [
'FireflyIII\Handlers\Events\UserConfirmation@resendConfirmation',
],
];
@@ -66,39 +78,6 @@ class EventServiceProvider extends ServiceProvider
parent::boot($events);
$this->registerDeleteEvents();
$this->registerCreateEvents();
- BudgetLimit::saved(
- function (BudgetLimit $budgetLimit) {
- $end = Navigation::addPeriod(clone $budgetLimit->startdate, $budgetLimit->repeat_freq, 0);
- $end->subDay();
- $set = $budgetLimit->limitrepetitions()
- ->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
- ->where('enddate', $end->format('Y-m-d 00:00:00'))
- ->get();
- if ($set->count() == 0) {
- $repetition = new LimitRepetition;
- $repetition->startdate = $budgetLimit->startdate;
- $repetition->enddate = $end;
- $repetition->amount = $budgetLimit->amount;
- $repetition->budgetLimit()->associate($budgetLimit);
-
- try {
- $repetition->save();
- } catch (QueryException $e) {
- Log::error('Trying to save new LimitRepetition failed: ' . $e->getMessage());
- }
- } else {
- if ($set->count() == 1) {
- $repetition = $set->first();
- $repetition->amount = $budgetLimit->amount;
- $repetition->save();
-
- }
- }
- }
- );
-
-
- //
}
/**
@@ -126,16 +105,6 @@ class EventServiceProvider extends ServiceProvider
*/
protected function registerDeleteEvents()
{
- TransactionJournal::deleted(
- function (TransactionJournal $journal) {
-
- /** @var Transaction $transaction */
- foreach ($journal->transactions()->get() as $transaction) {
- $transaction->delete();
- }
- }
- );
-
Account::deleted(
function (Account $account) {
diff --git a/app/Providers/ExportJobServiceProvider.php b/app/Providers/ExportJobServiceProvider.php
index b63d2b8819..380b14a2e9 100644
--- a/app/Providers/ExportJobServiceProvider.php
+++ b/app/Providers/ExportJobServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\ExportJob\ExportJobRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\ExportJob\ExportJobRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\ExportJob\ExportJobRepository', $arguments);
diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php
index 9994b5ba36..47ad19c301 100644
--- a/app/Providers/FireflyServiceProvider.php
+++ b/app/Providers/FireflyServiceProvider.php
@@ -1,4 +1,12 @@
app->bind(
'preferences', function () {
return new Preferences;
@@ -81,18 +84,11 @@ class FireflyServiceProvider extends ServiceProvider
}
);
-
$this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository');
$this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search');
$this->app->bind('FireflyIII\Repositories\User\UserRepositoryInterface', 'FireflyIII\Repositories\User\UserRepository');
-
- // CSV import
$this->app->bind('FireflyIII\Helpers\Csv\WizardInterface', 'FireflyIII\Helpers\Csv\Wizard');
-
- // attachments
$this->app->bind('FireflyIII\Helpers\Attachments\AttachmentHelperInterface', 'FireflyIII\Helpers\Attachments\AttachmentHelper');
-
- // make charts:
$this->app->bind(
'FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface', 'FireflyIII\Generator\Chart\Account\ChartJsAccountChartGenerator'
);
@@ -105,18 +101,12 @@ class FireflyServiceProvider extends ServiceProvider
'FireflyIII\Generator\Chart\PiggyBank\PiggyBankChartGeneratorInterface', 'FireflyIII\Generator\Chart\PiggyBank\ChartJsPiggyBankChartGenerator'
);
$this->app->bind('FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface', 'FireflyIII\Generator\Chart\Report\ChartJsReportChartGenerator');
-
-
$this->app->bind('FireflyIII\Helpers\Help\HelpInterface', 'FireflyIII\Helpers\Help\Help');
$this->app->bind('FireflyIII\Helpers\Report\ReportHelperInterface', 'FireflyIII\Helpers\Report\ReportHelper');
- $this->app->bind('FireflyIII\Helpers\Report\ReportQueryInterface', 'FireflyIII\Helpers\Report\ReportQuery');
$this->app->bind('FireflyIII\Helpers\FiscalHelperInterface', 'FireflyIII\Helpers\FiscalHelper');
-
- // better report helper interfaces:
$this->app->bind('FireflyIII\Helpers\Report\AccountReportHelperInterface', 'FireflyIII\Helpers\Report\AccountReportHelper');
$this->app->bind('FireflyIII\Helpers\Report\BalanceReportHelperInterface', 'FireflyIII\Helpers\Report\BalanceReportHelper');
$this->app->bind('FireflyIII\Helpers\Report\BudgetReportHelperInterface', 'FireflyIII\Helpers\Report\BudgetReportHelper');
-
}
}
diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php
index 1bff963407..db50c23499 100644
--- a/app/Providers/JournalServiceProvider.php
+++ b/app/Providers/JournalServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\Journal\JournalRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Journal\JournalRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Journal\JournalRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Journal\JournalRepository', $arguments);
diff --git a/app/Providers/PiggyBankServiceProvider.php b/app/Providers/PiggyBankServiceProvider.php
index 2586776891..14aa3353af 100644
--- a/app/Providers/PiggyBankServiceProvider.php
+++ b/app/Providers/PiggyBankServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\PiggyBank\PiggyBankRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\PiggyBank\PiggyBankRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\PiggyBank\PiggyBankRepository', $arguments);
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
index 9a500f25e3..e66d1e5c18 100644
--- a/app/Providers/RouteServiceProvider.php
+++ b/app/Providers/RouteServiceProvider.php
@@ -1,4 +1,12 @@
app->bind(
'FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\RuleGroup\RuleGroupRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\RuleGroup\RuleGroupRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\RuleGroup\RuleGroupRepository', $arguments);
diff --git a/app/Providers/RuleServiceProvider.php b/app/Providers/RuleServiceProvider.php
index c3480920f1..2a416fcea0 100644
--- a/app/Providers/RuleServiceProvider.php
+++ b/app/Providers/RuleServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\Rule\RuleRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Rule\RuleRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Rule\RuleRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Rule\RuleRepository', $arguments);
diff --git a/app/Providers/TagServiceProvider.php b/app/Providers/TagServiceProvider.php
index 227b27e73c..51e4a16e45 100644
--- a/app/Providers/TagServiceProvider.php
+++ b/app/Providers/TagServiceProvider.php
@@ -1,8 +1,17 @@
app->bind(
'FireflyIII\Repositories\Tag\TagRepositoryInterface',
function (Application $app, array $arguments) {
- if (!isset($arguments[0]) && Auth::check()) {
- return app('FireflyIII\Repositories\Tag\TagRepository', [Auth::user()]);
- } else {
- if (!isset($arguments[0]) && !Auth::check()) {
- throw new FireflyException('There is no user present.');
- }
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Repositories\Tag\TagRepository', [$app->auth->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Repositories\Tag\TagRepository', $arguments);
diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php
index c07260159d..ef95442a73 100644
--- a/app/Repositories/Account/AccountRepository.php
+++ b/app/Repositories/Account/AccountRepository.php
@@ -1,25 +1,28 @@
where('account_id', $account->id)->update(['account_id' => $moveTo->id]);
+ $query = $this->user->transactionjournals()->expanded()->sortCorrectly()
+ ->transactionTypes([TransactionType::DEPOSIT]);
+
+ if ($end >= $start) {
+ $query->before($end)->after($start);
}
- $account->delete();
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+ }
+ );
+ $query->whereIn('source.account_id', $accountIds);
+
+ }
+ // remove group by
+ $query->getQuery()->getQuery()->groups = null;
+
+ // that should do it:
+ $sum = strval($query->sum('source.amount'));
+ $sum = bcmul($sum, '-1');
+
+ return $sum;
- return true;
}
/**
- * @param $accountId
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
*
- * @return Account
+ * @return string
*/
- public function find(int $accountId): Account
+ public function earnedInPeriod(Collection $accounts, Carbon $start, Carbon $end): string
{
- $account = $this->user->accounts()->find($accountId);
- if (is_null($account)) {
- $account = new Account;
+ $query = $this->user->transactionjournals()->expanded()->sortCorrectly()
+ ->transactionTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]);
+
+ if ($end >= $start) {
+ $query->before($end)->after($start);
}
- return $account;
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+ $query->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+ }
+ );
+
+ $query->whereIn('destination.account_id', $accountIds);
+ $query->whereNotIn('source.account_id', $accountIds);
+
+ }
+ // remove group by
+ $query->getQuery()->getQuery()->groups = null;
+
+ // that should do it:
+ $sum = strval($query->sum('destination.amount'));
+
+ return $sum;
+
+ }
+
+ /**
+ * This method will call AccountRepositoryInterface::journalsInPeriod and get all withdrawaks made from the given $accounts,
+ * as well as the transfers that move away from those $accounts. This is a slightly sharper selection
+ * than made by journalsInPeriod itself.
+ *
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @see AccountRepositoryInterface::journalsInPeriod
+ *
+ * @return Collection
+ */
+ public function expensesInPeriod(Collection $accounts, Carbon $start, Carbon $end): Collection
+ {
+ $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
+ $journals = $this->journalsInPeriod($accounts, $types, $start, $end);
+ $accountIds = $accounts->pluck('id')->toArray();
+
+ // filter because some of these journals are still too much.
+ $journals = $journals->filter(
+ function (TransactionJournal $journal) use ($accountIds) {
+ if ($journal->transaction_type_type == TransactionType::WITHDRAWAL) {
+ return $journal;
+ }
+ /*
+ * The source of a transfer must be one of the $accounts in order to
+ * be included. Otherwise, it would not be an expense.
+ */
+ if (in_array($journal->source_account_id, $accountIds)) {
+ return $journal;
+ }
+ }
+ );
+
+ return $journals;
+ }
+
+ /**
+ * @param Account $account
+ *
+ * @return Carbon
+ */
+ public function firstUseDate(Account $account): Carbon
+ {
+ $first = new Carbon('1900-01-01');
+
+ /** @var Transaction $first */
+ $date = $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->orderBy('transaction_journals.date', 'ASC')
+ ->orderBy('transaction_journals.order', 'DESC')
+ ->orderBy('transaction_journals.id', 'ASC')
+ ->first(['transaction_journals.date']);
+ if (!is_null($date)) {
+ $first = new Carbon($date->date);
+ }
+
+ return $first;
}
/**
@@ -104,91 +220,6 @@ class AccountRepository implements AccountRepositoryInterface
return $this->user->accounts()->whereIn('id', $ids)->get(['accounts.*']);
}
- /**
- * @param array $types
- *
- * @return Collection
- */
- public function getAccounts(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);
- }
- $result = $query->get(['accounts.*']);
-
- $result = $result->sortBy(
- function (Account $account) {
- return strtolower($account->name);
- }
- );
-
- return $result;
- }
-
- /**
- * This method returns the users credit cards, along with some basic information about the
- * balance they have on their CC. To be used in the JSON boxes on the front page that say
- * how many bills there are still left to pay. The balance will be saved in field "balance".
- *
- * To get the balance, the field "date" is necessary.
- *
- * @param Carbon $date
- *
- * @return Collection
- */
- public function getCreditCards(Carbon $date): Collection
- {
- $set = $this->user->accounts()
- ->hasMetaValue('accountRole', 'ccAsset')
- ->hasMetaValue('ccType', 'monthlyFull')
- ->leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->whereNull('transactions.deleted_at')
- ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
- ->groupBy('accounts.id')
- ->get(
- [
- 'accounts.*',
- 'ccType.data as ccType',
- 'accountRole.data as accountRole',
- DB::Raw('SUM(`transactions`.`amount`) AS `balance`'),
- ]
- );
-
- return $set;
- }
-
- /**
- * Returns a list of transactions TO the $account, not including transfers
- * and/or expenses in the $accounts list.
- *
- * @param Account $account
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getExpensesByDestination(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $ids = $accounts->pluck('id')->toArray();
- $journals = $this->user->transactionjournals()
- ->expanded()
- ->before($end)
- ->where('destination_account.id', $account->id)
- ->whereIn('source_account.id', $ids)
- ->after($start)
- ->get(TransactionJournal::QUERYFIELDS);
-
- return $journals;
- }
-
/**
* @param TransactionJournal $journal
* @param Account $account
@@ -205,160 +236,32 @@ class AccountRepository implements AccountRepositoryInterface
return $transaction;
}
- /**
- * @param Preference $preference
- *
- * @return Collection
- */
- public function getFrontpageAccounts(Preference $preference): Collection
- {
- $query = $this->user->accounts()->accountTypeIn(['Default account', 'Asset account']);
-
- if (count($preference->data) > 0) {
- $query->whereIn('accounts.id', $preference->data);
- }
-
- $result = $query->get(['accounts.*']);
-
- return $result;
- }
-
- /**
- * This method is used on the front page where (in turn) its viewed journals-tiny.php which (in turn)
- * is almost the only place where formatJournal is used. Aka, we can use some custom querying to get some specific.
- * fields using left joins.
- *
- * @param Account $account
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return mixed
- */
- public function getFrontpageTransactions(Account $account, Carbon $start, Carbon $end): Collection
- {
- $set = $this->user
- ->transactionjournals()
- ->with(['transactions'])
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')->where('accounts.id', $account->id)
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id')
- ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->before($end)
- ->after($start)
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC')
- ->take(10)
- ->get(['transaction_journals.*', 'transaction_currencies.symbol', 'transaction_types.type']);
-
- return $set;
- }
-
- /**
- * Returns a list of transactions TO the given (asset) $account, but none from the
- * given list of accounts
- *
- * @param Account $account
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getIncomeByDestination(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $ids = $accounts->pluck('id')->toArray();
- $journals = $this->user->transactionjournals()
- ->expanded()
- ->before($end)
- ->where('source_account.id', $account->id)
- ->whereIn('destination_account.id', $ids)
- ->after($start)
- ->get(TransactionJournal::QUERYFIELDS);
-
- return $journals;
- }
-
- /**
- * @param Account $account
- * @param int $page
- * @param int $pageSize
- *
- * @return LengthAwarePaginator
- */
- public function getJournals(Account $account, int $page, int $pageSize = 50): LengthAwarePaginator
- {
- $offset = ($page - 1) * $pageSize;
- $query = $this->user
- ->transactionJournals()
- ->expanded()
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.account_id', $account->id)
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC');
-
- $count = $query->count();
- $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS);
- $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page);
-
- return $paginator;
-
-
- }
-
- /**
- * @param Account $account
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getJournalsInRange(Account $account, Carbon $start, Carbon $end): Collection
- {
- $query = $this->user
- ->transactionJournals()
- ->expanded()
- ->where(
- function (Builder $q) use ($account) {
- $q->where('destination_account.id', $account->id);
- $q->orWhere('source_account.id', $account->id);
- }
- )
- ->after($start)
- ->before($end);
-
- $set = $query->get(TransactionJournal::QUERYFIELDS);
-
- return $set;
- }
-
/**
* Get the accounts of a user that have piggy banks connected to them.
*
+ * @param Carbon $start
+ * @param Carbon $end
+ *
* @return Collection
*/
- public function getPiggyBankAccounts(): Collection
+ public function getPiggyBankAccounts(Carbon $start, Carbon $end): Collection
{
- $start = clone Session::get('start', new Carbon);
- $end = clone Session::get('end', new Carbon);
$collection = new Collection(DB::table('piggy_banks')->distinct()->get(['piggy_banks.account_id']));
- $ids = $collection->pluck('account_id')->toArray();
+ $accountIds = $collection->pluck('account_id')->toArray();
$accounts = new Collection;
-
- $ids = array_unique($ids);
- if (count($ids) > 0) {
- $accounts = $this->user->accounts()->whereIn('id', $ids)->where('accounts.active', 1)->get();
+ $accountIds = array_unique($accountIds);
+ if (count($accountIds) > 0) {
+ $accounts = $this->user->accounts()->whereIn('id', $accountIds)->where('accounts.active', 1)->get();
}
$accounts->each(
function (Account $account) use ($start, $end) {
- $account->startBalance = Steam::balance($account, $start, true);
- $account->endBalance = Steam::balance($account, $end, true);
- $account->piggyBalance = 0;
+ $account->startBalance = Steam::balanceIgnoreVirtual($account, $start);
+ $account->endBalance = Steam::balanceIgnoreVirtual($account, $end);
+ $account->piggyBalance = '0';
/** @var PiggyBank $piggyBank */
foreach ($account->piggyBanks as $piggyBank) {
- $account->piggyBalance += $piggyBank->currentRelevantRep()->currentamount;
+ $account->piggyBalance = bcadd($account->piggyBalance, $piggyBank->currentRelevantRep()->currentamount);
}
// sum of piggy bank amounts on this account:
// diff between endBalance and piggyBalance.
@@ -370,16 +273,20 @@ class AccountRepository implements AccountRepositoryInterface
}
);
+
return $accounts;
}
/**
- * Get savings accounts and the balance difference in the period.
+ * Get savings accounts.
+ *
+ * @param Carbon $start
+ * @param Carbon $end
*
* @return Collection
*/
- public function getSavingsAccounts(): Collection
+ public function getSavingsAccounts(Carbon $start, Carbon $end): Collection
{
$accounts = $this->user->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
@@ -387,8 +294,6 @@ class AccountRepository implements AccountRepositoryInterface
->where('accounts.active', 1)
->where('account_meta.data', '"savingAsset"')
->get(['accounts.*']);
- $start = clone Session::get('start', new Carbon);
- $end = clone Session::get('end', new Carbon);
$accounts->each(
function (Account $account) use ($start, $end) {
@@ -401,36 +306,130 @@ class AccountRepository implements AccountRepositoryInterface
if ($diff < 0 && $account->startBalance > 0) {
// percentage lost compared to start.
$pct = (($diff * -1) / $account->startBalance) * 100;
- } else {
- if ($diff >= 0 && $account->startBalance > 0) {
- $pct = ($diff / $account->startBalance) * 100;
- } else {
- $pct = 100;
- }
+
+ $pct = $pct > 100 ? 100 : $pct;
+ $account->difference = $diff;
+ $account->percentage = round($pct);
+
+ return;
+ }
+ if ($diff >= 0 && $account->startBalance > 0) {
+ $pct = ($diff / $account->startBalance) * 100;
+ $pct = $pct > 100 ? 100 : $pct;
+ $account->difference = $diff;
+ $account->percentage = round($pct);
+
+ return;
}
- $pct = $pct > 100 ? 100 : $pct;
$account->difference = $diff;
- $account->percentage = round($pct);
+ $account->percentage = 100;
}
);
+
return $accounts;
}
/**
+ * This method will call AccountRepositoryInterface::journalsInPeriod and get all deposits made to the given $accounts,
+ * as well as the transfers that move away to those $accounts. This is a slightly sharper selection
+ * than made by journalsInPeriod itself.
+ *
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @see AccountRepositoryInterface::journalsInPeriod
+ *
+ * @return Collection
+ */
+ public function incomesInPeriod(Collection $accounts, Carbon $start, Carbon $end): Collection
+ {
+ $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
+ $journals = $this->journalsInPeriod($accounts, $types, $start, $end);
+ $accountIds = $accounts->pluck('id')->toArray();
+
+ // filter because some of these journals are still too much.
+ $journals = $journals->filter(
+ function (TransactionJournal $journal) use ($accountIds) {
+ if ($journal->transaction_type_type == TransactionType::DEPOSIT) {
+ return $journal;
+ }
+ /*
+ * The destination of a transfer must be one of the $accounts in order to
+ * be included. Otherwise, it would not be income.
+ */
+ if (in_array($journal->destination_account_id, $accountIds)) {
+ return $journal;
+ }
+ }
+ );
+
+ return $journals;
+ }
+
+ /**
+ * @param Collection $accounts
+ * @param array $types
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function journalsInPeriod(Collection $accounts, array $types, Carbon $start, Carbon $end): Collection
+ {
+ // first collect actual transaction journals (fairly easy)
+ $query = $this->user->transactionjournals()->expanded()->sortCorrectly();
+
+ if ($end >= $start) {
+ $query->before($end)->after($start);
+ }
+
+ if (count($types) > 0) {
+ $query->transactionTypes($types);
+ }
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+ }
+ );
+ $query->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+ $set = join(', ', $accountIds);
+ $query->whereRaw('(source.account_id in (' . $set . ') XOR destination.account_id in (' . $set . '))');
+
+ }
+ // that should do it:
+ $fields = TransactionJournal::queryFields();
+ $fields[] = 'source.account_id as source_account_id';
+ $fields[] = 'source.amount as source_amount';
+ $fields[] = 'destination.account_id as destination_account_id';
+ $fields[] = 'destination.amount as destination_amount';
+ $complete = $query->get($fields);
+
+ return $complete;
+ }
+
+ /**
+ *
* @param Account $account
* @param Carbon $date
*
- * @return float
+ * @return string
*/
public function leftOnAccount(Account $account, Carbon $date): string
{
- $balance = Steam::balance($account, $date, true);
+ $balance = Steam::balanceIgnoreVirtual($account, $date);
/** @var PiggyBank $p */
foreach ($account->piggybanks()->get() as $p) {
- $balance -= $p->currentRelevantRep()->currentamount;
+ $balance = bcsub($balance, $p->currentRelevantRep()->currentamount);
}
return $balance;
@@ -450,16 +449,13 @@ class AccountRepository implements AccountRepositoryInterface
$journal = TransactionJournal::
leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
- ->orderBy('transaction_journals.date', 'ASC')
+ ->sortCorrectly()
->first(['transaction_journals.*']);
if (is_null($journal)) {
- $date = new Carbon;
- $date->addYear(); // in the future.
- } else {
- $date = $journal->date;
+ return new Carbon('1900-01-01');
}
- return $date;
+ return $journal->date;
}
/**
@@ -475,16 +471,15 @@ class AccountRepository implements AccountRepositoryInterface
$journal = TransactionJournal::
leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
- ->orderBy('transaction_journals.date', 'DESC')
+ ->orderBy('transaction_journals.date', 'ASC')
+ ->orderBy('transaction_journals.order', 'DESC')
+ ->orderBy('transaction_journals.id', 'Ã…SC')
->first(['transaction_journals.*']);
if (is_null($journal)) {
- $date = new Carbon;
- $date->addYear(); // in the future.
- } else {
- $date = $journal->date;
+ return new Carbon('1900-01-01');
}
- return $date;
+ return $journal->date;
}
/**
@@ -495,11 +490,10 @@ class AccountRepository implements AccountRepositoryInterface
public function openingBalanceTransaction(Account $account): TransactionJournal
{
$journal = TransactionJournal
- ::orderBy('transaction_journals.date', 'ASC')
+ ::sortCorrectly()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionType::OPENING_BALANCE])
- ->orderBy('created_at', 'ASC')
->first(['transaction_journals.*']);
if (is_null($journal)) {
return new TransactionJournal;
@@ -509,269 +503,85 @@ class AccountRepository implements AccountRepositoryInterface
}
/**
- * @param array $data
+ * This method is almost the same as ::spentInPeriod, but only works for expense accounts
+ * instead of the implied asset accounts for ::spentInPeriod. ::spentInPeriod will tell you
+ * how much money was spent by the given asset accounts. This method will tell you how much money
+ * these given expense accounts received. Ie. how much money was spent AT these expense accounts.
*
- * @return Account
- */
- public function store(array $data): Account
- {
- $newAccount = $this->storeAccount($data);
- if (!is_null($newAccount)) {
- $this->storeMetadata($newAccount, $data);
- }
-
-
- // continue with the opposing account:
- if ($data['openingBalance'] != 0) {
- $opposingData = [
- 'user' => $data['user'],
- 'accountType' => 'initial',
- 'virtualBalance' => 0,
- 'name' => $data['name'] . ' initial balance',
- 'active' => false,
- 'iban' => '',
- ];
- $opposing = $this->storeAccount($opposingData);
- if (!is_null($opposing) && !is_null($newAccount)) {
- $this->storeInitialBalance($newAccount, $opposing, $data);
- }
-
- }
-
- return $newAccount;
-
- }
-
- /**
- * @param $account
- * @param $name
- * @param $value
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
*
- * @return AccountMeta
- */
- public function storeMeta(Account $account, string $name, $value): AccountMeta
- {
- return AccountMeta::create(['name' => $name, 'data' => $value, 'account_id' => $account->id,]);
- }
-
- /**
* @return string
*/
- public function sumOfEverything(): string
+ public function spentAtInPeriod(Collection $accounts, Carbon $start, Carbon $end): string
{
- return strval($this->user->transactions()->sum('amount'));
+ /** @var HasMany $query */
+ $query = $this->user->transactionjournals()->expanded()->sortCorrectly()
+ ->transactionTypes([TransactionType::WITHDRAWAL]);
+ if ($end >= $start) {
+ $query->before($end)->after($start);
+ }
+
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+ $query->whereIn('destination.account_id', $accountIds);
+
+ }
+ // remove group by
+ $query->getQuery()->getQuery()->groups = null;
+
+ // that should do it:
+ $sum = strval($query->sum('destination.amount'));
+ $sum = bcmul($sum, '-1');
+
+ return $sum;
}
/**
- * @param Account $account
- * @param array $data
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
*
- * @SuppressWarnings(PHPMD.CyclomaticComplexity) // need the complexity.
- *
- * @return Account
+ * @return string
*/
- public function update(Account $account, array $data): Account
+ public function spentInPeriod(Collection $accounts, Carbon $start, Carbon $end): string
{
- // update the account:
- $account->name = $data['name'];
- $account->active = $data['active'] == '1' ? true : false;
- $account->virtual_balance = $data['virtualBalance'];
- $account->iban = $data['iban'];
- $account->save();
-
- $this->updateMetadata($account, $data);
- $openingBalance = $this->openingBalanceTransaction($account);
- if ($data['openingBalance'] != 0) {
- if (!is_null($openingBalance->id)) {
- $this->updateInitialBalance($account, $openingBalance, $data);
- } else {
- $type = $data['openingBalance'] < 0 ? 'expense' : 'revenue';
- $opposingData = [
- 'user' => $data['user'],
- 'accountType' => $type,
- 'name' => $data['name'] . ' initial balance',
- 'active' => false,
- 'iban' => '',
- 'virtualBalance' => 0,
- ];
- $opposing = $this->storeAccount($opposingData);
- if (!is_null($opposing)) {
- $this->storeInitialBalance($account, $opposing, $data);
- }
- }
-
- } else {
- if ($openingBalance) { // opening balance is zero, should we delete it?
- $openingBalance->delete(); // delete existing opening balance.
- }
+ /** @var HasMany $query */
+ $query = $this->user->transactionjournals()->expanded()->sortCorrectly()
+ ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]);
+ if ($end >= $start) {
+ $query->before($end)->after($start);
}
- return $account;
- }
-
- /**
- * @param array $data
- *
- * @return Account
- */
- protected function storeAccount(array $data): Account
- {
- $type = Config::get('firefly.accountTypeByIdentifier.' . $data['accountType']);
- $accountType = AccountType::whereType($type)->first();
- $newAccount = new Account(
- [
- 'user_id' => $data['user'],
- 'account_type_id' => $accountType->id,
- 'name' => $data['name'],
- 'virtual_balance' => $data['virtualBalance'],
- 'active' => $data['active'] === true ? true : false,
- 'iban' => $data['iban'],
- ]
- );
-
- if (!$newAccount->isValid()) {
- // does the account already exist?
- $searchData = [
- 'user_id' => $data['user'],
- 'account_type_id' => $accountType->id,
- 'virtual_balance' => $data['virtualBalance'],
- 'name' => $data['name'],
- 'iban' => $data['iban'],
- ];
- $existingAccount = Account::firstOrNullEncrypted($searchData);
- if (!$existingAccount) {
- Log::error('Account create error: ' . $newAccount->getErrors()->toJson());
- abort(500);
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
}
- $newAccount = $existingAccount;
+ );
+ $query->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+ $query->whereIn('source.account_id', $accountIds);
+ $query->whereNotIn('destination.account_id', $accountIds);
}
- $newAccount->save();
+ // remove group by
+ $query->getQuery()->getQuery()->groups = null;
- return $newAccount;
+ // that should do it:
+ $sum = strval($query->sum('source.amount'));
+
+ return $sum;
}
- /**
- * @param Account $account
- * @param Account $opposing
- * @param array $data
- *
- * @return TransactionJournal
- */
- protected function storeInitialBalance(Account $account, Account $opposing, array $data): TransactionJournal
- {
- $transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
- $journal = TransactionJournal::create(
- [
- 'user_id' => $data['user'],
- 'transaction_type_id' => $transactionType->id,
- 'bill_id' => null,
- 'transaction_currency_id' => $data['openingBalanceCurrency'],
- 'description' => 'Initial balance for "' . $account->name . '"',
- 'completed' => true,
- 'date' => $data['openingBalanceDate'],
- 'encrypted' => true,
- ]
- );
-
- if ($data['openingBalance'] < 0) {
- $firstAccount = $opposing;
- $secondAccount = $account;
- $firstAmount = $data['openingBalance'] * -1;
- $secondAmount = $data['openingBalance'];
- } else {
- $firstAccount = $account;
- $secondAccount = $opposing;
- $firstAmount = $data['openingBalance'];
- $secondAmount = $data['openingBalance'] * -1;
- }
-
- $one = new Transaction(['account_id' => $firstAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $firstAmount]);
- $one->save();// first transaction: from
-
- $two = new Transaction(['account_id' => $secondAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $secondAmount]);
- $two->save(); // second transaction: to
-
- return $journal;
-
- }
-
- /**
- * @param Account $account
- * @param array $data
- */
- protected function storeMetadata(Account $account, array $data)
- {
- foreach ($this->validFields as $field) {
- if (isset($data[$field])) {
- $metaData = new AccountMeta(
- [
- 'account_id' => $account->id,
- 'name' => $field,
- 'data' => $data[$field],
- ]
- );
- $metaData->save();
- }
-
-
- }
- }
-
- /**
- * @param Account $account
- * @param TransactionJournal $journal
- * @param array $data
- *
- * @return TransactionJournal
- */
- protected function updateInitialBalance(Account $account, TransactionJournal $journal, array $data): TransactionJournal
- {
- $journal->date = $data['openingBalanceDate'];
- $journal->save();
-
- /** @var Transaction $transaction */
- foreach ($journal->transactions()->get() as $transaction) {
- if ($account->id == $transaction->account_id) {
- $transaction->amount = $data['openingBalance'];
- $transaction->save();
- }
- if ($account->id != $transaction->account_id) {
- $transaction->amount = $data['openingBalance'] * -1;
- $transaction->save();
- }
- }
-
- return $journal;
- }
-
- /**
- * @param Account $account
- * @param array $data
- *
- */
- protected function updateMetadata(Account $account, array $data)
- {
- foreach ($this->validFields as $field) {
- $entry = $account->accountMeta()->where('name', $field)->first();
-
- if (isset($data[$field])) {
- // update if new data is present:
- if (!is_null($entry)) {
- $entry->data = $data[$field];
- $entry->save();
- } else {
- $metaData = new AccountMeta(
- [
- 'account_id' => $account->id,
- 'name' => $field,
- 'data' => $data[$field],
- ]
- );
- $metaData->save();
- }
- }
- }
-
- }
}
diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php
index 7eed5eb3a3..288ae35d96 100644
--- a/app/Repositories/Account/AccountRepositoryInterface.php
+++ b/app/Repositories/Account/AccountRepositoryInterface.php
@@ -1,15 +1,20 @@
getAttachmentLocation($attachment);
unlink($file);
diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php
index 2c25be8baa..b95cc969f8 100644
--- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php
+++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php
@@ -1,4 +1,12 @@
getCreditCards($end); // Find credit card accounts and possibly unpaid credit card bills.
- /** @var Account $creditCard */
- foreach ($creditCards as $creditCard) {
- if ($creditCard->balance == 0) {
- // find a transfer TO the credit card which should account for anything paid. If not, the CC is not yet used.
- $set = TransactionJournal::whereIn(
- 'transaction_journals.id', function (Builder $q) use ($creditCard, $start, $end) {
- $q->select('transaction_journals.id')
- ->from('transactions')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->where('transactions.account_id', $creditCard->id)
- ->where('transactions.amount', '>', 0)// this makes the filter unnecessary.
- ->where('transaction_journals.user_id', $this->user->id)
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->where('transaction_types.type', TransactionType::TRANSFER);
- }
- )->leftJoin(
- 'transactions', function (JoinClause $join) {
- $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0);
- }
- )->first([DB::raw('SUM(`transactions`.`amount`) as `sum_amount`')]);
- $sumAmount = $set->sum_amount ?? '0';
- $amount = bcadd($amount, $sumAmount);
- } else {
- $amount = bcadd($amount, $creditCard->balance);
- }
- }
-
- return $amount;
-
- }
-
/**
* This method also returns the amount of the journal in "journalAmount"
* for easy access.
@@ -315,11 +271,9 @@ class BillRepository implements BillRepositoryInterface
$offset = ($page - 1) * $pageSize;
$query = $bill->transactionjournals()
->expanded()
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC');
+ ->sortCorrectly();
$count = $query->count();
- $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS);
+ $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
$paginator = new LengthAwarePaginator($set, $count, $pageSize, $page);
return $paginator;
@@ -327,6 +281,7 @@ class BillRepository implements BillRepositoryInterface
/**
* Get all journals that were recorded on this bill between these dates.
+ *
* @param Bill $bill
* @param Carbon $start
* @param Carbon $end
@@ -481,12 +436,12 @@ class BillRepository implements BillRepositoryInterface
if (false === $journal->isWithdrawal()) {
return false;
}
-
- $matches = explode(',', $bill->match);
- $description = strtolower($journal->description) . ' ' . strtolower(TransactionJournal::destinationAccount($journal)->name);
-
- // new: add source to word match:
- $description .= ' ' . strtolower(TransactionJournal::sourceAccount($journal)->name);
+ $destinationAccounts = TransactionJournal::destinationAccountList($journal);
+ $sourceAccounts = TransactionJournal::sourceAccountList($journal);
+ $matches = explode(',', $bill->match);
+ $description = strtolower($journal->description) . ' ';
+ $description .= strtolower(join(' ', $destinationAccounts->pluck('name')->toArray()));
+ $description .= strtolower(join(' ', $sourceAccounts->pluck('name')->toArray()));
$wordMatch = $this->doWordMatch($matches, $description);
$amountMatch = $this->doAmountMatch(TransactionJournal::amountPositive($journal), $bill->amount_min, $bill->amount_max);
diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php
index e1667fe83e..3305e26d68 100644
--- a/app/Repositories/Bill/BillRepositoryInterface.php
+++ b/app/Repositories/Bill/BillRepositoryInterface.php
@@ -1,4 +1,12 @@
user = $user;
}
- /**
- * @param Budget $budget
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
- *
- * @return string
- */
- public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): string
- {
- return $this->commonBalanceInPeriod($budget, $start, $end, $accounts);
- }
-
- /**
- * @return bool
- */
- public function cleanupBudgets(): bool
- {
- // delete limits with amount 0:
- BudgetLimit::where('amount', 0)->delete();
-
- return true;
-
- }
-
/**
* @param Budget $budget
*
@@ -77,23 +56,6 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
return true;
}
- /**
- * @param Budget $budget
- * @param Account $account
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection
- {
- return $budget->transactionjournals()->expanded()
- ->before($end)
- ->after($start)
- ->where('source_account.id', $account->id)
- ->get(TransactionJournal::QUERYFIELDS);
- }
-
/**
* Find a budget.
*
@@ -112,18 +74,32 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
}
/**
+ * This method returns the oldest journal or transaction date known to this budget.
+ * Will cache result.
+ *
* @param Budget $budget
*
* @return Carbon
*/
- public function firstActivity(Budget $budget): Carbon
+ public function firstUseDate(Budget $budget): Carbon
{
- $first = $budget->transactionjournals()->orderBy('date', 'ASC')->first();
- if ($first) {
- return $first->date;
+ $oldest = Carbon::create()->startOfYear();
+ $journal = $budget->transactionjournals()->orderBy('date', 'ASC')->first();
+ if (!is_null($journal)) {
+ $oldest = $journal->date < $oldest ? $journal->date : $oldest;
}
- return new Carbon;
+ $transaction = $budget
+ ->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id')
+ ->orderBy('transaction_journals.date', 'ASC')->first(['transactions.*', 'transaction_journals.date']);
+ if (!is_null($transaction)) {
+ $carbon = new Carbon($transaction->date);
+ $oldest = $carbon < $oldest ? $carbon : $oldest;
+ }
+
+ return $oldest;
+
}
/**
@@ -151,68 +127,14 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
*/
public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection
{
- /** @var Collection $repetitions */
- return LimitRepetition::
+ $query = LimitRepetition::
leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id')
- ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
- ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'))
- ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00'))
- ->where('budgets.user_id', $this->user->id)
- ->get(['limit_repetitions.*', 'budget_limits.budget_id']);
- }
+ ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
+ ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'))
+ ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00'))
+ ->where('budgets.user_id', $this->user->id);
- /**
- * @param Account $account
- * @param Carbon $start
- * @param Carbon $end
- * @param Collection $accounts
- *
- * @return Collection
- */
- public function getAllWithoutBudget(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $ids = $accounts->pluck('id')->toArray();
-
- return $this->user
- ->transactionjournals()
- ->expanded()
- ->where('source_account.id', $account->id)
- ->whereNotIn('destination_account.id', $ids)
- ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereNull('budget_transaction_journal.id')
- ->before($end)
- ->after($start)
- ->get(TransactionJournal::QUERYFIELDS);
- }
-
- /**
- * Get the budgeted amounts for each budgets in each year.
- *
- * @param Collection $budgets
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getBudgetedPerYear(Collection $budgets, Carbon $start, Carbon $end): Collection
- {
- $budgetIds = $budgets->pluck('id')->toArray();
-
- $set = $this->user->budgets()
- ->leftJoin('budget_limits', 'budgets.id', '=', 'budget_limits.budget_id')
- ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id')
- ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d'))
- ->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d'))
- ->groupBy('budgets.id')
- ->groupBy('dateFormatted')
- ->whereIn('budgets.id', $budgetIds)
- ->get(
- [
- 'budgets.*',
- DB::raw('DATE_FORMAT(`limit_repetitions`.`startdate`,"%Y") as `dateFormatted`'),
- DB::raw('SUM(`limit_repetitions`.`amount`) as `budgeted`'),
- ]
- );
+ $set = $query->get(['limit_repetitions.*', 'budget_limits.budget_id']);
return $set;
}
@@ -234,258 +156,6 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
return $set;
}
- /**
- * Returns an array with every budget in it and the expenses for each budget
- * per month.
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function getBudgetsAndExpensesPerMonth(Collection $accounts, Carbon $start, Carbon $end): array
- {
- $ids = $accounts->pluck('id')->toArray();
-
- /** @var Collection $set */
- $set = $this->user->budgets()
- ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
- ->leftJoin(
- 'transactions', function (JoinClause $join) {
- $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0);
- }
- )
- ->groupBy('budgets.id')
- ->groupBy('dateFormatted')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->whereIn('transactions.account_id', $ids)
- ->get(
- [
- 'budgets.*',
- DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y-%m") AS `dateFormatted`'),
- DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'),
- ]
- );
-
- $set = $set->sortBy(
- function (Budget $budget) {
- return strtolower($budget->name);
- }
- );
-
- $return = [];
- foreach ($set as $budget) {
- $id = $budget->id;
- if (!isset($return[$id])) {
- $return[$id] = [
- 'budget' => $budget,
- 'entries' => [],
- ];
- }
- // store each entry:
- $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount;
- }
-
- return $return;
- }
-
- /**
- * Returns an array with every budget in it and the expenses for each budget
- * per year for.
- *
- * @param Collection $budgets
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // it's a query.
- *
- * @return array
- */
- public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array
- {
- $ids = $accounts->pluck('id')->toArray();
- $budgetIds = $budgets->pluck('id')->toArray();
-
- /** @var Collection $set */
- $set = $this->user->budgets()
- ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
- ->leftJoin(
- 'transactions', function (JoinClause $join) {
- $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0);
- }
- )
- ->groupBy('budgets.id')
- ->groupBy('dateFormatted')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->whereIn('transactions.account_id', $ids)
- ->whereIn('budgets.id', $budgetIds)
- ->get(
- [
- 'budgets.*',
- DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'),
- DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'),
- ]
- );
-
- $set = $set->sortBy(
- function (Budget $budget) {
- return strtolower($budget->name);
- }
- );
-
- $return = [];
- foreach ($set as $budget) {
- $id = $budget->id;
- if (!isset($return[$id])) {
- $return[$id] = [
- 'budget' => $budget,
- 'entries' => [],
- ];
- }
- // store each entry:
- $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount;
- }
-
- return $return;
- }
-
- /**
- * Returns a list of budgets, budget limits and limit repetitions
- * (doubling any of them in a left join)
- *
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection
- {
- /** @var Collection $set */
- $set = $this->user
- ->budgets()
- ->leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
- ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id')
- ->where(
- function (Builder $query) use ($start, $end) {
- $query->where(
- function (Builder $query) use ($start, $end) {
- $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d'));
- $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d'));
- }
- );
- $query->orWhere(
- function (Builder $query) {
- $query->whereNull('limit_repetitions.startdate');
- $query->whereNull('limit_repetitions.enddate');
- }
- );
- }
- )
- ->get(['budgets.*', 'limit_repetitions.startdate', 'limit_repetitions.enddate', 'limit_repetitions.amount']);
-
- $set = $set->sortBy(
- function (Budget $budget) {
- return strtolower($budget->name);
- }
- );
-
- return $set;
-
- }
-
- /**
- * @param Budget $budget
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return LimitRepetition
- */
- public function getCurrentRepetition(Budget $budget, Carbon $start, Carbon $end): LimitRepetition
- {
- $data = $budget->limitrepetitions()
- ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00'))
- ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00'))
- ->first(['limit_repetitions.*']);
- if (is_null($data)) {
- return new LimitRepetition;
- }
-
- return $data;
- }
-
- /**
- * Returns all expenses for the given budget and the given accounts, in the given period.
- *
- * @param Budget $budget
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection
- {
- $ids = $accounts->pluck('id')->toArray();
- $set = $budget->transactionjournals()
- ->before($end)
- ->after($start)
- ->expanded()
- ->where('transaction_types.type', TransactionType::WITHDRAWAL)
- ->whereIn('source_account.id', $ids)
- ->get(TransactionJournal::QUERYFIELDS);
-
- return $set;
- }
-
- /**
- * Returns the expenses for this budget grouped per day, with the date
- * in "date" (a string, not a Carbon) and the amount in "dailyAmount".
- *
- * @param Budget $budget
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end): Collection
- {
- $set = $this->user->budgets()
- ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.budget_id', '=', 'budgets.id')
- ->leftJoin('transaction_journals', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->whereNull('transaction_journals.deleted_at')
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->where('budgets.id', $budget->id)
- ->where('transactions.amount', '<', 0)
- ->groupBy('transaction_journals.date')
- ->orderBy('transaction_journals.date')
- ->get(['transaction_journals.date', DB::raw('SUM(`transactions`.`amount`) as `dailyAmount`')]);
-
- return $set;
- }
-
- /**
- * @param Budget $budget
- *
- * @return Carbon
- */
- public function getFirstBudgetLimitDate(Budget $budget): Carbon
- {
- $limit = $budget->budgetlimits()->orderBy('startdate', 'ASC')->first();
- if ($limit) {
- return $limit->startdate;
- }
-
- return Carbon::now()->startOfYear();
- }
-
/**
* @return Collection
*/
@@ -504,65 +174,72 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
}
/**
- * Returns all the transaction journals for a limit, possibly limited by a limit repetition.
+ * @param Collection $budgets
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
*
- * @param Budget $budget
- * @param LimitRepetition $repetition
- * @param int $take
- *
- * @return LengthAwarePaginator
+ * @return Collection
*/
- public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator
+ public function journalsInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection
{
- $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0;
- $setQuery = $budget->transactionjournals()->expanded()
- ->take($take)->offset($offset)
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC');
- $countQuery = $budget->transactionjournals();
-
-
- if (!is_null($repetition->id)) {
- $setQuery->after($repetition->startdate)->before($repetition->enddate);
- $countQuery->after($repetition->startdate)->before($repetition->enddate);
+ $return = new Collection;
+ $accountIds = [];
+ // expand the number of grabbed fields:
+ $fields = TransactionJournal::queryFields();
+ $fields[] = 'source.account_id';
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
}
+ // first get all journals for all budget(s):
+ $journalQuery = $this->user->transactionjournals()
+ ->expanded()
+ ->sortCorrectly()
+ ->before($end)
+ ->after($start)
+ ->leftJoin(
+ 'transactions as source',
+ function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0');
+ }
+ )
+ ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
+ ->whereIn('budget_transaction_journal.budget_id', $budgets->pluck('id')->toArray());
+ // add account id's, if relevant:
+ if (count($accountIds) > 0) {
+ $journalQuery->whereIn('source.account_id', $accountIds);
+ }
+ // get them:
+ $journals = $journalQuery->get(TransactionJournal::queryFields());
- $set = $setQuery->get(TransactionJournal::QUERYFIELDS);
- $count = $countQuery->count();
+ // then get transactions themselves.
+ $transactionQuery = $this->user->transactionjournals()
+ ->expanded()
+ ->before($end)
+ ->sortCorrectly()
+ ->after($start)
+ ->leftJoin('transactions as related', 'related.transaction_journal_id', '=', 'transaction_journals.id')
+ ->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'related.id')
+ ->leftJoin(
+ 'transactions as source',
+ function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0');
+ }
+ )
+ ->whereIn('budget_transaction.budget_id', $budgets->pluck('id')->toArray());
+ if (count($accountIds) > 0) {
+ $transactionQuery->whereIn('source.account_id', $accountIds);
+ }
- $paginator = new LengthAwarePaginator($set, $count, $take, $offset);
+ $transactions = $transactionQuery->get($fields);
- return $paginator;
- }
+ // return complete set:
+ $return = $return->merge($transactions);
+ $return = $return->merge($journals);
- /**
- * @param Carbon $start
- * @param Carbon $end
- * @param int $page
- * @param int $pageSize
- *
- * @return LengthAwarePaginator
- */
- public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator
- {
- $offset = ($page - 1) * $pageSize;
- $query = $this->user
- ->transactionjournals()
- ->expanded()
- ->where('transaction_types.type', TransactionType::WITHDRAWAL)
- ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereNull('budget_transaction_journal.id')
- ->before($end)
- ->after($start);
-
- $count = $query->count();
- $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS);
- $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page);
-
- return $paginator;
+ return $return;
}
/**
@@ -572,20 +249,114 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
*
* @return Collection
*/
- public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection
+ public function journalsInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): Collection
{
- $ids = $accounts->pluck('id')->toArray();
+ $accountIds = [];
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ }
- return $this->user
+ /** @var Collection $set */
+ $query = $this->user
->transactionjournals()
->expanded()
- ->whereIn('source_account.id', $ids)
- ->where('transaction_types.type', TransactionType::WITHDRAWAL)
+ ->sortCorrectly()
+ ->transactionTypes([TransactionType::WITHDRAWAL])
->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->whereNull('budget_transaction_journal.id')
+ ->leftJoin(
+ 'transactions as source',
+ function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0');
+ }
+ )
->before($end)
- ->after($start)
- ->get(TransactionJournal::QUERYFIELDS);
+ ->after($start)->with(
+ [
+ 'transactions' => function (HasMany $query) {
+ $query->where('transactions.amount', '<', 0);
+ },
+ 'transactions.budgets',
+ ]
+ );
+
+ // add account id's, if relevant:
+ if (count($accountIds) > 0) {
+ $query->whereIn('source.account_id', $accountIds);
+ }
+
+ $set = $query->get(TransactionJournal::queryFields());
+ $set = $set->filter(
+ function (TransactionJournal $journal) {
+ foreach ($journal->transactions as $t) {
+ if ($t->budgets->count() === 0) {
+ return $journal;
+ }
+ }
+ }
+ );
+
+ return $set;
+ }
+
+ /**
+ * @param Collection $budgets
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string
+ {
+ // first collect actual transaction journals (fairly easy)
+ $query = $this->user
+ ->transactionjournals()
+ ->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+ }
+ )
+ ->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+
+ if ($end >= $start) {
+ $query->before($end)->after($start);
+ }
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $set = join(', ', $accountIds);
+ $query->whereRaw('(source.account_id in (' . $set . ') XOR destination.account_id in (' . $set . '))');
+ }
+ if ($budgets->count() > 0) {
+ $budgetIds = $budgets->pluck('id')->toArray();
+ $query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
+ $query->whereIn('budget_transaction_journal.budget_id', $budgetIds);
+ }
+
+ // that should do it:
+ $first = strval($query->sum('source.amount'));
+
+ // then collection transactions (harder)
+ $query = $this->user->transactions()
+ ->where('transactions.amount', '<', 0)
+ ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
+ ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'));
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->whereIn('transactions.account_id', $accountIds);
+ }
+ if ($budgets->count() > 0) {
+ $budgetIds = $budgets->pluck('id')->toArray();
+ $query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
+ $query->whereIn('budget_transaction.budget_id', $budgetIds);
+ }
+ $second = strval($query->sum('transactions.amount'));
+
+ return bcadd($first, $second);
}
/**
@@ -595,169 +366,38 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
*
* @return string
*/
- public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string
+ public function spentInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): string
{
- $ids = $accounts->pluck('id')->toArray();
- $entry = $this->user
- ->transactionjournals()
- ->whereNotIn(
- 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) {
- $query
- ->select('transaction_journals.id')
- ->from('transaction_journals')
- ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00'))
- ->whereNotNull('budget_transaction_journal.budget_id');
- }
- )
- ->after($start)
- ->before($end)
- ->leftJoin(
- 'transactions', function (JoinClause $join) {
- $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0);
- }
- )
- ->whereIn('transactions.account_id', $ids)
- ->transactionTypes([TransactionType::WITHDRAWAL])
- ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]);
- if (is_null($entry->journalAmount)) {
- return '0';
- }
-
- return $entry->journalAmount;
- }
-
- /**
- * Returns an array with the following key:value pairs:
- *
- * yyyy-mm-dd:
- *
- * That array contains:
- *
- * budgetid:
- *
- * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget
- * from the given users accounts..
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array
- {
- $ids = $accounts->pluck('id')->toArray();
- /** @var Collection $query */
- $query = $this->user->transactionJournals()
- ->transactionTypes([TransactionType::WITHDRAWAL])
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
- ->whereIn('transactions.account_id', $ids)
- ->where('transactions.amount', '<', 0)
+ $types = [TransactionType::WITHDRAWAL];
+ $query = $this->user->transactionjournals()
+ ->distinct()
+ ->transactionTypes($types)
+ ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
+ ->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+ }
+ )
+ ->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ )
+ ->leftJoin('budget_transaction', 'source.id', '=', 'budget_transaction.transaction_id')
+ ->whereNull('budget_transaction_journal.id')
+ ->whereNull('budget_transaction.id')
->before($end)
- ->after($start)
- ->groupBy('budget_id')
- ->groupBy('dateFormatted')
- ->get(
- ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id',
- DB::raw('SUM(`transactions`.`amount`) AS `sum`')]
- );
+ ->after($start);
- $return = [];
- foreach ($query->toArray() as $entry) {
- $budgetId = $entry['budget_id'];
- if (!isset($return[$budgetId])) {
- $return[$budgetId] = [];
- }
- $return[$budgetId][$entry['dateFormatted']] = $entry['sum'];
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+
+ $set = join(', ', $accountIds);
+ $query->whereRaw('(source.account_id in (' . $set . ') XOR destination.account_id in (' . $set . '))');
}
+ $sum = strval($query->sum('source.amount'));
- return $return;
- }
-
- /**
- * Returns a list of expenses (in the field "spent", grouped per budget per account.
- *
- * @param Collection $budgets
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $accountIds = $accounts->pluck('id')->toArray();
- $budgetIds = $budgets->pluck('id')->toArray();
- $set = $this->user->transactionjournals()
- ->leftJoin(
- 'transactions AS t_from', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions AS t_to', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0);
- }
- )
- ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
- ->whereIn('t_from.account_id', $accountIds)
- ->whereNotIn('t_to.account_id', $accountIds)
- ->where(
- function (Builder $q) use ($budgetIds) {
- $q->whereIn('budget_transaction_journal.budget_id', $budgetIds);
- $q->orWhereNull('budget_transaction_journal.budget_id');
- }
- )
- ->after($start)
- ->before($end)
- ->groupBy('t_from.account_id')
- ->groupBy('budget_transaction_journal.budget_id')
- ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])// opening balance is not an expense.
- ->get(
- [
- 't_from.account_id', 'budget_transaction_journal.budget_id',
- DB::raw('SUM(`t_from`.`amount`) AS `spent`'),
- ]
- );
-
- return $set;
-
- }
-
- /**
- * Returns an array with the following key:value pairs:
- *
- * yyyy-mm-dd:
- *
- * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $budget
- * from all the users accounts.
- *
- * @param Budget $budget
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentPerDay(Budget $budget, Carbon $start, Carbon $end): array
- {
- /** @var Collection $query */
- $query = $budget->transactionjournals()
- ->transactionTypes([TransactionType::WITHDRAWAL])
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.amount', '<', 0)
- ->before($end)
- ->after($start)
- ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]);
-
- $return = [];
- foreach ($query->toArray() as $entry) {
- $return[$entry['dateFormatted']] = $entry['sum'];
- }
-
- return $return;
+ return $sum;
}
/**
@@ -796,39 +436,55 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
/**
* @param Budget $budget
- * @param Carbon $date
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param string $range
* @param int $amount
*
* @return BudgetLimit
*/
- public function updateLimitAmount(Budget $budget, Carbon $date, int $amount): BudgetLimit
+ public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $range, int $amount) : BudgetLimit
{
- // there should be a budget limit for this startdate:
+ // there might be a budget limit for this startdate:
+ $repeatFreq = config('firefly.range_to_repeat_freq.' . $range);
/** @var BudgetLimit $limit */
- $limit = $budget->budgetlimits()->where('budget_limits.startdate', $date)->first(['budget_limits.*']);
+ $limit = $budget->budgetlimits()
+ ->where('budget_limits.startdate', $start)
+ ->where('budget_limits.repeat_freq', $repeatFreq)->first(['budget_limits.*']);
- if (!$limit) {
- // if not, create one!
- $limit = new BudgetLimit;
- $limit->budget()->associate($budget);
- $limit->startdate = $date;
- $limit->amount = $amount;
- $limit->repeat_freq = 'monthly';
- $limit->repeats = 0;
+ // delete if amount is zero.
+ if (!is_null($limit) && $amount <= 0.0) {
+ $limit->delete();
+
+ return new BudgetLimit;
+ }
+ // update if exists:
+ if (!is_null($limit)) {
+ $limit->amount = $amount;
$limit->save();
- // likewise, there should be a limit repetition to match the end date
- // (which is always the end of the month) but that is caught by an event.
+ // fire event to create or update LimitRepetition.
+ event(new BudgetLimitUpdated($limit, $end));
- } else {
- if ($amount > 0) {
- $limit->amount = $amount;
- $limit->save();
- } else {
- $limit->delete();
- }
+ return $limit;
}
+ // create one and return it.
+ $limit = new BudgetLimit;
+ $limit->budget()->associate($budget);
+ $limit->startdate = $start;
+ $limit->amount = $amount;
+ $limit->repeat_freq = $repeatFreq;
+ $limit->repeats = 0;
+ $limit->save();
+ event(new BudgetLimitStored($limit, $end));
+
+
+ // likewise, there should be a limit repetition to match the end date
+ // (which is always the end of the month) but that is caught by an event.
+ // so handled automatically.
+
return $limit;
}
+
}
diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php
index b7ea891594..6feeb5336c 100644
--- a/app/Repositories/Budget/BudgetRepositoryInterface.php
+++ b/app/Repositories/Budget/BudgetRepositoryInterface.php
@@ -1,14 +1,19 @@
- *
- * That array contains:
- *
- * budgetid:
- *
- * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget
- * from the given users accounts..
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array;
-
- /**
- * Returns a list of expenses (in the field "spent", grouped per budget per account.
- *
* @param Collection $budgets
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
- * @return Collection
+ * @return string
*/
- public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection;
-
- /**
- * Returns an array with the following key:value pairs:
- *
- * yyyy-mm-dd:
- *
- * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget
- * from all the users accounts.
- *
- * @param Budget $budget
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentPerDay(Budget $budget, Carbon $start, Carbon $end): array;
+ public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string;
/**
* @param array $data
@@ -294,11 +128,13 @@ interface BudgetRepositoryInterface
/**
* @param Budget $budget
- * @param Carbon $date
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param string $range
* @param int $amount
*
* @return BudgetLimit
*/
- public function updateLimitAmount(Budget $budget, Carbon $date, int $amount) : BudgetLimit;
+ public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $range, int $amount) : BudgetLimit;
}
diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php
index 14f5495fc4..4287eb429c 100644
--- a/app/Repositories/Category/CategoryRepository.php
+++ b/app/Repositories/Category/CategoryRepository.php
@@ -1,14 +1,23 @@
delete();
- $collection = $this->user->categories()
- ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id')
- ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->leftJoin(
- 'transactions AS t_src', function (JoinClause $join) {
- $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions AS t_dest', function (JoinClause $join) {
- $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0);
- }
- )
- ->whereIn('t_dest.account_id', $accounts->pluck('id')->toArray())// to these accounts (earned)
- ->whereNotIn('t_src.account_id', $accounts->pluck('id')->toArray())//-- but not from these accounts
- ->whereIn(
- 'transaction_types.type', [TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE]
- )
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->groupBy('categories.id')
- ->groupBy('dateFormatted')
- ->get(
- [
- 'categories.*',
- DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'),
- DB::raw('SUM(`t_dest`.`amount`) AS `earned`'),
- ]
- );
+ return true;
+ }
- return $collection;
+ /**
+ * @param Collection $categories
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string
+ {
+ $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
+ $sum = bcmul($this->sumInPeriod($categories, $accounts, $types, $start, $end), '-1');
+ return $sum;
}
+ /**
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ public function earnedInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) :string
+ {
+ $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
+ $sum = $this->sumInPeriodWithoutCategory($accounts, $types, $start, $end);
+
+ return $sum;
+ }
+
+ /**
+ * Find a category
+ *
+ * @param int $categoryId
+ *
+ * @return Category
+ */
+ public function find(int $categoryId) : Category
+ {
+ $category = $this->user->categories()->find($categoryId);
+ if (is_null($category)) {
+ $category = new Category;
+ }
+
+ return $category;
+ }
+
+ /**
+ * @param Category $category
+ * @param Collection $accounts
+ *
+ * @return Carbon
+ */
+ public function firstUseDate(Category $category, Collection $accounts): Carbon
+ {
+ $first = null;
+
+ /** @var TransactionJournal $first */
+ $firstJournalQuery = $category->transactionjournals()->orderBy('date', 'ASC');
+
+ if ($accounts->count() > 0) {
+ // filter journals:
+ $ids = $accounts->pluck('id')->toArray();
+ $firstJournalQuery->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id');
+ $firstJournalQuery->whereIn('t.account_id', $ids);
+ }
+
+ $firstJournal = $firstJournalQuery->first(['transaction_journals.date']);
+
+ if ($firstJournal) {
+ $first = $firstJournal->date;
+ }
+
+ // check transactions:
+
+ $firstTransactionQuery = $category->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->orderBy('transaction_journals.date', 'ASC');
+ if ($accounts->count() > 0) {
+ // filter journals:
+ $ids = $accounts->pluck('id')->toArray();
+ $firstTransactionQuery->whereIn('transactions.account_id', $ids);
+ }
+
+ $firstTransaction = $firstTransactionQuery->first(['transaction_journals.date']);
+
+ if (!is_null($firstTransaction) && ((!is_null($first) && $firstTransaction->date < $first) || is_null($first))) {
+ $first = new Carbon($firstTransaction->date);
+ }
+ if (is_null($first)) {
+ return new Carbon('1900-01-01');
+ }
+
+ return $first;
+ }
+
/**
* Returns a list of all the categories belonging to a user.
*
@@ -104,202 +169,389 @@ class CategoryRepository implements CategoryRepositoryInterface
}
/**
- * This method returns a very special collection for each category:
+ * @param Category $category
+ * @param int $page
+ * @param int $pageSize
*
- * category, year, expense/earned, amount
+ * @return LengthAwarePaginator
+ */
+ public function getJournals(Category $category, int $page, int $pageSize): LengthAwarePaginator
+ {
+ $complete = new Collection;
+ // first collect actual transaction journals (fairly easy)
+ $query = $this->user->transactionjournals()->expanded()->sortCorrectly();
+ $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
+ $query->where('category_transaction_journal.category_id', $category->id);
+ $first = $query->get(TransactionJournal::queryFields());
+
+ // then collection transactions (harder)
+ $query = $this->user->transactionjournals()->distinct()
+ ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id')
+ ->where('category_transaction.category_id', $category->id);
+ $second = $query->get(['transaction_journals.*']);
+
+ $complete = $complete->merge($first);
+ $complete = $complete->merge($second);
+
+ // sort:
+ /** @var Collection $complete */
+ $complete = $complete->sortByDesc(
+ function ($model) {
+ $date = new Carbon($model->date);
+
+ return intval($date->format('U'));
+ }
+ );
+ // create paginator
+ $offset = ($page - 1) * $pageSize;
+ $subSet = $complete->slice($offset, $pageSize)->all();
+ $paginator = new LengthAwarePaginator($subSet, $complete->count(), $pageSize, $page);
+
+ return $paginator;
+ }
+
+ /**
+ * Get all transactions in a category in a range.
*
- * categories can be duplicated.
+ * @param Collection $categories
+ * @param Collection $accounts
+ * @param array $types
+ * @param Carbon $start
+ * @param Carbon $end
*
+ * @return Collection
+ */
+ public function journalsInPeriod(Collection $categories, Collection $accounts, array $types, Carbon $start, Carbon $end): Collection
+ {
+ $complete = new Collection;
+ // first collect actual transaction journals (fairly easy)
+ $query = $this->user->transactionjournals()->expanded()->sortCorrectly();
+
+ if ($end >= $start) {
+ $query->before($end)->after($start);
+ }
+
+ if (count($types) > 0) {
+ $query->transactionTypes($types);
+ }
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id');
+ $query->whereIn('t.account_id', $accountIds);
+ }
+ if ($categories->count() > 0) {
+ $categoryIds = $categories->pluck('id')->toArray();
+ $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
+ $query->whereIn('category_transaction_journal.category_id', $categoryIds);
+ }
+
+ // that should do it:
+ $first = $query->get(TransactionJournal::queryFields());
+
+
+ // then collection transactions (harder)
+ $query = $this->user->transactionjournals()->distinct()
+ ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
+
+ if (count($types) > 0) {
+ $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
+ $query->whereIn('transaction_types.type', $types);
+ }
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->whereIn('transactions.account_id', $accountIds);
+ }
+ if ($categories->count() > 0) {
+ $categoryIds = $categories->pluck('id')->toArray();
+ $query->whereIn('category_transaction.category_id', $categoryIds);
+ }
+
+
+ $second = $query->get(['transaction_journals.*']);
+
+ $complete = $complete->merge($first);
+ $complete = $complete->merge($second);
+
+ return $complete;
+ }
+
+ /**
+ * @param Collection $accounts
+ * @param array $types
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function journalsInPeriodWithoutCategory(Collection $accounts, array $types, Carbon $start, Carbon $end) : Collection
+ {
+ /** @var Collection $set */
+ $query = $this->user
+ ->transactionjournals();
+ if (count($types) > 0) {
+ $query->transactionTypes($types);
+ }
+
+ $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
+ ->whereNull('category_transaction_journal.id')
+ ->before($end)
+ ->after($start);
+
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id');
+ $query->whereIn('t.account_id', $accountIds);
+ }
+
+ $set = $query->get(['transaction_journals.*']);
+
+ if ($set->count() == 0) {
+ return new Collection;
+ }
+
+ // grab all the transactions from this set.
+ // take only the journals with transactions that all have no category.
+ // select transactions left join journals where id in this set
+ // and left join transaction-category where null category
+ $journalIds = $set->pluck('id')->toArray();
+ $secondQuery = $this->user->transactions()
+ ->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id')
+ ->whereNull('category_transaction.id')
+ ->whereIn('transaction_journals.id', $journalIds);
+
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $secondQuery->whereIn('transactions.account_id', $accountIds);
+ }
+
+ // this second set REALLY doesn't have any categories.
+ $secondSet = $secondQuery->get(['transactions.transaction_journal_id']);
+ $allIds = $secondSet->pluck('transaction_journal_id')->toArray();
+ $return = $this->user->transactionjournals()->sortCorrectly()->expanded()->whereIn('transaction_journals.id', $allIds)->get(
+ TransactionJournal::queryFields()
+ );
+
+ return $return;
+
+
+ }
+
+ /**
+ * @param Category $category
+ * @param Collection $accounts
+ *
+ * @return Carbon
+ */
+ public function lastUseDate(Category $category, Collection $accounts): Carbon
+ {
+ $last = null;
+
+ /** @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);
+ }
+
+ $lastJournal = $lastJournalQuery->first(['transaction_journals.*']);
+
+ 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');
+ }
+
+ return $last;
+ }
+
+ /**
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
- * @return Collection
- */
- public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection
- {
-
- $set = $this->user->categories()
- ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id')
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'category_transaction_journal.transaction_journal_id')
- ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereIn('transaction_types.type', [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL])
- ->whereIn('transactions.account_id', $accounts->pluck('id')->toArray())
- ->whereIn('categories.id', $categories->pluck('id')->toArray())
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->groupBy('categories.id')
- ->groupBy('transaction_types.type')
- ->groupBy('dateFormatted')
- ->get(
- [
- 'categories.*',
- DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y") as `dateFormatted`'),
- 'transaction_types.type',
- DB::raw('SUM(`amount`) as `sum`'),
- ]
- );
-
- return $set;
-
- }
-
- /**
- * Returns a list of transaction journals in the range (all types, all accounts) that have no category
- * associated to them.
- *
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function listNoCategory(Carbon $start, Carbon $end): Collection
- {
- return $this->user
- ->transactionjournals()
- ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereNull('category_transaction_journal.id')
- ->before($end)
- ->after($start)
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC')
- ->get(['transaction_journals.*']);
- }
-
- /**
- * Returns a collection of Categories appended with the amount of money that has been spent
- * in these categories, based on the $accounts involved, in period X, grouped per month.
- * The amount spent in category X in period X is saved in field "spent".
- *
- * @param $accounts
- * @param $start
- * @param $end
- *
- * @return Collection
- */
- public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $accountIds = $accounts->pluck('id')->toArray();
- $query = $this->user->categories()
- ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id')
- ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->leftJoin(
- 'transactions AS t_src', function (JoinClause $join) {
- $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions AS t_dest', function (JoinClause $join) {
- $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0);
- }
- )
- ->whereIn(
- 'transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE]
- )// spent on these things.
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->groupBy('categories.id')
- ->groupBy('dateFormatted');
-
- if (count($accountIds) > 0) {
- $query->whereIn('t_src.account_id', $accountIds)// from these accounts (spent)
- ->whereNotIn('t_dest.account_id', $accountIds);//-- but not from these accounts (spent internally)
- }
-
- $collection = $query->get(
- [
- 'categories.*',
- DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'),
- DB::raw('SUM(`t_src`.`amount`) AS `spent`'),
- ]
- );
-
- return $collection;
- }
-
- /**
- * Returns the total amount of money related to transactions without any category connected to
- * it. Returns either the earned amount.
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
* @return string
*/
- public function sumEarnedNoCategory(Collection $accounts, Carbon $start, Carbon $end): string
+ public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string
{
- return $this->sumNoCategory($accounts, $start, $end, self::EARNED);
- }
-
- /**
- * Returns the total amount of money related to transactions without any category connected to
- * it. Returns either the spent amount.
- *
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return string
- */
- public function sumSpentNoCategory(Collection $accounts, Carbon $start, Carbon $end): string
- {
- $sum = $this->sumNoCategory($accounts, $start, $end, self::SPENT);
- if (is_null($sum)) {
- return '0';
- }
+ $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
+ $sum = $this->sumInPeriod($categories, $accounts, $types, $start, $end);
return $sum;
}
/**
- * Returns the total amount of money related to transactions without any category connected to
- * it. Returns either the earned or the spent amount.
- *
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
- * @param int $group
*
* @return string
*/
- protected function sumNoCategory(Collection $accounts, Carbon $start, Carbon $end, $group = self::EARNED)
+ public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : string
{
- $accountIds = $accounts->pluck('id')->toArray();
- if ($group == self::EARNED) {
- $types = [TransactionType::DEPOSIT];
- } else {
- $types = [TransactionType::WITHDRAWAL];
- }
+ $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
+ $sum = $this->sumInPeriodWithoutCategory($accounts, $types, $start, $end);
- // is withdrawal or transfer AND account_from is in the list of $accounts
- $query = $this->user
- ->transactionjournals()
- ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereNull('category_transaction_journal.id')
- ->before($end)
- ->after($start)
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->transactionTypes($types);
- if (count($accountIds) > 0) {
- $query->whereIn('transactions.account_id', $accountIds);
- }
+ return $sum;
+ }
-
- $single = $query->first(
+ /**
+ * @param array $data
+ *
+ * @return Category
+ */
+ public function store(array $data): Category
+ {
+ $newCategory = Category::firstOrCreateEncrypted(
[
- DB::raw('SUM(`transactions`.`amount`) as `sum`'),
+ 'user_id' => $data['user'],
+ 'name' => $data['name'],
]
);
- if (!is_null($single)) {
- return $single->sum;
+ $newCategory->save();
+
+ return $newCategory;
+ }
+
+ /**
+ * @param Category $category
+ * @param array $data
+ *
+ * @return Category
+ */
+ public function update(Category $category, array $data): Category
+ {
+ // update the account:
+ $category->name = $data['name'];
+ $category->save();
+
+ return $category;
+ }
+
+ /**
+ * @param Collection $categories
+ * @param Collection $accounts
+ * @param array $types
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ private function sumInPeriod(Collection $categories, Collection $accounts, array $types, Carbon $start, Carbon $end): string
+ {
+ // first collect actual transaction journals (fairly easy)
+ $query = $this->user
+ ->transactionjournals()
+ ->transactionTypes($types)
+ ->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+ }
+ )
+ ->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+
+ if ($end >= $start) {
+ $query->before($end)->after($start);
+ }
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $set = join(', ', $accountIds);
+ $query->whereRaw('(source.account_id in (' . $set . ') XOR destination.account_id in (' . $set . '))');
+
+ }
+ if ($categories->count() > 0) {
+ $categoryIds = $categories->pluck('id')->toArray();
+ $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
+ $query->whereIn('category_transaction_journal.category_id', $categoryIds);
}
- return '0';
+ // that should do it:
+ $first = strval($query->sum('source.amount'));
+
+ // then collection transactions (harder)
+ $query = $this->user->transactions()
+ ->where('transactions.amount', '<', 0)
+ ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
+ ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'));
+ if (count($types) > 0) {
+ $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
+ $query->whereIn('transaction_types.type', $types);
+ }
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+ $query->whereIn('transactions.account_id', $accountIds);
+ }
+ if ($categories->count() > 0) {
+ $categoryIds = $categories->pluck('id')->toArray();
+ $query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
+ $query->whereIn('category_transaction.category_id', $categoryIds);
+ }
+ $second = strval($query->sum('transactions.amount'));
+
+ return bcadd($first, $second);
+
+ }
+
+ /**
+ * @param Collection $accounts
+ * @param array $types
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return string
+ */
+ private function sumInPeriodWithoutCategory(Collection $accounts, array $types, Carbon $start, Carbon $end): string
+ {
+ $query = $this->user->transactionjournals()
+ ->distinct()
+ ->transactionTypes($types)
+ ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
+ ->leftJoin(
+ 'transactions as t', function (JoinClause $join) {
+ $join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0);
+ }
+ )
+ ->leftJoin('category_transaction', 't.id', '=', 'category_transaction.transaction_id')
+ ->whereNull('category_transaction_journal.id')
+ ->whereNull('category_transaction.id')
+ ->before($end)
+ ->after($start);
+
+ if ($accounts->count() > 0) {
+ $accountIds = $accounts->pluck('id')->toArray();
+
+ $query->whereIn('t.account_id', $accountIds);
+ }
+ $sum = strval($query->sum('t.amount'));
+
+ return $sum;
}
}
diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php
index 5c5966c8b7..3df5717ab9 100644
--- a/app/Repositories/Category/CategoryRepositoryInterface.php
+++ b/app/Repositories/Category/CategoryRepositoryInterface.php
@@ -1,9 +1,19 @@
user = $user;
- }
-
- /**
- * @param Category $category
- *
- * @return int
- */
- public function countJournals(Category $category): int
- {
- return $category->transactionjournals()->count();
-
- }
-
- /**
- * @param Category $category
- *
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return int
- */
- public function countJournalsInRange(Category $category, Carbon $start, Carbon $end): int
- {
- return $category->transactionjournals()->before($end)->after($start)->count();
- }
-
- /**
- * @param Category $category
- *
- * @return bool
- */
- public function destroy(Category $category): bool
- {
- $category->delete();
-
- return true;
- }
-
- /**
- * Returns an array with the following key:value pairs:
- *
- * yyyy-mm-dd:
- *
- * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category
- * from all the users $accounts.
- *
- * @param Category $category
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function earnedPerDay(Category $category, Carbon $start, Carbon $end): array
- {
- /** @var Collection $query */
- $query = $category->transactionjournals()
- ->transactionTypes([TransactionType::DEPOSIT])
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.amount', '>', 0)
- ->before($end)
- ->after($start)
- ->groupBy('date')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]);
-
- $return = [];
- foreach ($query->toArray() as $entry) {
- $return[$entry['dateFormatted']] = $entry['sum'];
- }
-
- return $return;
- }
-
- /**
- * Find a category
- *
- * @param int $categoryId
- *
- * @return Category
- */
- public function find(int $categoryId) : Category
- {
- $category = $this->user->categories()->find($categoryId);
- if (is_null($category)) {
- $category = new Category;
- }
-
- return $category;
- }
-
- /**
- * @param Category $category
- *
- * @return Carbon
- */
- public function getFirstActivityDate(Category $category): Carbon
- {
- /** @var TransactionJournal $first */
- $first = $category->transactionjournals()->orderBy('date', 'ASC')->first();
- if ($first) {
- return $first->date;
- }
-
- return new Carbon;
-
- }
-
- /**
- * @param Category $category
- * @param int $page
- * @param int $pageSize
- *
- * @return Collection
- */
- public function getJournals(Category $category, int $page, int $pageSize = 50): Collection
- {
- $offset = $page > 0 ? $page * $pageSize : 0;
-
- return $category->transactionjournals()->expanded()->take($pageSize)->offset($offset)
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC')
- ->get(TransactionJournal::QUERYFIELDS);
-
- }
-
- /**
- * @param Category $category
- * @param Collection $accounts
- *
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $ids = $accounts->pluck('id')->toArray();
-
- return $category->transactionjournals()
- ->after($start)
- ->before($end)
- ->expanded()
- ->whereIn('source_account.id', $ids)
- ->whereNotIn('destination_account.id', $ids)
- ->get(TransactionJournal::QUERYFIELDS);
- }
-
- /**
- * @param Category $category
- * @param Carbon $start
- * @param Carbon $end
- * @param int $page
- * @param int $pageSize
- *
- *
- * @return Collection
- */
- public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection
- {
- $offset = $page > 0 ? $page * $pageSize : 0;
-
- return $category->transactionjournals()
- ->after($start)
- ->before($end)
- ->expanded()
- ->take($pageSize)
- ->offset($offset)
- ->get(TransactionJournal::QUERYFIELDS);
- }
-
- /**
- * @param Category $category
- *
- * @return Carbon|null
- */
- public function getLatestActivity(Category $category): Carbon
- {
- $latest = $category->transactionjournals()
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC')
- ->first();
- if ($latest) {
- return $latest->date;
- }
-
- return new Carbon('1900-01-01');
- }
-
- /**
- * Returns an array with the following key:value pairs:
- *
- * yyyy-mm-dd:
- *
- * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $category
- * from all the users accounts.
- *
- * @param Category $category
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentPerDay(Category $category, Carbon $start, Carbon $end): array
- {
- /** @var Collection $query */
- $query = $category->transactionjournals()
- ->transactionTypes([TransactionType::WITHDRAWAL])
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.amount', '<', 0)
- ->before($end)
- ->after($start)
- ->groupBy('date')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]);
-
- $return = [];
- foreach ($query->toArray() as $entry) {
- $return[$entry['dateFormatted']] = $entry['sum'];
- }
-
- return $return;
- }
-
- /**
- * @param array $data
- *
- * @return Category
- */
- public function store(array $data): Category
- {
- $newCategory = Category::firstOrCreateEncrypted(
- [
- 'user_id' => $data['user'],
- 'name' => $data['name'],
- ]
- );
- $newCategory->save();
-
- return $newCategory;
- }
-
- /**
- * @param Category $category
- * @param array $data
- *
- * @return Category
- */
- public function update(Category $category, array $data): Category
- {
- // update the account:
- $category->name = $data['name'];
- $category->save();
-
- return $category;
- }
-}
diff --git a/app/Repositories/Category/SingleCategoryRepositoryInterface.php b/app/Repositories/Category/SingleCategoryRepositoryInterface.php
deleted file mode 100644
index 011fc522a1..0000000000
--- a/app/Repositories/Category/SingleCategoryRepositoryInterface.php
+++ /dev/null
@@ -1,144 +0,0 @@
-
- *
- * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category
- * from all the users accounts.
- *
- * @param Category $category
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function earnedPerDay(Category $category, Carbon $start, Carbon $end): array;
-
- /**
- * Find a category
- *
- * @param int $categoryId
- *
- * @return Category
- */
- public function find(int $categoryId) : Category;
-
- /**
- * @param Category $category
- *
- * @return Carbon
- */
- public function getFirstActivityDate(Category $category): Carbon;
-
- /**
- * @param Category $category
- * @param int $page
- * @param int $pageSize
- *
- * @return Collection
- */
- public function getJournals(Category $category, int $page, int $pageSize = 50): Collection;
-
- /**
- * @param Category $category
- * @param Collection $accounts
- *
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection;
-
- /**
- * @param Category $category
- * @param Carbon $start
- * @param Carbon $end
- * @param int $page
- * @param int $pageSize
- *
- *
- * @return Collection
- */
- public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection;
-
- /**
- * @param Category $category
- *
- * @return Carbon
- */
- public function getLatestActivity(Category $category): Carbon;
-
- /**
- * Returns an array with the following key:value pairs:
- *
- * yyyy-mm-dd:
- *
- * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $category
- * from all the users accounts.
- *
- * @param Category $category
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return array
- */
- public function spentPerDay(Category $category, Carbon $start, Carbon $end): array;
-
-
- /**
- * @param array $data
- *
- * @return Category
- */
- public function store(array $data): Category;
-
- /**
- * @param Category $category
- * @param array $data
- *
- * @return Category
- */
- public function update(Category $category, array $data): Category;
-}
diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php
index f9d60c9740..f713cc22ad 100644
--- a/app/Repositories/Currency/CurrencyRepository.php
+++ b/app/Repositories/Currency/CurrencyRepository.php
@@ -1,4 +1,12 @@
user()->associate($this->user);
- /*
- * In theory this random string could give db error.
- */
- $exportJob->key = Str::random(12);
- $exportJob->status = 'export_status_never_started';
- $exportJob->save();
+ $count = 0;
+ while ($count < 30) {
+ $key = Str::random(12);
+ $existing = $this->findByKey($key);
+ if (is_null($existing->id)) {
+ $exportJob = new ExportJob;
+ $exportJob->user()->associate($this->user);
+ $exportJob->key = Str::random(12);
+ $exportJob->status = 'export_status_never_started';
+ $exportJob->save();
+
+ // breaks the loop:
+
+ return $exportJob;
+ }
+ $count++;
+
+ }
+
+ return new ExportJob;
- return $exportJob;
}
/**
- *
- * FIXME this may return null
- *
* @param string $key
*
* @return ExportJob|null
*/
public function findByKey(string $key): ExportJob
{
- return $this->user->exportJobs()->where('key', $key)->first();
+ $result = $this->user->exportJobs()->where('key', $key)->first();
+ if (is_null($result)) {
+ return new ExportJob;
+ }
+
+ return $result;
}
}
diff --git a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php
index 43572ed1c2..18ef2827f0 100644
--- a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php
+++ b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php
@@ -1,5 +1,4 @@
accounts = $accounts;
- $this->user = $user;
- $this->start = $start;
- $this->end = $end;
- }
-
- /**
- * @deprecated
- * @return Collection
- */
- public function collect(): Collection
- {
- // get all the journals:
- $ids = $this->accounts->pluck('id')->toArray();
-
- return $this->user->transactionjournals()
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->whereIn('transactions.account_id', $ids)
- ->before($this->end)
- ->after($this->start)
- ->orderBy('transaction_journals.date')
- ->get(['transaction_journals.*']);
- }
-
-}
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index 71e7beb15d..51c1ebd969 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -1,4 +1,12 @@
user = $user;
}
+ /**
+ * Returns the amount in the account before the specified transaction took place.
+ *
+ * @param Transaction $transaction
+ *
+ * @return string
+ */
+ public function balanceBeforeTransaction(Transaction $transaction): string
+ {
+ // some dates from journal
+ $journal = $transaction->transactionJournal;
+ $query = Transaction::
+ leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transactions.account_id', $transaction->account_id)
+ ->where('transaction_journals.user_id', $this->user->id)
+ ->where(
+ function (Builder $q) use ($journal) {
+ $q->where('transaction_journals.date', '<', $journal->date->format('Y-m-d'));
+ $q->orWhere(
+ function (Builder $qq) use ($journal) {
+ $qq->where('transaction_journals.date', '=', $journal->date->format('Y-m-d'));
+ $qq->where('transaction_journals.order', '>', $journal->order);
+ }
+ );
+
+ }
+ )
+ ->where('transactions.id', '!=', $transaction->id)
+ ->whereNull('transactions.deleted_at')
+ ->whereNull('transaction_journals.deleted_at')
+ ->orderBy('transaction_journals.date', 'DESC')
+ ->orderBy('transaction_journals.order', 'ASC')
+ ->orderBy('transaction_journals.id', 'DESC');
+ $sum = $query->sum('transactions.amount');
+
+ return strval($sum);
+ }
+
/**
* @param TransactionJournal $journal
*
@@ -46,6 +96,11 @@ class JournalRepository implements JournalRepositoryInterface
*/
public function delete(TransactionJournal $journal): bool
{
+ /** @var Transaction $transaction */
+ foreach ($journal->transactions()->get() as $transaction) {
+ $transaction->delete();
+ }
+
$journal->delete();
return true;
@@ -74,68 +129,15 @@ class JournalRepository implements JournalRepositoryInterface
public function first(): TransactionJournal
{
$entry = $this->user->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']);
+
if (is_null($entry)) {
+
return new TransactionJournal;
}
return $entry;
}
- /**
- * @param TransactionJournal $journal
- * @param Transaction $transaction
- *
- * @return string
- */
- public function getAmountBefore(TransactionJournal $journal, Transaction $transaction): string
- {
- $set = $transaction->account->transactions()->leftJoin(
- 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
- )
- ->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d'))
- ->where('transaction_journals.order', '>=', $journal->order)
- ->where('transaction_journals.id', '!=', $journal->id)
- ->get(['transactions.*']);
- $sum = '0';
- foreach ($set as $entry) {
- $sum = bcadd($entry->amount, $sum);
- }
-
- return $sum;
-
- }
-
- /**
- * @param array $types
- * @param int $offset
- * @param int $count
- *
- * @return Collection
- */
- public function getCollectionOfTypes(array $types, int $offset, int $count): Collection
- {
- $set = $this->user->transactionJournals()
- ->expanded()
- ->transactionTypes($types)
- ->take($count)->offset($offset)
- ->orderBy('date', 'DESC')
- ->orderBy('order', 'ASC')
- ->orderBy('id', 'DESC')
- ->get(TransactionJournal::QUERYFIELDS);
-
- return $set;
- }
-
- /**
- * @param TransactionType $dbType
- *
- * @return Collection
- */
- public function getJournalsOfType(TransactionType $dbType): Collection
- {
- return $this->user->transactionjournals()->where('transaction_type_id', $dbType->id)->orderBy('id', 'DESC')->take(50)->get();
- }
-
/**
* @param array $types
* @param int $page
@@ -143,68 +145,141 @@ class JournalRepository implements JournalRepositoryInterface
*
* @return LengthAwarePaginator
*/
- public function getJournalsOfTypes(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
+ public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
{
$offset = ($page - 1) * $pageSize;
- $query = $this->user
- ->transactionJournals()
- ->expanded()
- ->transactionTypes($types);
-
-
- $count = $query->count();
- $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS);
+ $query = $this->user->transactionJournals()->expanded()->sortCorrectly();
+ if (count($types) > 0) {
+ $query->transactionTypes($types);
+ }
+ $count = $this->user->transactionJournals()->transactionTypes($types)->count();
+ $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
$journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
return $journals;
}
/**
- * @param string $type
+ * Returns a collection of ALL journals, given a specific account and a date range.
*
- * @return TransactionType
+ * @param Collection $accounts
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
*/
- public function getTransactionType(string $type): TransactionType
+ public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection
{
- return TransactionType::whereType($type)->first();
- }
+ $query = $this->user->transactionJournals()->expanded()->sortCorrectly();
+ $query->before($end);
+ $query->after($start);
- /**
- * @param int $journalId
- * @param Carbon $date
- *
- * @return TransactionJournal
- */
- public function getWithDate(int $journalId, Carbon $date): TransactionJournal
- {
- return $this->user->transactionjournals()->where('id', $journalId)->where('date', $date->format('Y-m-d 00:00:00'))->first();
- }
-
- /**
- *
- * * 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
- */
- public function saveTags(TransactionJournal $journal, array $array): bool
- {
- /** @var \FireflyIII\Repositories\Tag\TagRepositoryInterface $tagRepository */
- $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface');
-
- foreach ($array as $name) {
- if (strlen(trim($name)) > 0) {
- $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
- if (!is_null($tag)) {
- $tagRepository->connect($journal, $tag);
- }
+ if ($accounts->count() > 0) {
+ $ids = $accounts->pluck('id')->toArray();
+ // join source and destination:
+ $query->leftJoin(
+ 'transactions as source', function (JoinClause $join) {
+ $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
}
+ );
+ $query->leftJoin(
+ 'transactions as destination', function (JoinClause $join) {
+ $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+ }
+ );
+
+ $query->where(
+ function (Builder $q) use ($ids) {
+ $q->whereIn('destination.account_id', $ids);
+ $q->orWhereIn('source.account_id', $ids);
+ }
+ );
}
- return true;
+ $set = $query->get(TransactionJournal::queryFields());
+
+ return $set;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ *
+ * @return Collection
+ */
+ public function getPiggyBankEvents(TransactionJournal $journal): Collection
+ {
+ /** @var Collection $set */
+ $events = $journal->piggyBankEvents()->get();
+ $events->each(
+ function (PiggyBankEvent $event) {
+ $event->piggyBank = $event->piggyBank()->withTrashed()->first();
+ }
+ );
+
+ return $events;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ *
+ * @return Collection
+ */
+ public function getTransactions(TransactionJournal $journal): Collection
+ {
+ $transactions = new Collection;
+ switch ($journal->transactionType->type) {
+ case TransactionType::DEPOSIT:
+ /** @var Collection $transactions */
+ $transactions = $journal->transactions()
+ ->groupBy('transactions.account_id')
+ ->where('amount', '<', 0)
+ ->groupBy('transactions.id')
+ ->orderBy('amount', 'ASC')->get(
+ ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+ );
+ $final = $journal->transactions()
+ ->groupBy('transactions.account_id')
+ ->where('amount', '>', 0)
+ ->orderBy('amount', 'ASC')->first(
+ ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+ );
+ $transactions->push($final);
+ break;
+ case TransactionType::TRANSFER:
+
+ /** @var Collection $transactions */
+ $transactions = $journal->transactions()
+ ->groupBy('transactions.id')
+ ->orderBy('transactions.id')->get(
+ ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+ );
+ break;
+ case TransactionType::WITHDRAWAL:
+
+ /** @var Collection $transactions */
+ $transactions = $journal->transactions()
+ ->where('amount', '>', 0)
+ ->groupBy('transactions.id')
+ ->orderBy('amount', 'ASC')->get(
+ ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+ );
+ $final = $journal->transactions()
+ ->where('amount', '<', 0)
+ ->groupBy('transactions.account_id')
+ ->orderBy('amount', 'ASC')->first(
+ ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+ );
+ $transactions->push($final);
+ break;
+ }
+ // foreach do balance thing
+ $transactions->each(
+ function (Transaction $t) {
+ $t->before = $this->balanceBeforeTransaction($t);
+ }
+ );
+
+ return $transactions;
}
/**
@@ -233,7 +308,6 @@ class JournalRepository implements JournalRepositoryInterface
);
$journal->save();
-
// store or get category
if (strlen($data['category']) > 0) {
$category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
@@ -248,19 +322,19 @@ class JournalRepository implements JournalRepositoryInterface
}
// store accounts (depends on type)
- list($fromAccount, $toAccount) = $this->storeAccounts($transactionType, $data);
+ list($sourceAccount, $destinationAccount) = $this->storeAccounts($transactionType, $data);
// store accompanying transactions.
Transaction::create( // first transaction.
[
- 'account_id' => $fromAccount->id,
+ 'account_id' => $sourceAccount->id,
'transaction_journal_id' => $journal->id,
'amount' => $data['amount'] * -1,
]
);
Transaction::create( // second transaction.
[
- 'account_id' => $toAccount->id,
+ 'account_id' => $destinationAccount->id,
'transaction_journal_id' => $journal->id,
'amount' => $data['amount'],
]
@@ -278,6 +352,37 @@ class JournalRepository implements JournalRepositoryInterface
}
+ /**
+ * Store journal only, uncompleted, with attachments if necessary.
+ *
+ * @param array $data
+ *
+ * @return TransactionJournal
+ */
+ public function storeJournal(array $data): TransactionJournal
+ {
+ // find transaction type.
+ $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
+
+ // store actual journal.
+ $journal = new TransactionJournal(
+ [
+ 'user_id' => $data['user'],
+ 'transaction_type_id' => $transactionType->id,
+ 'transaction_currency_id' => $data['amount_currency_id_amount'],
+ 'description' => $data['description'],
+ 'completed' => 0,
+ 'date' => $data['date'],
+ 'interest_date' => $data['interest_date'],
+ 'book_date' => $data['book_date'],
+ 'process_date' => $data['process_date'],
+ ]
+ );
+ $journal->save();
+
+ return $journal;
+ }
+
/**
* @param TransactionJournal $journal
* @param array $data
@@ -306,7 +411,7 @@ class JournalRepository implements JournalRepositoryInterface
$journal->budgets()->detach();
if (intval($data['budget_id']) > 0) {
/** @var \FireflyIII\Models\Budget $budget */
- $budget = Budget::find($data['budget_id']);
+ $budget = Budget::where('user_id', $this->user->id)->where('id', $data['budget_id'])->first();
$journal->budgets()->save($budget);
}
@@ -341,16 +446,143 @@ class JournalRepository implements JournalRepositoryInterface
}
/**
+ *
+ * * 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
*/
- public function updateTags(TransactionJournal $journal, array $array): 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)) {
+ $tagRepository->connect($journal, $tag);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param TransactionType $type
+ * @param array $data
+ *
+ * @return array
+ * @throws FireflyException
+ */
+ private function storeAccounts(TransactionType $type, array $data): array
+ {
+ $sourceAccount = null;
+ $destinationAccount = null;
+ switch ($type->type) {
+ case TransactionType::WITHDRAWAL:
+ list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data);
+ break;
+
+ case TransactionType::DEPOSIT:
+ list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data);
+
+ break;
+ case TransactionType::TRANSFER:
+ $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
+ $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
+ break;
+ default:
+ throw new FireflyException('Did not recognise transaction type.');
+ }
+
+ if (is_null($destinationAccount)) {
+ Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]);
+ throw new FireflyException('"destination"-account is null, so we cannot continue!');
+ }
+
+ if (is_null($sourceAccount)) {
+ Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]);
+ throw new FireflyException('"source"-account is null, so we cannot continue!');
+
+ }
+
+
+ return [$sourceAccount, $destinationAccount];
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ */
+ private function storeDepositAccounts(array $data): array
+ {
+ $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
+
+ if (strlen($data['source_account_name']) > 0) {
+ $fromType = AccountType::where('type', 'Revenue account')->first();
+ $fromAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
+ );
+
+ return [$fromAccount, $destinationAccount];
+ }
+ $fromType = AccountType::where('type', 'Cash account')->first();
+ $fromAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
+ );
+
+ return [$fromAccount, $destinationAccount];
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ */
+ private function storeWithdrawalAccounts(array $data): array
+ {
+ $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
+
+ if (strlen($data['destination_account_name']) > 0) {
+ $destinationType = AccountType::where('type', 'Expense account')->first();
+ $destinationAccount = Account::firstOrCreateEncrypted(
+ [
+ 'user_id' => $data['user'],
+ 'account_type_id' => $destinationType->id,
+ 'name' => $data['destination_account_name'],
+ 'active' => 1,
+ ]
+ );
+
+ return [$sourceAccount, $destinationAccount];
+ }
+ $destinationType = AccountType::where('type', 'Cash account')->first();
+ $destinationAccount = Account::firstOrCreateEncrypted(
+ ['user_id' => $data['user'], 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
+ );
+
+ return [$sourceAccount, $destinationAccount];
+
+
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param array $array
+ *
+ * @return bool
+ */
+ private function updateTags(TransactionJournal $journal, array $array): bool
{
// create tag repository
- /** @var \FireflyIII\Repositories\Tag\TagRepositoryInterface $tagRepository */
- $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface');
+ /** @var TagRepositoryInterface $tagRepository */
+ $tagRepository = app(TagRepositoryInterface::class);
// find or create all tags:
@@ -381,96 +613,4 @@ class JournalRepository implements JournalRepositoryInterface
return true;
}
-
- /**
- * @param TransactionType $type
- * @param array $data
- *
- * @return array
- * @throws FireflyException
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- */
- protected function storeAccounts(TransactionType $type, array $data): array
- {
- $fromAccount = null;
- $toAccount = null;
- switch ($type->type) {
- case TransactionType::WITHDRAWAL:
- list($fromAccount, $toAccount) = $this->storeWithdrawalAccounts($data);
- break;
-
- case TransactionType::DEPOSIT:
- list($fromAccount, $toAccount) = $this->storeDepositAccounts($data);
-
- break;
- case TransactionType::TRANSFER:
- $fromAccount = Account::find($data['account_from_id']);
- $toAccount = Account::find($data['account_to_id']);
- break;
- default:
- throw new FireflyException('Did not recognise transaction type.');
- }
-
- if (is_null($toAccount)) {
- Log::error('"to"-account is null, so we cannot continue!', ['data' => $data]);
- throw new FireflyException('"to"-account is null, so we cannot continue!');
- }
-
- if (is_null($fromAccount)) {
- Log::error('"from"-account is null, so we cannot continue!', ['data' => $data]);
- throw new FireflyException('"from"-account is null, so we cannot continue!');
-
- }
-
-
- return [$fromAccount, $toAccount];
- }
-
- /**
- * @param array $data
- *
- * @return array
- */
- protected function storeDepositAccounts(array $data): array
- {
- $toAccount = Account::find($data['account_id']);
-
- if (strlen($data['revenue_account']) > 0) {
- $fromType = AccountType::where('type', 'Revenue account')->first();
- $fromAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['revenue_account'], 'active' => 1]
- );
- } else {
- $toType = AccountType::where('type', 'Cash account')->first();
- $fromAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]
- );
- }
-
- return [$fromAccount, $toAccount];
- }
-
- /**
- * @param array $data
- *
- * @return array
- */
- protected function storeWithdrawalAccounts(array $data): array
- {
- $fromAccount = Account::find($data['account_id']);
-
- if (strlen($data['expense_account']) > 0) {
- $toType = AccountType::where('type', 'Expense account')->first();
- $toAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => $data['expense_account'], 'active' => 1]
- );
- } else {
- $toType = AccountType::where('type', 'Cash account')->first();
- $toAccount = Account::firstOrCreateEncrypted(
- ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]
- );
- }
-
- return [$fromAccount, $toAccount];
- }
}
diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php
index ea4526806c..0b452ec64c 100644
--- a/app/Repositories/Journal/JournalRepositoryInterface.php
+++ b/app/Repositories/Journal/JournalRepositoryInterface.php
@@ -1,4 +1,12 @@
where('piggy_bank_id', $piggyBank->id)->groupBy('date')->get(['date', DB::raw('SUM(`amount`) AS `sum`')]);
+ $piggyBank = $this->user->piggyBanks()->where('piggy_banks.id', $piggyBankid)->first(['piggy_banks.*']);
+ if (!is_null($piggyBank)) {
+ return $piggyBank;
+ }
- return new Collection($var);
+ return new PiggyBank();
}
/**
@@ -97,6 +109,21 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
return $set;
}
+ /**
+ * Also add amount in name.
+ *
+ * @return Collection
+ */
+ public function getPiggyBanksWithAmount() : Collection
+ {
+ $set = $this->getPiggyBanks();
+ foreach ($set as $piggy) {
+ $piggy->name = $piggy->name . ' (' . Amount::format($piggy->currentRelevantRep()->currentamount, false) . ')';
+ }
+
+ return $set;
+ }
+
/**
* Set all piggy banks to order 0.
*
diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
index 206e4704b8..54f3dd655f 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
@@ -1,4 +1,12 @@
order = ($rule->order + 1);
$rule->save();
$this->resetRulesInGroupOrder($rule->ruleGroup);
+
return true;
}
@@ -141,6 +143,7 @@ class RuleRepository implements RuleRepositoryInterface
$rule->order = ($rule->order - 1);
$rule->save();
$this->resetRulesInGroupOrder($rule->ruleGroup);
+
return true;
}
@@ -342,6 +345,7 @@ class RuleRepository implements RuleRepositoryInterface
$this->storeAction($rule, $actionValues);
}
+
return true;
}
@@ -349,6 +353,7 @@ class RuleRepository implements RuleRepositoryInterface
/**
* @param Rule $rule
* @param array $data
+ *
* @return bool
*/
private function storeTriggers(Rule $rule, array $data): bool
@@ -378,6 +383,7 @@ class RuleRepository implements RuleRepositoryInterface
$this->storeTrigger($rule, $triggerValues);
$order++;
}
+
return true;
}
}
diff --git a/app/Repositories/Rule/RuleRepositoryInterface.php b/app/Repositories/Rule/RuleRepositoryInterface.php
index 4c98093d20..2acf299b30 100644
--- a/app/Repositories/Rule/RuleRepositoryInterface.php
+++ b/app/Repositories/Rule/RuleRepositoryInterface.php
@@ -1,5 +1,4 @@
delete();
- } else {
- // move
- $rule->ruleGroup()->associate($moveTo);
- $rule->save();
+ continue;
}
+ // move
+ $rule->ruleGroup()->associate($moveTo);
+ $rule->save();
}
$ruleGroup->delete();
@@ -133,6 +141,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
$ruleGroup->order = ($ruleGroup->order + 1);
$ruleGroup->save();
$this->resetRuleGroupOrder();
+
return true;
}
@@ -155,6 +164,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
$ruleGroup->order = ($ruleGroup->order - 1);
$ruleGroup->save();
$this->resetRuleGroupOrder();
+
return true;
}
diff --git a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php
index 8e91189457..5faab4fbe3 100644
--- a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php
+++ b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php
@@ -1,4 +1,12 @@
pluck('id')->toArray();
-
-
- $entry = $object->transactionjournals()
- ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE])
- ->before($end)
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
- ->whereIn('accounts.id', $ids)
- ->after($start)
- ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]);
- $amount = $entry->journalAmount ?? '0';
-
- return $amount;
- }
-}
diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php
index a9cbea562f..b2ae35a528 100644
--- a/app/Repositories/Tag/TagRepository.php
+++ b/app/Repositories/Tag/TagRepository.php
@@ -1,17 +1,21 @@
user = $user;
}
- /**
- * @param Collection $accounts
- * @param Carbon $start
- * @param Carbon $end
- *
- * @return Collection
- */
- public function allCoveredByBalancingActs(Collection $accounts, Carbon $start, Carbon $end): Collection
- {
- $ids = $accounts->pluck('id')->toArray();
- $set = $this->user->tags()
- ->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id')
- ->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
- ->leftJoin(
- 'transactions AS t_from', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0);
- }
- )
- ->leftJoin(
- 'transactions AS t_to', function (JoinClause $join) {
- $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0);
- }
- )
- ->where('tags.tagMode', 'balancingAct')
- ->where('transaction_types.type', TransactionType::TRANSFER)
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
- ->whereNull('transaction_journals.deleted_at')
- ->whereIn('t_from.account_id', $ids)
- ->whereIn('t_to.account_id', $ids)
- ->groupBy('t_to.account_id')
- ->get(
- [
- 't_to.account_id',
- DB::raw('SUM(`t_to`.`amount`) as `sum`'),
- ]
- );
-
- return $set;
- }
-
/**
*
* @param TransactionJournal $journal
* @param Tag $tag
*
- * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
- *
* @return bool
*/
public function connect(TransactionJournal $journal, Tag $tag): bool
@@ -161,74 +121,6 @@ class TagRepository implements TagRepositoryInterface
}
- /**
- * Can a tag become an advance payment?
- *
- * @param Tag $tag
- *
- * @return bool
- */
- public function tagAllowAdvance(Tag $tag): bool
- {
- /*
- * If this tag is a balancing act, and it contains transfers, it cannot be
- * changes to an advancePayment.
- */
-
- if ($tag->tagMode == 'balancingAct' || $tag->tagMode == 'nothing') {
- foreach ($tag->transactionjournals as $journal) {
- if ($journal->isTransfer()) {
- return false;
- }
- }
- }
-
- /*
- * If this tag contains more than one expenses, it cannot become an advance payment.
- */
- $count = 0;
- foreach ($tag->transactionjournals as $journal) {
- if ($journal->isWithdrawal()) {
- $count++;
- }
- }
- if ($count > 1) {
- return false;
- }
-
- return true;
-
- }
-
- /**
- * Can a tag become a balancing act?
- *
- * @param Tag $tag
- *
- * @return bool
- */
- public function tagAllowBalancing(Tag $tag): bool
- {
- /*
- * If has more than two transactions already, cannot become a balancing act:
- */
- if ($tag->transactionjournals->count() > 2) {
- return false;
- }
-
- /*
- * If any transaction is a deposit, cannot become a balancing act.
- */
- foreach ($tag->transactionjournals as $journal) {
- if ($journal->isDeposit()) {
- return false;
- }
- }
-
- return true;
- }
-
-
/**
* @param Tag $tag
* @param array $data
@@ -253,8 +145,6 @@ class TagRepository implements TagRepositoryInterface
* @param TransactionJournal $journal
* @param Tag $tag
*
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- *
* @return bool
*/
protected function connectAdvancePayment(TransactionJournal $journal, Tag $tag): bool
@@ -333,25 +223,32 @@ class TagRepository implements TagRepositoryInterface
}
/**
+ * The incoming journal ($journal)'s accounts (source accounts for a withdrawal, destination accounts for a deposit)
+ * must match the already existing transaction's accounts exactly.
+ *
* @param TransactionJournal $journal
* @param Tag $tag
*
- * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's complex but nothing can be done.
*
* @return bool
*/
protected function matchAll(TransactionJournal $journal, Tag $tag): bool
{
+ $checkSources = join(',', TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray());
+ $checkDestinations = join(',', TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray());
+
$match = true;
/** @var TransactionJournal $check */
foreach ($tag->transactionjournals as $check) {
// $checkAccount is the source_account for a withdrawal
// $checkAccount is the destination_account for a deposit
+ $thisSources = join(',', TransactionJournal::sourceAccountList($check)->pluck('id')->toArray());
+ $thisDestinations = join(',', TransactionJournal::destinationAccountList($check)->pluck('id')->toArray());
- if ($check->isWithdrawal() && TransactionJournal::sourceAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) {
+ if ($check->isWithdrawal() && $thisSources !== $checkSources) {
$match = false;
}
- if ($check->isDeposit() && TransactionJournal::destinationAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) {
+ if ($check->isDeposit() && $thisDestinations !== $checkDestinations) {
$match = false;
}
diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php
index d8c1d33c05..4f9304c662 100644
--- a/app/Repositories/Tag/TagRepositoryInterface.php
+++ b/app/Repositories/Tag/TagRepositoryInterface.php
@@ -1,10 +1,16 @@
all()->count();
}
}
diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php
index e26a36360d..2d20707cd4 100644
--- a/app/Repositories/User/UserRepositoryInterface.php
+++ b/app/Repositories/User/UserRepositoryInterface.php
@@ -1,5 +1,4 @@
action->action_value;
$budgets = $repository->getActiveBudgets();
$budget = $budgets->filter(
@@ -55,10 +55,7 @@ class SetBudget implements ActionInterface
}
)->first();
if (!is_null($budget)) {
- Log::debug('Will set budget "' . $search . '" (#' . $budget->id . ') on journal #' . $journal->id . '.');
$journal->budgets()->sync([$budget->id]);
- } else {
- Log::debug('Could not find budget "' . $search . '". Failed.');
}
return true;
diff --git a/app/Rules/Actions/SetCategory.php b/app/Rules/Actions/SetCategory.php
index 86ccef422d..848dec089e 100644
--- a/app/Rules/Actions/SetCategory.php
+++ b/app/Rules/Actions/SetCategory.php
@@ -1,5 +1,4 @@
action->action_value;
$category = Category::firstOrCreateEncrypted(['name' => $name, 'user_id' => Auth::user()->id]);
- Log::debug('Will set category "' . $name . '" (#' . $category->id . ') on journal #' . $journal->id . '.');
$journal->categories()->sync([$category->id]);
return true;
diff --git a/app/Rules/Actions/SetDescription.php b/app/Rules/Actions/SetDescription.php
index 47dabc12d6..0cec07ed15 100644
--- a/app/Rules/Actions/SetDescription.php
+++ b/app/Rules/Actions/SetDescription.php
@@ -1,5 +1,4 @@
triggered();
if ($triggered) {
if ($this->actions->count() > 0) {
- Log::debug('Journal #' . $journal->id . ' triggered, actions executed.');
$this->actions();
}
return true;
}
- Log::debug('Journal #' . $journal->id . ' not triggered, did nothing.');
return false;
@@ -195,7 +193,6 @@ final class Processor
}
}
- Log::debug('Total: ' . $foundTriggers . ' found triggers. ' . $hitTriggers . ' triggers were hit.');
return ($hitTriggers == $foundTriggers && $foundTriggers > 0);
diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php
index 80be3c7366..7cbef9a970 100644
--- a/app/Rules/TransactionMatcher.php
+++ b/app/Rules/TransactionMatcher.php
@@ -1,5 +1,4 @@
0 ? ($page - 1) * $pagesize : 0;
- $set = $this->repository->getCollectionOfTypes($this->transactionTypes, $offset, $pagesize);
+ $paginator = $this->repository->getJournals($this->transactionTypes, $page, $pagesize);
+ $set = $paginator->getCollection();
+
// Filter transactions that match the given triggers.
$filtered = $set->filter(
diff --git a/app/Rules/Triggers/AbstractTrigger.php b/app/Rules/Triggers/AbstractTrigger.php
index 9fc2db2ba0..96422a4387 100644
--- a/app/Rules/Triggers/AbstractTrigger.php
+++ b/app/Rules/Triggers/AbstractTrigger.php
@@ -1,5 +1,4 @@
source_account_name ?? TransactionJournal::sourceAccount($journal)->name);
- $search = strtolower($this->triggerValue);
- $strpos = strpos($fromAccountName, $search);
+ $fromAccountName = '';
+
+ /** @var Account $account */
+ foreach (TransactionJournal::sourceAccountList($journal) as $account) {
+ $fromAccountName .= strtolower($account->name);
+ }
+
+ $search = strtolower($this->triggerValue);
+ $strpos = strpos($fromAccountName, $search);
if (!($strpos === false)) {
return true;
diff --git a/app/Rules/Triggers/FromAccountEnds.php b/app/Rules/Triggers/FromAccountEnds.php
index 96e83fadcf..319683e87f 100644
--- a/app/Rules/Triggers/FromAccountEnds.php
+++ b/app/Rules/Triggers/FromAccountEnds.php
@@ -1,5 +1,4 @@
source_account_name ?? TransactionJournal::sourceAccount($journal)->name);
+ $name = '';
+
+ /** @var Account $account */
+ foreach (TransactionJournal::sourceAccountList($journal) as $account) {
+ $name .= strtolower($account->name);
+ }
+
$nameLength = strlen($name);
$search = strtolower($this->triggerValue);
$searchLength = strlen($search);
diff --git a/app/Rules/Triggers/FromAccountIs.php b/app/Rules/Triggers/FromAccountIs.php
index 061044cf7d..dcd9587556 100644
--- a/app/Rules/Triggers/FromAccountIs.php
+++ b/app/Rules/Triggers/FromAccountIs.php
@@ -1,5 +1,4 @@
source_account_name ?? TransactionJournal::sourceAccount($journal)->name);
+ $name = '';
+
+ /** @var Account $account */
+ foreach (TransactionJournal::sourceAccountList($journal) as $account) {
+ $name .= strtolower($account->name);
+ }
+
$search = strtolower($this->triggerValue);
if ($name == $search) {
diff --git a/app/Rules/Triggers/FromAccountStarts.php b/app/Rules/Triggers/FromAccountStarts.php
index 62eaafac67..71a84e253d 100644
--- a/app/Rules/Triggers/FromAccountStarts.php
+++ b/app/Rules/Triggers/FromAccountStarts.php
@@ -1,5 +1,4 @@
source_account_name ?? TransactionJournal::sourceAccount($journal)->name);
+ $name = '';
+
+ /** @var Account $account */
+ foreach (TransactionJournal::sourceAccountList($journal) as $account) {
+ $name .= strtolower($account->name);
+ }
+
$search = strtolower($this->triggerValue);
$part = substr($name, 0, strlen($search));
diff --git a/app/Rules/Triggers/ToAccountContains.php b/app/Rules/Triggers/ToAccountContains.php
index f6d7c31c68..150f149b14 100644
--- a/app/Rules/Triggers/ToAccountContains.php
+++ b/app/Rules/Triggers/ToAccountContains.php
@@ -1,5 +1,4 @@
destination_account_name ?? TransactionJournal::destinationAccount($journal)->name);
- $search = strtolower($this->triggerValue);
- $strpos = strpos($toAccountName, $search);
+ $toAccountName = '';
+
+ /** @var Account $account */
+ foreach (TransactionJournal::destinationAccountList($journal) as $account) {
+ $toAccountName .= strtolower($account->name);
+ }
+
+ $search = strtolower($this->triggerValue);
+ $strpos = strpos($toAccountName, $search);
if (!($strpos === false)) {
return true;
diff --git a/app/Rules/Triggers/ToAccountEnds.php b/app/Rules/Triggers/ToAccountEnds.php
index f01d8d8ba5..9f4d6e0da7 100644
--- a/app/Rules/Triggers/ToAccountEnds.php
+++ b/app/Rules/Triggers/ToAccountEnds.php
@@ -1,5 +1,4 @@
destination_account_name ?? TransactionJournal::destinationAccount($journal)->name);
+ $toAccountName = '';
+
+ /** @var Account $account */
+ foreach (TransactionJournal::destinationAccountList($journal) as $account) {
+ $toAccountName .= strtolower($account->name);
+ }
+
$toAccountNameLength = strlen($toAccountName);
$search = strtolower($this->triggerValue);
$searchLength = strlen($search);
diff --git a/app/Rules/Triggers/ToAccountIs.php b/app/Rules/Triggers/ToAccountIs.php
index 6d1ea0181e..613770b97f 100644
--- a/app/Rules/Triggers/ToAccountIs.php
+++ b/app/Rules/Triggers/ToAccountIs.php
@@ -1,5 +1,4 @@
destination_account_name ?? TransactionJournal::destinationAccount($journal)->name);
- $search = strtolower($this->triggerValue);
+ $toAccountName = '';
+
+ /** @var Account $account */
+ foreach (TransactionJournal::destinationAccountList($journal) as $account) {
+ $toAccountName .= strtolower($account->name);
+ }
+
+ $search = strtolower($this->triggerValue);
if ($toAccountName == $search) {
return true;
diff --git a/app/Rules/Triggers/ToAccountStarts.php b/app/Rules/Triggers/ToAccountStarts.php
index 8f00f80d0c..3dab74bd09 100644
--- a/app/Rules/Triggers/ToAccountStarts.php
+++ b/app/Rules/Triggers/ToAccountStarts.php
@@ -1,5 +1,4 @@
destination_account_name ?? TransactionJournal::destinationAccount($journal)->name);
- $search = strtolower($this->triggerValue);
+ $toAccountName = '';
- $part = substr($toAccountName, 0, strlen($search));
+ /** @var Account $account */
+ foreach (TransactionJournal::destinationAccountList($journal) as $account) {
+ $toAccountName .= strtolower($account->name);
+ }
+
+ $search = strtolower($this->triggerValue);
+ $part = substr($toAccountName, 0, strlen($search));
if ($part == $search) {
return true;
diff --git a/app/Rules/Triggers/TransactionType.php b/app/Rules/Triggers/TransactionType.php
index a57d1aa1cc..0abff3d7d5 100644
--- a/app/Rules/Triggers/TransactionType.php
+++ b/app/Rules/Triggers/TransactionType.php
@@ -1,5 +1,4 @@
expanded()
->where('transaction_journals.user_id', Auth::user()->id)
- ->get(TransactionJournal::QUERYFIELDS);
+ ->get(TransactionJournal::queryFields());
if ($object->count() > 0) {
return $object;
diff --git a/app/Support/Binder/UnfinishedJournal.php b/app/Support/Binder/UnfinishedJournal.php
new file mode 100644
index 0000000000..52f3d3aa26
--- /dev/null
+++ b/app/Support/Binder/UnfinishedJournal.php
@@ -0,0 +1,48 @@
+leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->where('completed', 0)
+ ->where('user_id', Auth::user()->id)->first(['transaction_journals.*']);
+ if ($object) {
+ return $object;
+ }
+ }
+
+ throw new NotFoundHttpException;
+
+ }
+}
diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php
index 19f6c0f6cc..c571e659ba 100644
--- a/app/Support/CacheProperties.php
+++ b/app/Support/CacheProperties.php
@@ -1,4 +1,12 @@
user->bills()->where('active', 1)->where('automatch', 1)->get();
/** @var \FireflyIII\Models\Bill $bill */
diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php
index 4b266746c9..c93c603ef2 100644
--- a/app/Support/ExpandedForm.php
+++ b/app/Support/ExpandedForm.php
@@ -1,9 +1,18 @@
label($name, $options);
- $options = $this->expandOptionArray($name, $label, $options);
- $classes = $this->getHolderClasses($name);
- $value = $this->fillFieldValue($name, $value);
- $options['step'] = 'any';
- $options['min'] = '0.01';
- $defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency();
- $currencies = Amt::getAllCurrencies();
- unset($options['currency']);
- unset($options['placeholder']);
- $html = view('form.amount-small', compact('defaultCurrency', 'currencies', 'classes', 'name', 'value', 'options'))->render();
-
- return $html;
-
- }
-
/**
* @param $name
* @param null $value
@@ -70,6 +54,31 @@ class ExpandedForm
}
+ /**
+ * @param $name
+ * @param null $value
+ * @param array $options
+ *
+ * @return string
+ */
+ public function amountSmall(string $name, $value = null, array $options = []): string
+ {
+ $label = $this->label($name, $options);
+ $options = $this->expandOptionArray($name, $label, $options);
+ $classes = $this->getHolderClasses($name);
+ $value = $this->fillFieldValue($name, $value);
+ $options['step'] = 'any';
+ $options['min'] = '0.01';
+ $defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency();
+ $currencies = Amt::getAllCurrencies();
+ unset($options['currency']);
+ unset($options['placeholder']);
+ $html = view('form.amount-small', compact('defaultCurrency', 'currencies', 'classes', 'name', 'value', 'options'))->render();
+
+ return $html;
+
+ }
+
/**
* @param $name
* @param null $value
@@ -196,17 +205,39 @@ class ExpandedForm
* Takes any collection and tries to make a sensible select list compatible array of it.
*
* @param \Illuminate\Support\Collection $set
- * @param bool $addEmpty
*
* @return array
*/
- public function makeSelectList(Collection $set, bool $addEmpty = false): array
+ public function makeSelectList(Collection $set): array
{
$selectList = [];
- if ($addEmpty) {
- $selectList[0] = '(none)';
+ $fields = ['title', 'name', 'description'];
+ /** @var Eloquent $entry */
+ foreach ($set as $entry) {
+ $entryId = intval($entry->id);
+ $title = null;
+
+ foreach ($fields as $field) {
+ if (isset($entry->$field) && is_null($title)) {
+ $title = $entry->$field;
+ }
+ }
+ $selectList[$entryId] = $title;
}
- $fields = ['title', 'name', 'description'];
+
+ return $selectList;
+ }
+
+ /**
+ * @param \Illuminate\Support\Collection $set
+ *
+ * @return array
+ */
+ public function makeSelectListWithEmpty(Collection $set): array
+ {
+ $selectList = [];
+ $selectList[0] = '(none)';
+ $fields = ['title', 'name', 'description'];
/** @var Eloquent $entry */
foreach ($set as $entry) {
$entryId = intval($entry->id);
@@ -286,7 +317,7 @@ class ExpandedForm
return $html;
}
-
+
/**
* @param $name
@@ -321,7 +352,6 @@ class ExpandedForm
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
- $value = $this->fillFieldValue($name, $value);
$html = view('form.static', compact('classes', 'name', 'label', 'value', 'options'))->render();
return $html;
@@ -422,6 +452,9 @@ class ExpandedForm
} catch (RuntimeException $e) {
// don't care about session errors.
}
+ if ($value instanceof Carbon) {
+ $value = $value->format('Y-m-d');
+ }
return $value;
diff --git a/app/Support/Facades/Amount.php b/app/Support/Facades/Amount.php
index ea067dcb32..84eadc9dbe 100644
--- a/app/Support/Facades/Amount.php
+++ b/app/Support/Facades/Amount.php
@@ -1,4 +1,12 @@
'defaultAsset'],
- ['accountRole' => 'savingAsset',],
- ['accountRole' => 'sharedAsset',],
- ['accountRole' => 'ccAsset', 'ccMonthlyPaymentDate' => '2015-05-27', 'ccType' => 'monthlyFull',],
- ['accountRole' => 'savingAsset',],
- ['accountRole' => 'savingAsset',],
- ];
-
- foreach ($assets as $index => $name) {
- // create account:
- $account = Account::create(
- [
- 'user_id' => $user->id,
- 'account_type_id' => 3,
- 'name' => $name,
- 'active' => 1,
- 'encrypted' => 1,
- 'iban' => $ibans[$index],
- ]
- );
- foreach ($assetMeta[$index] as $name => $value) {
- AccountMeta::create(['account_id' => $account->id, 'name' => $name, 'data' => $value,]);
- }
- }
-
- return true;
+ $this->data = $data;
+ $start = new Carbon;
+ $start->startOfYear();
+ $start->subYears(2);
+ $end = new Carbon;
+ $this->start = $start;
+ $this->end = $end;
+ $this->time = $end->format('Y-m-d H:i:s');
}
/**
- * @param User $user
- * @param Carbon $start
- *
- * @return TransactionJournal
+ * @param array $data
*/
- public static function createAttachments(User $user, Carbon $start): TransactionJournal
+ public static function run(array $data)
{
-
- $args = [
- 'user' => $user,
- 'description' => 'Some journal for attachment',
- 'date' => $start,
- 'from' => 'Job',
- 'to' => 'TestData Checking Account',
- 'amount' => '100',
- 'transaction_type' => 2,
- ];
- $journal = self::createJournal($args);
-
- // and now attachments
- $encrypted = Crypt::encrypt('I are secret');
- $one = Attachment::create(
- [
- 'attachable_id' => $journal->id,
- 'attachable_type' => 'FireflyIII\Models\TransactionJournal',
- 'user_id' => $user->id,
- 'md5' => md5('Hallo'),
- 'filename' => 'empty-file.txt',
- 'title' => 'Empty file',
- 'description' => 'This file is empty',
- 'notes' => 'What notes',
- 'mime' => 'text/plain',
- 'size' => strlen($encrypted),
- 'uploaded' => 1,
- ]
- );
-
-
- // and now attachment.
- $two = Attachment::create(
- [
- 'attachable_id' => $journal->id,
- 'attachable_type' => 'FireflyIII\Models\TransactionJournal',
- 'user_id' => $user->id,
- 'md5' => md5('Ook hallo'),
- 'filename' => 'empty-file-2.txt',
- 'title' => 'Empty file 2',
- 'description' => 'This file is empty too',
- 'notes' => 'What notes do',
- 'mime' => 'text/plain',
- 'size' => strlen($encrypted),
- 'uploaded' => 1,
- ]
- );
- // echo crypted data to the file.
- $disk = Storage::disk('upload');
- $disk->put('at-' . $one->id . '.data', $encrypted);
- $disk->put('at-' . $two->id . '.data', $encrypted);
-
- return $journal;
+ $seeder = new TestData($data);
+ $seeder->go();
}
/**
- * @param User $user
*
- * @return bool
*/
- public static function createBills(User $user): bool
+ private function createAccounts()
{
- Bill::create(
- [
- 'name' => 'Rent',
- 'match' => 'rent,land,lord',
- 'amount_min' => 795,
- 'amount_max' => 805,
- 'user_id' => $user->id,
- 'date' => '2015-01-01',
- 'active' => 1,
- 'automatch' => 1,
- 'repeat_freq' => 'monthly',
- 'skip' => 0,
- ]
- );
- Bill::create(
- [
- 'name' => 'Health insurance',
- 'match' => 'zilveren,kruis,health',
- 'amount_min' => 120,
- 'amount_max' => 140,
- 'user_id' => $user->id,
- 'date' => '2015-01-01',
- 'active' => 1,
- 'automatch' => 1,
- 'repeat_freq' => 'monthly',
- 'skip' => 0,
- ]
- );
-
- return true;
-
- }
-
- /**
- * @param User $user
- * @param Carbon $current
- * @param string $name
- * @param string $amount
- *
- * @return BudgetLimit
- */
- public static function createBudgetLimit(User $user, Carbon $current, string $name, string $amount): BudgetLimit
- {
- $start = clone $current;
- $end = clone $current;
- $budget = self::findBudget($user, $name);
- $start->startOfMonth();
- $end->endOfMonth();
-
- $limit = BudgetLimit::create(
- [
- 'budget_id' => $budget->id,
- 'startdate' => $start->format('Y-m-d'),
- 'amount' => $amount,
- 'repeats' => 0,
- 'repeat_freq' => 'monthly',
- ]
- );
-
- return $limit;
- }
-
- /**
- * @param User $user
- *
- * @return bool
- */
- public static function createBudgets(User $user): bool
- {
- Budget::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]);
- Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $user->id]);
- Budget::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]);
-
- // some empty budgets.
- foreach (['A', 'B', 'C', 'D', 'E'] as $letter) {
- Budget::firstOrCreateEncrypted(['name' => 'Empty budget ' . $letter, 'user_id' => $user->id]);
- }
-
- return true;
- }
-
- /**
- * @param User $user
- * @param Carbon $date
- *
- * @return TransactionJournal
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public static function createCar(User $user, Carbon $date): TransactionJournal
- {
- // twice:
- $amount = strval(rand(4000, 5000) / 100);
- $args = [
- 'user' => $user,
- 'description' => 'Bought gas',
- 'date' => new Carbon($date->format('Y-m') . '-10'),// paid on 10th
- 'from' => 'TestData Checking Account',
- 'to' => 'Shell',
- 'amount' => $amount,
- 'category' => 'Car',
- 'budget' => 'Car',
- ];
- self::createJournal($args);
-
- // again!
- $args['date'] = new Carbon($date->format('Y-m') . '-20'); // paid on 20th
- $args['amount'] = strval(rand(4000, 5000) / 100);
- $args['description'] = 'Gas for car';
- $journal = self::createJournal($args);
-
- return $journal;
- }
-
- /**
- * @param User $user
- *
- * @return bool
- */
- public static function createCategories(User $user): bool
- {
- Category::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]);
- Category::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]);
-
- return true;
- }
-
- /**
- * @param User $user
- * @param Carbon $date
- *
- * @return bool
- *
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public static function createDrinksAndOthers(User $user, Carbon $date): bool
- {
- $start = clone $date;
- $end = clone $date;
- $today = new Carbon;
- $start->startOfMonth();
- $end->endOfMonth();
- $current = clone $start;
- while ($current < $end && $current < $today) {
-
- // weekly drink:
- $thisDate = clone $current;
- $thisDate->addDay();
- $amount = strval(rand(1500, 3600) / 100);
- $args = [
- 'user' => $user,
- 'description' => 'Going out for drinks',
- 'date' => $thisDate,
- 'from' => 'TestData Checking Account',
- 'to' => 'Cafe Central',
- 'amount' => $amount,
- 'category' => 'Drinks',
- 'budget' => 'Going out',
- ];
- self::createJournal($args);
-
- $current->addWeek();
- }
-
- return true;
- }
-
- /**
- * @param User $user
- *
- * @return bool
- */
- public static function createExpenseAccounts(User $user): bool
- {
- $expenses = ['Adobe', 'Google', 'Vitens', 'Albert Heijn', 'PLUS', 'Apple', 'Bakker', 'Belastingdienst', 'bol.com', 'Cafe Central', 'conrad.nl',
- 'Coolblue', 'Shell',
- 'DUO', 'Etos', 'FEBO', 'Greenchoice', 'Halfords', 'XS4All', 'iCentre', 'Jumper', 'Land lord'];
- foreach ($expenses as $name) {
- // create account:
- Account::create(
- [
- 'user_id' => $user->id,
- 'account_type_id' => 4,
- 'name' => $name,
- 'active' => 1,
- 'encrypted' => 1,
- ]
- );
- }
-
- return true;
-
- }
-
- /**
- * @param User $user
- * @param Carbon $date
- *
- * @return bool
- *
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public static function createGroceries(User $user, Carbon $date): bool
- {
- $start = clone $date;
- $end = clone $date;
- $today = new Carbon;
- $start->startOfMonth();
- $end->endOfMonth();
-
- $stores = ['Albert Heijn', 'PLUS', 'Bakker'];
- $descriptions = ['Groceries', 'Bought some groceries', 'Got groceries'];
-
- $current = clone $start;
- while ($current < $end && $current < $today) {
- // daily groceries:
- $amount = (string)round((rand(1500, 2500) / 100), 2);
-
- $args = [
- 'user' => $user,
- 'description' => $descriptions[rand(0, count($descriptions) - 1)],
- 'date' => $current,
- 'from' => 'TestData Checking Account',
- 'to' => $stores[rand(0, count($stores) - 1)],
- 'amount' => $amount,
- 'category' => 'Daily groceries',
- 'budget' => 'Groceries',
- ];
- self::createJournal($args);
- $current->addDay();
- }
-
- return true;
- }
-
- /**
- * @param User $user
- * @param string $description
- * @param Carbon $date
- * @param string $amount
- *
- * @return TransactionJournal
- */
- public static function createIncome(User $user, string $description, Carbon $date, string $amount): TransactionJournal
- {
- $date = new Carbon($date->format('Y-m') . '-23'); // paid on 23rd.
- $today = new Carbon;
- if ($date >= $today) {
- return new TransactionJournal;
- }
-
- // create journal:
- $args = [
- 'user' => $user,
- 'description' => $description,
- 'date' => $date,
- 'from' => 'Job',
- 'to' => 'TestData Checking Account',
- 'amount' => $amount,
- 'category' => 'Salary',
- 'transaction_type' => 2,
- ];
- $journal = self::createJournal($args);
-
- return $journal;
-
- }
-
- /**
- * @param array $opt
- *
- * @return TransactionJournal
- */
- public static function createJournal(array $opt): TransactionJournal
- {
- $type = $opt['transaction_type'] ?? 1;
-
- $journal = TransactionJournal::create(
- [
- 'user_id' => $opt['user']->id,
- 'transaction_type_id' => $type,
- 'transaction_currency_id' => 1,
- 'description' => $opt['description'],
- 'completed' => 1,
- 'date' => $opt['date'],
- ]
- );
- self::createTransactions($journal, self::findAccount($opt['user'], $opt['from']), self::findAccount($opt['user'], $opt['to']), $opt['amount']);
- if (isset($opt['category'])) {
- $journal->categories()->save(self::findCategory($opt['user'], $opt['category']));
- }
- if (isset($opt['budget'])) {
- $journal->budgets()->save(self::findBudget($opt['user'], $opt['budget']));
- }
-
- return $journal;
- }
-
- /**
- * @param User $user
- *
- * @return bool
- *
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public static function createPiggybanks(User $user): bool
- {
- $account = self::findAccount($user, 'TestData Savings');
-
- $camera = PiggyBank::create(
- [
- 'account_id' => $account->id,
- 'name' => 'New camera',
- 'targetamount' => 1000,
- 'startdate' => '2015-04-01',
- 'reminder_skip' => 0,
- 'remind_me' => 0,
- 'order' => 1,
- ]
- );
- $repetition = $camera->piggyBankRepetitions()->first();
- $repetition->currentamount = 735;
- $repetition->save();
-
- // events:
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $camera->id,
- 'date' => '2015-05-01',
- 'amount' => '245',
- ]
- );
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $camera->id,
- 'date' => '2015-06-01',
- 'amount' => '245',
- ]
- );
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $camera->id,
- 'date' => '2015-07-01',
- 'amount' => '245',
- ]
- );
-
-
- $phone = PiggyBank::create(
- [
- 'account_id' => $account->id,
- 'name' => 'New phone',
- 'targetamount' => 600,
- 'startdate' => '2015-04-01',
- 'reminder_skip' => 0,
- 'remind_me' => 0,
- 'order' => 2,
- ]
- );
- $repetition = $phone->piggyBankRepetitions()->first();
- $repetition->currentamount = 333;
- $repetition->save();
-
- // events:
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $phone->id,
- 'date' => '2015-05-01',
- 'amount' => '111',
- ]
- );
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $phone->id,
- 'date' => '2015-06-01',
- 'amount' => '111',
- ]
- );
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $phone->id,
- 'date' => '2015-07-01',
- 'amount' => '111',
- ]
- );
-
- $couch = PiggyBank::create(
- [
- 'account_id' => $account->id,
- 'name' => 'New couch',
- 'targetamount' => 500,
- 'startdate' => '2015-04-01',
- 'reminder_skip' => 0,
- 'remind_me' => 0,
- 'order' => 3,
- ]
- );
- $repetition = $couch->piggyBankRepetitions()->first();
- $repetition->currentamount = 120;
- $repetition->save();
-
- // events:
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $couch->id,
- 'date' => '2015-05-01',
- 'amount' => '40',
- ]
- );
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $couch->id,
- 'date' => '2015-06-01',
- 'amount' => '40',
- ]
- );
- PiggyBankEvent::create(
- [
- 'piggy_bank_id' => $couch->id,
- 'date' => '2015-07-01',
- 'amount' => '40',
- ]
- );
-
- // empty one.
- PiggyBank::create(
- [
- 'account_id' => $account->id,
- 'name' => 'New head set',
- 'targetamount' => 500,
- 'startdate' => '2015-04-01',
- 'reminder_skip' => 0,
- 'remind_me' => 0,
- 'order' => 4,
- ]
- );
-
- return true;
- }
-
- /**
- * @param User $user
- * @param string $description
- * @param Carbon $date
- * @param string $amount
- *
- * @return TransactionJournal
- */
- public static function createPower(User $user, string $description, Carbon $date, string $amount): TransactionJournal
- {
- $args = [
- 'user' => $user,
- 'description' => $description,
- 'date' => new Carbon($date->format('Y-m') . '-06'),// paid on 10th
- 'from' => 'TestData Checking Account',
- 'to' => 'Greenchoice',
- 'amount' => $amount,
- 'category' => 'House',
- 'budget' => 'Bills',
- ];
- $journal = self::createJournal($args);
-
- return $journal;
-
- }
-
- /**
- * @param User $user
- * @param string $description
- * @param Carbon $date
- * @param string $amount
- *
- * @return TransactionJournal
- */
- public static function createRent(User $user, string $description, Carbon $date, string $amount): TransactionJournal
- {
- $args = [
- 'user' => $user,
- 'description' => $description,
- 'date' => $date,
- 'from' => 'TestData Checking Account',
- 'to' => 'Land lord',
- 'amount' => $amount,
- 'category' => 'Rent',
- 'budget' => 'Bills',
- ];
- $journal = self::createJournal($args);
-
- return $journal;
-
- }
-
- /**
- * @param User $user
- *
- * @return bool
- */
- public static function createRevenueAccounts(User $user): bool
- {
- $revenues = ['Job', 'Belastingdienst', 'Bank', 'KPN', 'Google'];
- foreach ($revenues as $name) {
- // create account:
- Account::create(
- [
- 'user_id' => $user->id,
- 'account_type_id' => 5,
- 'name' => $name,
- 'active' => 1,
- 'encrypted' => 1,
- ]
- );
- }
-
- return true;
- }
-
- /**
- * @param User $user
- *
- * @return RuleGroup
- */
- public static function createRules(User $user): RuleGroup
- {
- $group = RuleGroup::create(
- [
- 'user_id' => $user->id,
- 'order' => 1,
- 'title' => trans('firefly.default_rule_group_name'),
- 'description' => trans('firefly.default_rule_group_description'),
- 'active' => 1,
- ]
- );
- $rule = Rule::create(
- [
- 'user_id' => $user->id,
- 'rule_group_id' => $group->id,
- 'order' => 1,
- 'active' => 1,
- 'stop_processing' => 0,
- 'title' => trans('firefly.default_rule_name'),
- 'description' => trans('firefly.default_rule_description'),
- ]
- );
-
- // three triggers:
- RuleTrigger::create(
- [
- 'rule_id' => $rule->id,
- 'order' => 1,
- 'active' => 1,
- 'stop_processing' => 0,
- 'trigger_type' => 'user_action',
- 'trigger_value' => 'store-journal',
- ]
- );
- RuleTrigger::create(
- [
- 'rule_id' => $rule->id,
- 'order' => 2,
- 'active' => 1,
- 'stop_processing' => 0,
- 'trigger_type' => 'description_is',
- 'trigger_value' => trans('firefly.default_rule_trigger_description'),
- ]
- );
- RuleTrigger::create(
- [
- 'rule_id' => $rule->id,
- 'order' => 3,
- 'active' => 1,
- 'stop_processing' => 0,
- 'trigger_type' => 'from_account_is',
- 'trigger_value' => trans('firefly.default_rule_trigger_from_account'),
- ]
- );
-
- // two actions:
- RuleAction::create(
- [
- 'rule_id' => $rule->id,
- 'order' => 1,
- 'active' => 1,
- 'action_type' => 'prepend_description',
- 'action_value' => trans('firefly.default_rule_action_prepend'),
- ]
- );
- RuleAction::create(
- [
- 'rule_id' => $rule->id,
- 'order' => 1,
- 'active' => 1,
- 'action_type' => 'set_category',
- 'action_value' => trans('firefly.default_rule_action_set_category'),
- ]
- );
-
- return $group;
- }
-
- /**
- * @param User $user
- * @param Carbon $date
- *
- * @return TransactionJournal
- */
- public static function createSavings(User $user, Carbon $date): TransactionJournal
- {
- $args = [
- 'user' => $user,
- 'description' => 'Save money',
- 'date' => new Carbon($date->format('Y-m') . '-24'),// paid on 24th.
- 'from' => 'TestData Checking Account',
- 'to' => 'TestData Savings',
- 'amount' => '150',
- 'category' => 'Money management',
- 'transaction_type' => 3,
- ];
- $journal = self::createJournal($args);
-
- return $journal;
-
- }
-
- /**
- * @param User $user
- * @param string $description
- * @param Carbon $date
- * @param string $amount
- *
- * @return TransactionJournal
- */
- public static function createTV(User $user, string $description, Carbon $date, string $amount): TransactionJournal
- {
- $args = [
- 'user' => $user,
- 'description' => $description,
- 'date' => new Carbon($date->format('Y-m') . '-15'),
- 'from' => 'TestData Checking Account',
- 'to' => 'XS4All',
- 'amount' => $amount,
- 'category' => 'House',
- 'budget' => 'Bills',
- ];
- $journal = self::createJournal($args);
-
- return $journal;
-
- }
-
- /**
- * @param User $user
- * @param Carbon $date
- *
- * @return Tag
- */
- public static function createTags(User $user, Carbon $date): Tag
- {
- $title = 'SomeTag' . $date->month . '.' . $date->year . '.nothing';
-
- $tag = Tag::create(
- [
- 'user_id' => $user->id,
- 'tag' => $title,
- 'tagMode' => 'nothing',
- 'date' => $date->format('Y-m-d'),
-
-
- ]
- );
-
- return $tag;
- }
-
- /**
- * @param TransactionJournal $journal
- * @param Account $from
- * @param Account $to
- * @param string $amount
- *
- * @return bool
- */
- public static function createTransactions(TransactionJournal $journal, Account $from, Account $to, string $amount): bool
- {
- Transaction::create(
- [
- 'account_id' => $from->id,
- 'transaction_journal_id' => $journal->id,
- 'amount' => bcmul($amount, '-1'),
-
- ]
- );
- Transaction::create(
- [
- 'account_id' => $to->id,
- 'transaction_journal_id' => $journal->id,
- 'amount' => $amount,
-
- ]
- );
-
- return true;
- }
-
- /**
- * @return User
- */
- public static function createUsers(): User
- {
- $user = User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]);
- User::create(['email' => 'thegrumpydictator+empty@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]);
- User::create(['email' => 'thegrumpydictator+deleteme@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]);
-
-
- $admin = Role::where('name', 'owner')->first();
- $user->attachRole($admin);
-
- return $user;
- }
-
- /**
- * @param User $user
- * @param string $description
- * @param Carbon $date
- * @param string $amount
- *
- * @return TransactionJournal
- */
- public static function createWater(User $user, string $description, Carbon $date, string $amount): TransactionJournal
- {
- $args = [
- 'user' => $user,
- 'description' => $description,
- 'date' => new Carbon($date->format('Y-m') . '-10'), // paid on 10th
- 'from' => 'TestData Checking Account',
- 'to' => 'Vitens',
- 'amount' => $amount,
- 'category' => 'House',
- 'budget' => 'Bills',
- ];
- $journal = self::createJournal($args);
-
- return $journal;
-
- }
-
- /**
- * @param User $user
- * @param $name
- *
- * @return Account
- */
- public static function findAccount(User $user, string $name): Account
- {
- /** @var Account $account */
- foreach ($user->accounts()->get() as $account) {
- if ($account->name == $name) {
- return $account;
- }
- }
-
- return new Account;
- }
-
- /**
- * @param User $user
- * @param $name
- *
- * @return Budget
- */
- public static function findBudget(User $user, string $name): Budget
- {
- /** @var Budget $budget */
- foreach (Budget::get() as $budget) {
- if ($budget->name == $name && $user->id == $budget->user_id) {
- return $budget;
- }
- }
-
- return Budget::firstOrCreateEncrypted(['name' => $name, 'user_id' => $user->id]);
- }
-
- /**
- * @param User $user
- * @param $name
- *
- * @return Category
- */
- public static function findCategory(User $user, string $name): Category
- {
- /** @var Category $category */
- foreach (Category::get() as $category) {
- if ($category->name == $name && $user->id == $category->user_id) {
- return $category;
- }
- }
-
- return Category::firstOrCreateEncrypted(['name' => $name, 'user_id' => $user->id]);
- }
-
- /**
- * @param User $user
- * @param Carbon $date
- *
- * @return TransactionJournal
- */
- public static function openingBalanceSavings(User $user, Carbon $date): TransactionJournal
- {
- // opposing account for opening balance:
- $opposing = Account::create(
- [
- 'user_id' => $user->id,
- 'account_type_id' => 6,
- 'name' => 'Opposing for savings',
+ $insert = [];
+ foreach ($this->data['accounts'] as $account) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $account['user_id'],
+ 'account_type_id' => $account['account_type_id'],
+ 'name' => Crypt::encrypt($account['name']),
'active' => 1,
'encrypted' => 1,
- ]
- );
+ 'virtual_balance' => 0,
+ 'iban' => isset($account['iban']) ? Crypt::encrypt($account['iban']) : null,
+ ];
+ }
+ DB::table('accounts')->insert($insert);
+ $insert = [];
+ foreach ($this->data['account-meta'] as $meta) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $meta['account_id'],
+ 'name' => $meta['name'],
+ 'data' => $meta['data'],
+ ];
+ }
+ DB::table('account_meta')->insert($insert);
+ }
- // savings
- $savings = TestData::findAccount($user, 'TestData Savings');
+ /**
+ *
+ */
+ private function createAttachments()
+ {
+ $insert = [];
+ $disk = Storage::disk('upload');
+ foreach ($this->data['attachments'] as $attachment) {
+ $data = Crypt::encrypt($attachment['content']);
+ $attachmentId = DB::table('attachments')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'attachable_id' => $attachment['attachable_id'],
+ 'attachable_type' => $attachment['attachable_type'],
+ 'user_id' => $attachment['user_id'],
+ 'md5' => md5($attachment['content']),
+ 'filename' => Crypt::encrypt($attachment['filename']),
+ 'title' => Crypt::encrypt($attachment['title']),
+ 'description' => Crypt::encrypt($attachment['description']),
+ 'notes' => Crypt::encrypt($attachment['notes']),
+ 'mime' => Crypt::encrypt($attachment['mime']),
+ 'size' => strlen($attachment['content']),
+ 'uploaded' => 1,
+ ]
+ );
- $journal = TransactionJournal::create(
- [
- 'user_id' => $user->id,
- 'transaction_type_id' => 4,
- 'transaction_currency_id' => 1,
- 'description' => 'Opening balance for savings account',
- 'completed' => 1,
- 'date' => $date->format('Y-m-d'),
- ]
- );
- self::createTransactions($journal, $opposing, $savings, '10000');
+ $disk->put('at-' . $attachmentId . '.data', $data);
+ }
+ }
- return $journal;
+ /**
+ *
+ */
+ private function createBills()
+ {
+ $insert = [];
+ foreach ($this->data['bills'] as $bill) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $bill['user_id'],
+ 'name' => Crypt::encrypt($bill['name']),
+ 'match' => Crypt::encrypt($bill['match']),
+ 'amount_min' => $bill['amount_min'],
+ 'amount_max' => $bill['amount_max'],
+ 'date' => $bill['date'],
+ 'active' => $bill['active'],
+ 'automatch' => $bill['automatch'],
+ 'repeat_freq' => $bill['repeat_freq'],
+ 'skip' => $bill['skip'],
+ 'name_encrypted' => 1,
+ 'match_encrypted' => 1,
+ ];
+ }
+ DB::table('bills')->insert($insert);
+ }
+
+ /**
+ *
+ */
+ private function createBudgets()
+ {
+ $insert = [];
+ foreach ($this->data['budgets'] as $budget) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $budget['user_id'],
+ 'name' => Crypt::encrypt($budget['name']),
+ 'encrypted' => 1,
+ ];
+ }
+ DB::table('budgets')->insert($insert);
+
+ foreach ($this->data['budget-limits'] as $limit) {
+ $amount = rand($limit['amount_min'], $limit['amount_max']);
+ $limitId = DB::table('budget_limits')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'budget_id' => $limit['budget_id'],
+ 'startdate' => $limit['startdate'],
+ 'amount' => $amount,
+ 'repeats' => 0,
+ 'repeat_freq' => $limit['repeat_freq'],
+ ]
+ );
+
+ DB::table('limit_repetitions')->insert(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'budget_limit_id' => $limitId,
+ 'startdate' => $limit['startdate'],
+ 'enddate' => Navigation::endOfPeriod(new Carbon($limit['startdate']), $limit['repeat_freq'])->format('Y-m-d'),
+ 'amount' => $amount,
+ ]
+ );
+ }
+ $current = clone $this->start;
+ while ($current <= $this->end) {
+ foreach ($this->data['monthly-limits'] as $limit) {
+ $amount = rand($limit['amount_min'], $limit['amount_max']);
+ $limitId = DB::table('budget_limits')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'budget_id' => $limit['budget_id'],
+ 'startdate' => $current->format('Y-m-d'),
+ 'amount' => $amount,
+ 'repeats' => 0,
+ 'repeat_freq' => 'monthly',
+ ]
+ );
+
+ DB::table('limit_repetitions')->insert(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'budget_limit_id' => $limitId,
+ 'startdate' => $current->format('Y-m-d'),
+ 'enddate' => Navigation::endOfPeriod($current, 'monthly')->format('Y-m-d'),
+ 'amount' => $amount,
+ ]
+ );
+ }
+
+ $current->addMonth();
+ }
}
+ /**
+ *
+ */
+ private function createCategories()
+ {
+ $insert = [];
+ foreach ($this->data['categories'] as $category) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $category['user_id'],
+ 'name' => Crypt::encrypt($category['name']),
+ 'encrypted' => 1,
+ ];
+ }
+ DB::table('categories')->insert($insert);
+ }
+
+ /**
+ *
+ */
+ private function createJournals()
+ {
+ $current = clone $this->start;
+ $transactions = [];
+ while ($current <= $this->end) {
+ $date = $current->format('Y-m-');
+ $month = $current->format('F');
+
+ // run all monthly withdrawals:
+ foreach ($this->data['monthly-withdrawals'] as $withdrawal) {
+ $description = str_replace(':month', $month, $withdrawal['description']);
+ $journalId = DB::table('transaction_journals')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $withdrawal['user_id'],
+ 'transaction_type_id' => 1,
+ 'bill_id' => $withdrawal['bill_id'] ?? null,
+ 'transaction_currency_id' => 1,
+ 'description' => Crypt::encrypt($description),
+ 'completed' => 1,
+ 'date' => $date . $withdrawal['day-of-month'],
+ 'interest_date' => $withdrawal['interest_date'] ?? null,
+ 'book_date' => $withdrawal['book_date'] ?? null,
+ 'process_date' => $withdrawal['process_date'] ?? null,
+ 'encrypted' => 1,
+ 'order' => 0,
+ 'tag_count' => 0,
+ ]
+ );
+ $amount = (rand($withdrawal['min_amount'] * 100, $withdrawal['max_amount'] * 100)) / 100;
+ $transactions[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'transaction_journal_id' => $journalId,
+ 'account_id' => $withdrawal['source_id'],
+ 'amount' => $amount * -1,
+ ];
+ $transactions[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'transaction_journal_id' => $journalId,
+ 'account_id' => $withdrawal['destination_id'],
+ 'amount' => $amount,
+ ];
+
+ // link to budget if set.
+ if (isset($withdrawal['budget_id'])) {
+ DB::table('budget_transaction_journal')->insert(
+ [
+ 'budget_id' => $withdrawal['budget_id'],
+ 'transaction_journal_id' => $journalId,
+
+ ]
+ );
+ }
+ // link to category if set.
+ if (isset($withdrawal['category_id'])) {
+ DB::table('category_transaction_journal')->insert(
+ [
+ 'category_id' => $withdrawal['category_id'],
+ 'transaction_journal_id' => $journalId,
+
+ ]
+ );
+ }
+ }
+
+ // run all monthly deposits:
+ foreach ($this->data['monthly-deposits'] as $deposit) {
+ $description = str_replace(':month', $month, $deposit['description']);
+ $journalId = DB::table('transaction_journals')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $deposit['user_id'],
+ 'transaction_type_id' => 2,
+ 'bill_id' => $deposit['bill_id'] ?? null,
+ 'transaction_currency_id' => 1,
+ 'description' => Crypt::encrypt($description),
+ 'completed' => 1,
+ 'date' => $date . $deposit['day-of-month'],
+ 'interest_date' => $deposit['interest_date'] ?? null,
+ 'book_date' => $deposit['book_date'] ?? null,
+ 'process_date' => $deposit['process_date'] ?? null,
+ 'encrypted' => 1,
+ 'order' => 0,
+ 'tag_count' => 0,
+ ]
+ );
+ $amount = (rand($deposit['min_amount'] * 100, $deposit['max_amount'] * 100)) / 100;
+ $transactions[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'transaction_journal_id' => $journalId,
+ 'account_id' => $deposit['source_id'],
+ 'amount' => $amount * -1,
+ ];
+ $transactions[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'transaction_journal_id' => $journalId,
+ 'account_id' => $deposit['destination_id'],
+ 'amount' => $amount,
+ ];
+
+ // link to category if set.
+ if (isset($deposit['category_id'])) {
+ DB::table('category_transaction_journal')->insert(
+ [
+ 'category_id' => $deposit['category_id'],
+ 'transaction_journal_id' => $journalId,
+
+ ]
+ );
+ }
+ }
+ // run all monthly transfers:
+ foreach ($this->data['monthly-transfers'] as $transfer) {
+ $description = str_replace(':month', $month, $transfer['description']);
+ $journalId = DB::table('transaction_journals')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $transfer['user_id'],
+ 'transaction_type_id' => 3,
+ 'bill_id' => $transfer['bill_id'] ?? null,
+ 'transaction_currency_id' => 1,
+ 'description' => Crypt::encrypt($description),
+ 'completed' => 1,
+ 'date' => $date . $transfer['day-of-month'],
+ 'interest_date' => $transfer['interest_date'] ?? null,
+ 'book_date' => $transfer['book_date'] ?? null,
+ 'process_date' => $transfer['process_date'] ?? null,
+ 'encrypted' => 1,
+ 'order' => 0,
+ 'tag_count' => 0,
+ ]
+ );
+ $amount = (rand($transfer['min_amount'] * 100, $transfer['max_amount'] * 100)) / 100;
+ $transactions[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'transaction_journal_id' => $journalId,
+ 'account_id' => $transfer['source_id'],
+ 'amount' => $amount * -1,
+ ];
+ $transactions[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'transaction_journal_id' => $journalId,
+ 'account_id' => $transfer['destination_id'],
+ 'amount' => $amount,
+ ];
+ // link to category if set.
+ if (isset($transfer['category_id'])) {
+ DB::table('category_transaction_journal')->insert(
+ [
+ 'category_id' => $transfer['category_id'],
+ 'transaction_journal_id' => $journalId,
+
+ ]
+ );
+ }
+ }
+ DB::table('transactions')->insert($transactions);
+ $transactions = [];
+ $current->addMonth();
+ }
+
+
+ }
+
+ /**
+ *
+ */
+ private function createMultiDeposits()
+ {
+ foreach ($this->data['multi-deposits'] as $deposit) {
+ $journalId = DB::table('transaction_journals')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $deposit['user_id'],
+ 'transaction_type_id' => 2,
+ 'transaction_currency_id' => 1,
+ 'description' => Crypt::encrypt($deposit['description']),
+ 'completed' => 1,
+ 'date' => $deposit['date'],
+ 'interest_date' => $deposit['interest_date'] ?? null,
+ 'book_date' => $deposit['book_date'] ?? null,
+ 'process_date' => $deposit['process_date'] ?? null,
+ 'encrypted' => 1,
+ 'order' => 0,
+ 'tag_count' => 0,
+ ]
+ );
+ foreach ($deposit['source_ids'] as $index => $source) {
+ $description = $deposit['description'] . ' (#' . ($index + 1) . ')';
+ $amount = $deposit['amounts'][$index];
+ $first = DB::table('transactions')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $deposit['destination_id'],
+ 'transaction_journal_id' => $journalId,
+ 'description' => $description,
+ 'amount' => $amount,
+ ]
+ );
+ $second = DB::table('transactions')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $source,
+ 'transaction_journal_id' => $journalId,
+ 'description' => $description,
+ 'amount' => $amount * -1,
+ ]
+ );
+ // link first and second to budget and category, if present.
+
+ if (isset($deposit['category_ids'][$index])) {
+ DB::table('category_transaction')->insert(
+ [
+ 'category_id' => $deposit['category_ids'][$index],
+ 'transaction_id' => $first,
+ ]
+ );
+ DB::table('category_transaction')->insert(
+ [
+ 'category_id' => $deposit['category_ids'][$index],
+ 'transaction_id' => $second,
+ ]
+ );
+ }
+ }
+ }
+ }
+
+ private function createMultiTransfers()
+ {
+ foreach ($this->data['multi-transfers'] as $transfer) {
+ $journalId = DB::table('transaction_journals')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $transfer['user_id'],
+ 'transaction_type_id' => 3,
+ 'transaction_currency_id' => 1,
+ 'description' => Crypt::encrypt($transfer['description']),
+ 'completed' => 1,
+ 'date' => $transfer['date'],
+ 'interest_date' => $transfer['interest_date'] ?? null,
+ 'book_date' => $transfer['book_date'] ?? null,
+ 'process_date' => $transfer['process_date'] ?? null,
+ 'encrypted' => 1,
+ 'order' => 0,
+ 'tag_count' => 0,
+ ]
+ );
+ foreach ($transfer['destination_ids'] as $index => $destination) {
+ $description = $transfer['description'] . ' (#' . ($index + 1) . ')';
+ $amount = $transfer['amounts'][$index];
+ $source = $transfer['source_ids'][$index];
+ $first = DB::table('transactions')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $source,
+ 'transaction_journal_id' => $journalId,
+ 'description' => $description,
+ 'amount' => $amount * -1,
+ ]
+ );
+ $second = DB::table('transactions')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $destination,
+ 'transaction_journal_id' => $journalId,
+ 'description' => $description,
+ 'amount' => $amount,
+ ]
+ );
+
+ if (isset($transfer['category_ids'][$index])) {
+ DB::table('category_transaction')->insert(
+ [
+ 'category_id' => $transfer['category_ids'][$index],
+ 'transaction_id' => $first,
+ ]
+ );
+ DB::table('category_transaction')->insert(
+ [
+ 'category_id' => $transfer['category_ids'][$index],
+ 'transaction_id' => $second,
+ ]
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function createMultiWithdrawals()
+ {
+ foreach ($this->data['multi-withdrawals'] as $withdrawal) {
+ $journalId = DB::table('transaction_journals')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $withdrawal['user_id'],
+ 'transaction_type_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'description' => Crypt::encrypt($withdrawal['description']),
+ 'completed' => 1,
+ 'date' => $withdrawal['date'],
+ 'interest_date' => $withdrawal['interest_date'] ?? null,
+ 'book_date' => $withdrawal['book_date'] ?? null,
+ 'process_date' => $withdrawal['process_date'] ?? null,
+ 'encrypted' => 1,
+ 'order' => 0,
+ 'tag_count' => 0,
+ ]
+ );
+ foreach ($withdrawal['destination_ids'] as $index => $destination) {
+ $description = $withdrawal['description'] . ' (#' . ($index + 1) . ')';
+ $amount = $withdrawal['amounts'][$index];
+ $first = DB::table('transactions')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $withdrawal['source_id'],
+ 'transaction_journal_id' => $journalId,
+ 'description' => $description,
+ 'amount' => $amount * -1,
+ ]
+ );
+ $second = DB::table('transactions')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $destination,
+ 'transaction_journal_id' => $journalId,
+ 'description' => $description,
+ 'amount' => $amount,
+ ]
+ );
+ // link first and second to budget and category, if present.
+ if (isset($withdrawal['budget_ids'][$index])) {
+ DB::table('budget_transaction')->insert(
+ [
+ 'budget_id' => $withdrawal['budget_ids'][$index],
+ 'transaction_id' => $first,
+ ]
+ );
+ DB::table('budget_transaction')->insert(
+ [
+ 'budget_id' => $withdrawal['budget_ids'][$index],
+ 'transaction_id' => $second,
+ ]
+ );
+ }
+
+ if (isset($withdrawal['category_ids'][$index])) {
+ DB::table('category_transaction')->insert(
+ [
+ 'category_id' => $withdrawal['category_ids'][$index],
+ 'transaction_id' => $first,
+ ]
+ );
+ DB::table('category_transaction')->insert(
+ [
+ 'category_id' => $withdrawal['category_ids'][$index],
+ 'transaction_id' => $second,
+ ]
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function createPiggyBanks()
+ {
+ foreach ($this->data['piggy-banks'] as $piggyBank) {
+ $piggyId = DB::table('piggy_banks')->insertGetId(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'account_id' => $piggyBank['account_id'],
+ 'name' => Crypt::encrypt($piggyBank['name']),
+ 'targetamount' => $piggyBank['targetamount'],
+ 'startdate' => $piggyBank['startdate'],
+ 'reminder_skip' => 0,
+ 'remind_me' => 0,
+ 'order' => $piggyBank['order'],
+ 'encrypted' => 1,
+ ]
+ );
+ if (isset($piggyBank['currentamount'])) {
+ DB::table('piggy_bank_repetitions')->insert(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'piggy_bank_id' => $piggyId,
+ 'startdate' => $piggyBank['startdate'],
+ 'currentamount' => $piggyBank['currentamount'],
+ ]
+ );
+ }
+ }
+
+ foreach ($this->data['piggy-events'] as $event) {
+ DB::table('piggy_bank_events')->insert(
+ [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'piggy_bank_id' => $event['piggy_bank_id'],
+ 'date' => $event['date'],
+ 'amount' => $event['amount'],
+ ]
+ );
+ }
+ }
+
+ /**
+ *
+ */
+ private function createRules()
+ {
+ $insert = [];
+ foreach ($this->data['rule-groups'] as $group) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $group['user_id'],
+ 'order' => $group['order'],
+ 'title' => $group['title'],
+ 'description' => $group['description'],
+ 'active' => 1,
+ ];
+ }
+ DB::table('rule_groups')->insert($insert);
+ $insert = [];
+ foreach ($this->data['rules'] as $rule) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $rule['user_id'],
+ 'rule_group_id' => $rule['rule_group_id'],
+ 'order' => $rule['order'],
+ 'active' => $rule['active'],
+ 'stop_processing' => $rule['stop_processing'],
+ 'title' => $rule['title'],
+ 'description' => $rule['description'],
+ ];
+ }
+ DB::table('rules')->insert($insert);
+
+ $insert = [];
+ foreach ($this->data['rule-triggers'] as $trigger) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'rule_id' => $trigger['rule_id'],
+ 'order' => $trigger['order'],
+ 'active' => $trigger['active'],
+ 'stop_processing' => $trigger['stop_processing'],
+ 'trigger_type' => $trigger['trigger_type'],
+ 'trigger_value' => $trigger['trigger_value'],
+ ];
+ }
+ DB::table('rule_triggers')->insert($insert);
+
+ $insert = [];
+ foreach ($this->data['rule-actions'] as $action) {
+ $insert[] = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'rule_id' => $action['rule_id'],
+ 'order' => $action['order'],
+ 'active' => $action['active'],
+ 'stop_processing' => $action['stop_processing'],
+ 'action_type' => $action['action_type'],
+ 'action_value' => $action['action_value'],
+ ];
+ }
+ DB::table('rule_actions')->insert($insert);
+ }
+
+ /**
+ *
+ */
+ private function createTags()
+ {
+ $insert = [];
+ foreach ($this->data['tags'] as $tag) {
+ $insert[]
+ = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'user_id' => $tag['user_id'],
+ 'tag' => Crypt::encrypt($tag['tag']),
+ 'tagMode' => $tag['tagMode'],
+ 'date' => $tag['date'] ?? null,
+ ];
+
+ }
+ DB::table('tags')->insert($insert);
+ }
+
+ /**
+ *
+ */
+ private function createUsers()
+ {
+ $insert = [];
+ foreach ($this->data['users'] as $user) {
+ $insert[]
+ = [
+ 'created_at' => $this->time,
+ 'updated_at' => $this->time,
+ 'email' => $user['email'],
+ 'password' => bcrypt($user['password']),
+ ];
+
+ }
+ DB::table('users')->insert($insert);
+ $insert = [];
+ foreach ($this->data['roles'] as $role) {
+ $insert[]
+ = [
+ 'user_id' => $role['user_id'],
+ 'role_id' => $role['role'],
+ ];
+ }
+ DB::table('role_user')->insert($insert);
+ }
+
+ /**
+ *
+ */
+ private function go()
+ {
+ $this->createUsers();
+ $this->createAccounts();
+ $this->createBills();
+ $this->createBudgets();
+ $this->createCategories();
+ $this->createPiggyBanks();
+ $this->createRules();
+ $this->createTags();
+ $this->createJournals();
+ $this->createAttachments();
+ $this->createMultiWithdrawals();
+ $this->createMultiDeposits();
+ $this->createMultiTransfers();
+ }
}
diff --git a/app/Support/Models/TagSupport.php b/app/Support/Models/TagSupport.php
new file mode 100644
index 0000000000..5af0e7c8fc
--- /dev/null
+++ b/app/Support/Models/TagSupport.php
@@ -0,0 +1,91 @@
+tagMode == 'balancingAct' || $tag->tagMode == 'nothing') {
+ foreach ($tag->transactionjournals as $journal) {
+ if ($journal->isTransfer()) {
+ return false;
+ }
+ }
+ }
+
+ /*
+ * If this tag contains more than one expenses, it cannot become an advance payment.
+ */
+ $count = 0;
+ foreach ($tag->transactionjournals as $journal) {
+ if ($journal->isWithdrawal()) {
+ $count++;
+ }
+ }
+ if ($count > 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Can a tag become a balancing act?
+ *
+ * @param Tag $tag
+ *
+ * @return bool
+ */
+ public static function tagAllowBalancing(Tag $tag): bool
+ {
+ /*
+ * If has more than two transactions already, cannot become a balancing act:
+ */
+ if ($tag->transactionjournals->count() > 2) {
+ return false;
+ }
+
+ /*
+ * If any transaction is a deposit, cannot become a balancing act.
+ */
+ foreach ($tag->transactionjournals as $journal) {
+ if ($journal->isDeposit()) {
+ return false;
+ }
+ }
+
+ return true;
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php
index 253361547a..1469715340 100644
--- a/app/Support/Models/TransactionJournalSupport.php
+++ b/app/Support/Models/TransactionJournalSupport.php
@@ -1,5 +1,4 @@
get();
}
- $transaction = $journal->transactions->sortByDesc('amount')->first();
- $amount = $transaction->amount;
+ // saves on queries:
+ $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount');
+
if ($journal->isWithdrawal()) {
- $amount = bcmul($amount, '-1');
+ $amount = $amount * -1;
}
+ $amount = strval($amount);
$cache->store($amount);
return $amount;
-
-
}
/**
@@ -69,13 +72,10 @@ class TransactionJournalSupport extends Model
return $cache->get();
}
- $amount = '0';
- /** @var Transaction $t */
- foreach ($journal->transactions as $t) {
- if ($t->amount > 0) {
- $amount = $t->amount;
- }
- }
+ // saves on queries:
+ $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount');
+
+ $amount = strval($amount);
$cache->store($amount);
return $amount;
@@ -134,48 +134,46 @@ class TransactionJournalSupport extends Model
/**
* @param TransactionJournal $journal
*
- * @return Account
+ * @return Collection
*/
- public static function destinationAccount(TransactionJournal $journal): Account
+ public static function destinationAccountList(TransactionJournal $journal): Collection
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty('transaction-journal');
- $cache->addProperty('destination-account');
+ $cache->addProperty('destination-account-list');
if ($cache->has()) {
return $cache->get();
}
- $transaction = $journal->transactions()->where('amount', '>', 0)->first();
- if (!is_null($transaction)) {
- $account = $transaction->account;
- $cache->store($account);
- } else {
- $account = new Account;
+ $transactions = $journal->transactions()->where('amount', '>', 0)->orderBy('transactions.account_id')->with('account')->get();
+ $list = new Collection;
+ /** @var Transaction $t */
+ foreach ($transactions as $t) {
+ $list->push($t->account);
}
+ $cache->store($list);
- return $account;
+ return $list;
}
/**
* @param TransactionJournal $journal
*
- * @return string
+ * @return Collection
*/
- public static function destinationAccountTypeStr(TransactionJournal $journal): string
+ public static function destinationTransactionList(TransactionJournal $journal): Collection
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty('transaction-journal');
- $cache->addProperty('destination-account-type-str');
+ $cache->addProperty('destination-transaction-list');
if ($cache->has()) {
return $cache->get();
}
+ $list = $journal->transactions()->where('amount', '>', 0)->with('account')->get();
+ $cache->store($list);
- $account = self::destinationAccount($journal);
- $type = $account->accountType ? $account->accountType->type : '(unknown)';
- $cache->store($type);
-
- return $type;
+ return $list;
}
/**
@@ -214,50 +212,60 @@ class TransactionJournalSupport extends Model
}
/**
- * @param TransactionJournal $journal
- *
- * @return Account
+ * @return array
*/
- public static function sourceAccount(TransactionJournal $journal): Account
+ public static function queryFields(): array
{
- $cache = new CacheProperties;
- $cache->addProperty($journal->id);
- $cache->addProperty('transaction-journal');
- $cache->addProperty('source-account');
- if ($cache->has()) {
- return $cache->get();
- }
- $transaction = $journal->transactions()->where('amount', '<', 0)->first();
- if (!is_null($transaction)) {
- $account = $transaction->account;
- $cache->store($account);
- } else {
- $account = new Account;
- }
-
- return $account;
+ return [
+ 'transaction_journals.*',
+ 'transaction_types.type AS transaction_type_type', // the other field is called "transaction_type_id" so this is pretty consistent.
+ 'transaction_currencies.code AS transaction_currency_code',
+ ];
}
/**
* @param TransactionJournal $journal
*
- * @return string
+ * @return Collection
*/
- public static function sourceAccountTypeStr(TransactionJournal $journal): string
+ public static function sourceAccountList(TransactionJournal $journal): Collection
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty('transaction-journal');
- $cache->addProperty('source-account-type-str');
+ $cache->addProperty('source-account-list');
if ($cache->has()) {
return $cache->get();
}
+ $transactions = $journal->transactions()->where('amount', '<', 0)->orderBy('transactions.account_id')->with('account')->get();
+ $list = new Collection;
+ /** @var Transaction $t */
+ foreach ($transactions as $t) {
+ $list->push($t->account);
+ }
+ $cache->store($list);
- $account = self::sourceAccount($journal);
- $type = $account->accountType ? $account->accountType->type : '(unknown)';
- $cache->store($type);
+ return $list;
+ }
- return $type;
+ /**
+ * @param TransactionJournal $journal
+ *
+ * @return Collection
+ */
+ public static function sourceTransactionList(TransactionJournal $journal): Collection
+ {
+ $cache = new CacheProperties;
+ $cache->addProperty($journal->id);
+ $cache->addProperty('transaction-journal');
+ $cache->addProperty('source-transaction-list');
+ if ($cache->has()) {
+ return $cache->get();
+ }
+ $list = $journal->transactions()->where('amount', '<', 0)->with('account')->get();
+ $cache->store($list);
+
+ return $list;
}
/**
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index 0d3777e93e..063f04be03 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -1,4 +1,12 @@
'addDay', 'daily' => 'addDay',
+ '1D' => 'endOfDay', 'daily' => 'endOfDay',
'1W' => 'addWeek', 'week' => 'addWeek', 'weekly' => 'addWeek',
'1M' => 'addMonth', 'month' => 'addMonth', 'monthly' => 'addMonth',
'3M' => 'addMonths', 'quarter' => 'addMonths', 'quarterly' => 'addMonths', '6M' => 'addMonths', 'half-year' => 'addMonths',
@@ -103,9 +111,14 @@ class Navigation
$function = $functionMap[$repeatFreq];
if (isset($modifierMap[$repeatFreq])) {
$currentEnd->$function($modifierMap[$repeatFreq]);
- } else {
- $currentEnd->$function();
+
+ if (in_array($repeatFreq, $subDay)) {
+ $currentEnd->subDay();
+ }
+
+ return $currentEnd;
}
+ $currentEnd->$function();
if (in_array($repeatFreq, $subDay)) {
$currentEnd->subDay();
}
@@ -328,9 +341,10 @@ class Navigation
if ($range == '6M') {
if ($start->month >= 7) {
$end->endOfYear();
- } else {
- $end->startOfYear()->addMonths(6);
+
+ return $end;
}
+ $end->startOfYear()->addMonths(6);
return $end;
}
@@ -362,11 +376,14 @@ class Navigation
if ($range == '6M') {
if ($start->month >= 7) {
$start->startOfYear()->addMonths(6);
- } else {
- $start->startOfYear();
+
+ return $start;
}
+ $start->startOfYear();
return $start;
+
+
}
throw new FireflyException('updateStartDate cannot handle $range "' . $range . '"');
}
diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php
index 52b958816b..717c7d364b 100644
--- a/app/Support/Preferences.php
+++ b/app/Support/Preferences.php
@@ -1,4 +1,12 @@
id)->where('name', $name)->first();
- $preference->delete();
+ Preference::where('user_id', Auth::user()->id)->where('name', $name)->delete();
return true;
}
@@ -111,9 +117,10 @@ class Preferences
$user = Auth::user();
if (is_null($user)) {
// make new preference, return it:
- $pref = new Preference;
+ $pref = new Preference;
$pref->name = $name;
$pref->data = $value;
+
return $pref;
}
@@ -135,13 +142,18 @@ class Preferences
if (!is_null($pref)) {
$pref->data = $value;
- } else {
- $pref = new Preference;
- $pref->name = $name;
- $pref->data = $value;
- $pref->user()->associate($user);
+ $pref->save();
+ Cache::forever($fullName, $pref);
+
+ return $pref;
}
+
+ $pref = new Preference;
+ $pref->name = $name;
+ $pref->data = $value;
+ $pref->user()->associate($user);
+
$pref->save();
Cache::forever($fullName, $pref);
diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php
index deb68245dd..cc163b99a9 100644
--- a/app/Support/Search/Search.php
+++ b/app/Support/Search/Search.php
@@ -1,4 +1,12 @@
orWhere('transaction_journals.description', 'LIKE', '%' . e($word) . '%');
}
}
- )->get(TransactionJournal::QUERYFIELDS);
+ )->get(TransactionJournal::queryFields());
// encrypted
- $all = Auth::user()->transactionjournals()->expanded()->where('transaction_journals.encrypted', 1)->get(TransactionJournal::QUERYFIELDS);
+ $all = Auth::user()->transactionjournals()->expanded()->where('transaction_journals.encrypted', 1)->get(TransactionJournal::queryFields());
$set = $all->filter(
function (TransactionJournal $journal) use ($words) {
foreach ($words as $word) {
diff --git a/app/Support/Search/SearchInterface.php b/app/Support/Search/SearchInterface.php
index c44c0d8285..f9914d1a50 100644
--- a/app/Support/Search/SearchInterface.php
+++ b/app/Support/Search/SearchInterface.php
@@ -1,4 +1,12 @@
addProperty($account->id);
$cache->addProperty('balance');
$cache->addProperty($date);
- $cache->addProperty($ignoreVirtualBalance);
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ $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')
+ );
+ $balance = bcadd($balance, $account->virtual_balance);
+ $cache->store($balance);
+
+ return $balance;
+ }
+
+ /**
+ *
+ * @param \FireflyIII\Models\Account $account
+ * @param \Carbon\Carbon $date
+ *
+ * @return string
+ */
+ public function balanceIgnoreVirtual(Account $account, Carbon $date): string
+ {
+
+ // abuse chart properties:
+ $cache = new CacheProperties;
+ $cache->addProperty($account->id);
+ $cache->addProperty('balance-no-virtual');
+ $cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
}
@@ -44,9 +79,6 @@ class Steam
)->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount')
);
- if (!$ignoreVirtualBalance) {
- $balance = bcadd($balance, $account->virtual_balance);
- }
$cache->store($balance);
return $balance;
diff --git a/app/Support/Twig/Budget.php b/app/Support/Twig/Budget.php
index 3363f356ac..496b5ae2cc 100644
--- a/app/Support/Twig/Budget.php
+++ b/app/Support/Twig/Budget.php
@@ -1,4 +1,12 @@
addProperty('formatAccountPerspective');
+ $cache->addProperty($journal->id);
+ $cache->addProperty($account->id);
+
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ // get the account amount:
+ $transactions = $journal->transactions()->where('transactions.account_id', $account->id)->get(['transactions.*']);
+ $amount = '0';
+ foreach ($transactions as $transaction) {
+ $amount = bcadd($amount, strval($transaction->amount));
+ }
+ if ($journal->isTransfer()) {
+ $amount = bcmul($amount, '-1');
+ }
+
+ // check if this sum is the same as the journal:
+ $journalSum = TransactionJournal::amount($journal);
+ $full = Amount::formatJournal($journal);
+ if (bccomp($journalSum, $amount) === 0 || bccomp(bcmul($journalSum, '-1'), $amount) === 0) {
+ $cache->store($full);
+
+ return $full;
+ }
+
+ $formatted = Amount::format($amount, true);
+
+ if ($journal->isTransfer()) {
+ $formatted = '' . Amount::format($amount) . '';
+ }
+ $str = $formatted . ' (' . $full . ')';
+ $cache->store($str);
+
+ return $str;
+
+ }
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function formatBudgetPerspective(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'formatBudgetPerspective', function (TransactionJournal $journal, ModelBudget $budget) {
+
+ $cache = new CacheProperties;
+ $cache->addProperty('formatBudgetPerspective');
+ $cache->addProperty($journal->id);
+ $cache->addProperty($budget->id);
+
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ // get the account amount:
+ $transactions = $journal->transactions()->where('transactions.amount', '<', 0)->get(['transactions.*']);
+ $amount = '0';
+ foreach ($transactions as $transaction) {
+ $currentBudget = $transaction->budgets->first();
+ if (!is_null($currentBudget) && $currentBudget->id === $budget->id) {
+ $amount = bcadd($amount, strval($transaction->amount));
+ }
+ }
+ if ($amount === '0') {
+ $formatted = Amount::formatJournal($journal);
+ $cache->store($formatted);
+
+ return $formatted;
+ }
+
+ $formatted = Amount::format($amount, true) . ' (' . Amount::formatJournal($journal) . ')';
+ $cache->store($formatted);
+
+ return $formatted;
+ }
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function getDestinationAccount(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'destinationAccount', function (TransactionJournal $journal) {
+ $cache = new CacheProperties;
+ $cache->addProperty($journal->id);
+ $cache->addProperty('transaction-journal');
+ $cache->addProperty('destination-account-string');
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ $list = TransactionJournal::destinationAccountList($journal);
+ $array = [];
+ /** @var Account $entry */
+ foreach ($list as $entry) {
+ if ($entry->accountType->type == 'Cash account') {
+ $array[] = '(cash)';
+ continue;
+ }
+ $array[] = '' . e($entry->name) . '';
+ }
+ $array = array_unique($array);
+ $result = join(', ', $array);
+ $cache->store($result);
+
+ return $result;
+
+ }
+ );
+ }
+
/**
* @return array
*/
@@ -33,7 +173,14 @@ class Journal extends Twig_Extension
public function getFunctions(): array
{
$functions = [
- $this->invalidJournal(),
+ $this->getSourceAccount(),
+ $this->getDestinationAccount(),
+ $this->formatAccountPerspective(),
+ $this->formatBudgetPerspective(),
+ $this->journalBudgets(),
+ $this->journalCategories(),
+ $this->transactionBudgets(),
+ $this->transactionCategories(),
];
return $functions;
@@ -52,20 +199,183 @@ class Journal extends Twig_Extension
/**
* @return Twig_SimpleFunction
*/
- protected function invalidJournal(): Twig_SimpleFunction
+ public function getSourceAccount(): Twig_SimpleFunction
{
return new Twig_SimpleFunction(
- 'invalidJournal', function (TransactionJournal $journal): bool {
- if (!isset($journal->transactions[1]) || !isset($journal->transactions[0])) {
- return true;
+ 'sourceAccount', function (TransactionJournal $journal): string {
+
+ $cache = new CacheProperties;
+ $cache->addProperty($journal->id);
+ $cache->addProperty('transaction-journal');
+ $cache->addProperty('source-account-string');
+ if ($cache->has()) {
+ return $cache->get();
}
- return false;
+ $list = TransactionJournal::sourceAccountList($journal);
+ $array = [];
+ /** @var Account $entry */
+ foreach ($list as $entry) {
+ if ($entry->accountType->type == 'Cash account') {
+ $array[] = '(cash)';
+ continue;
+ }
+ $array[] = '' . e($entry->name) . '';
+ }
+ $array = array_unique($array);
+ $result = join(', ', $array);
+ $cache->store($result);
+
+ return $result;
+
+
}
);
}
/**
+ * @return Twig_SimpleFunction
+ */
+ public function journalBudgets(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'journalBudgets', function (TransactionJournal $journal): string {
+ $cache = new CacheProperties;
+ $cache->addProperty($journal->id);
+ $cache->addProperty('transaction-journal');
+ $cache->addProperty('budget-string');
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+
+ $budgets = [];
+ // get all budgets:
+ foreach ($journal->budgets as $budget) {
+ $budgets[] = '' . e($budget->name) . '';
+ }
+ // and more!
+ foreach ($journal->transactions as $transaction) {
+ foreach ($transaction->budgets as $budget) {
+ $budgets[] = '' . e($budget->name) . '';
+ }
+ }
+ $string = join(', ', array_unique($budgets));
+ $cache->store($string);
+
+ return $string;
+
+
+ }
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function journalCategories(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'journalCategories', function (TransactionJournal $journal): string {
+ $cache = new CacheProperties;
+ $cache->addProperty($journal->id);
+ $cache->addProperty('transaction-journal');
+ $cache->addProperty('category-string');
+ if ($cache->has()) {
+ return $cache->get();
+ }
+ $categories = [];
+ // get all categories for the journal itself (easy):
+ foreach ($journal->categories as $category) {
+ $categories[] = '' . e($category->name) . '';
+ }
+ if (count($categories) === 0) {
+ $set = Category::distinct()->leftJoin('category_transaction', 'categories.id', '=', 'category_transaction.category_id')
+ ->leftJoin('transactions', 'category_transaction.transaction_id', '=', 'transactions.id')
+ ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('categories.user_id', $journal->user_id)
+ ->where('transaction_journals.id', $journal->id)
+ ->get(['categories.*']);
+ /** @var Category $category */
+ foreach ($set as $category) {
+ $categories[] = '' . e($category->name)
+ . '';
+ }
+ }
+
+ $string = join(', ', array_unique($categories));
+ $cache->store($string);
+
+ return $string;
+ }
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function transactionBudgets(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'transactionBudgets', function (Transaction $transaction): string {
+ $cache = new CacheProperties;
+ $cache->addProperty($transaction->id);
+ $cache->addProperty('transaction');
+ $cache->addProperty('budget-string');
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ $budgets = [];
+ // get all budgets:
+ foreach ($transaction->budgets as $budget) {
+ $budgets[] = '' . e($budget->name) . '';
+ }
+ $string = join(', ', array_unique($budgets));
+ $cache->store($string);
+
+ return $string;
+
+
+ }
+ );
+ }
+
+ /**
+ * @return Twig_SimpleFunction
+ */
+ public function transactionCategories(): Twig_SimpleFunction
+ {
+ return new Twig_SimpleFunction(
+ 'transactionCategories', function (Transaction $transaction): string {
+ $cache = new CacheProperties;
+ $cache->addProperty($transaction->id);
+ $cache->addProperty('transaction');
+ $cache->addProperty('category-string');
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+
+ $categories = [];
+ // get all budgets:
+ foreach ($transaction->categories as $category) {
+ $categories[] = '' . e($category->name) . '';
+ }
+
+ $string = join(', ', array_unique($categories));
+ $cache->store($string);
+
+ return $string;
+
+
+ }
+ );
+ }
+
+ /**
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 5.
+ *
* @return Twig_SimpleFilter
*/
protected function typeIcon(): Twig_SimpleFilter
diff --git a/app/Support/Twig/PiggyBank.php b/app/Support/Twig/PiggyBank.php
index 4c101e21fb..e124fc5256 100644
--- a/app/Support/Twig/PiggyBank.php
+++ b/app/Support/Twig/PiggyBank.php
@@ -1,4 +1,12 @@
verifyKey($secret, $value);
}
@@ -78,7 +83,9 @@ class FireflyValidator extends Validator
{
$field = $parameters[1] ?? 'id';
-
+ if (intval($value) === 0) {
+ return true;
+ }
$count = DB::table($parameters[0])->where('user_id', Auth::user()->id)->where($field, $value)->count();
if ($count === 1) {
return true;
@@ -92,7 +99,6 @@ class FireflyValidator extends Validator
* @param $attribute
* @param $value
*
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return bool
*/
@@ -136,12 +142,11 @@ class FireflyValidator extends Validator
$value = $this->data['rule-action-value'][$index] ?? false;
switch ($name) {
default:
- Log::debug(' (' . $attribute . ') (index:' . $index . ') Name is "' . $name . '" so no action is taken.');
return true;
case 'set_budget':
/** @var BudgetRepositoryInterface $repository */
- $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+ $repository = app(BudgetRepositoryInterface::class);
$budgets = $repository->getBudgets();
// count budgets, should have at least one
$count = $budgets->filter(
@@ -214,7 +219,6 @@ class FireflyValidator extends Validator
* @param $value
* @param $parameters
*
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return bool
*/
@@ -279,7 +283,6 @@ class FireflyValidator extends Validator
* @param $value
* @param $parameters
*
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return bool
*/
@@ -310,7 +313,6 @@ class FireflyValidator extends Validator
* @param $value
* @param $parameters
*
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return bool
*/
@@ -342,9 +344,6 @@ class FireflyValidator extends Validator
* @param $value
* @param $parameters
*
- * @SuppressWarnings(PHPMD.UnusedFormalParameter) // cant remove it
- * @SuppressWarnings(PHPMD.CyclomaticComplexity) // its as simple as I can get it.
- *
* @return bool
*/
public function validateUniquePiggyBankForUser($attribute, $value, $parameters): bool
diff --git a/bootstrap/app.php b/bootstrap/app.php
index 500021fb9a..c9bc5691c7 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -1,4 +1,6 @@
=5.3.3"
+ "php": "^5.3.3|^7.0"
},
"require-dev": {
+ "ext-intl": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~1.5"
},
- "suggest": {
- "ext-intl": "*"
- },
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "1.5.x-dev"
- }
+ "branch-alias": []
},
"autoload": {
"psr-4": {
@@ -3061,7 +3058,7 @@
"faker",
"fixtures"
],
- "time": "2015-05-29 06:29:14"
+ "time": "2016-04-29 12:21:54"
},
{
"name": "hamcrest/hamcrest-php",
@@ -3111,56 +3108,6 @@
],
"time": "2016-04-21 19:47:43"
},
- {
- "name": "johnkary/phpunit-speedtrap",
- "version": "v1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/johnkary/phpunit-speedtrap.git",
- "reference": "76a26f8a903a9434608cdad2b41c40cd134ea326"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/johnkary/phpunit-speedtrap/zipball/76a26f8a903a9434608cdad2b41c40cd134ea326",
- "reference": "76a26f8a903a9434608cdad2b41c40cd134ea326",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "require-dev": {
- "phpunit/phpunit": "3.7.*|~4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "JohnKary": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "John Kary",
- "email": "john@johnkary.net"
- }
- ],
- "description": "Find slow tests in your PHPUnit test suite",
- "homepage": "https://github.com/johnkary/phpunit-speedtrap",
- "keywords": [
- "phpunit",
- "profile",
- "slow"
- ],
- "time": "2015-09-13 19:01:00"
- },
{
"name": "maximebf/debugbar",
"version": "v1.11.1",
@@ -3228,12 +3175,12 @@
"source": {
"type": "git",
"url": "https://github.com/padraic/mockery.git",
- "reference": "4957f4086614e4fb4e710680f2c000dda0db1008"
+ "reference": "ad31ff997d983e0d5d60ac80cfcedcbb4e6c4461"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/padraic/mockery/zipball/4957f4086614e4fb4e710680f2c000dda0db1008",
- "reference": "4957f4086614e4fb4e710680f2c000dda0db1008",
+ "url": "https://api.github.com/repos/padraic/mockery/zipball/ad31ff997d983e0d5d60ac80cfcedcbb4e6c4461",
+ "reference": "ad31ff997d983e0d5d60ac80cfcedcbb4e6c4461",
"shasum": ""
},
"require": {
@@ -3285,7 +3232,7 @@
"test double",
"testing"
],
- "time": "2016-04-20 18:36:44"
+ "time": "2016-05-03 10:17:25"
},
{
"name": "phpdocumentor/reflection-docblock",
@@ -3550,21 +3497,24 @@
},
{
"name": "phpunit/php-timer",
- "version": "1.0.7",
+ "version": "1.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
- "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
+ "require-dev": {
+ "phpunit/phpunit": "~4|~5"
+ },
"type": "library",
"autoload": {
"classmap": [
@@ -3587,7 +3537,7 @@
"keywords": [
"timer"
],
- "time": "2015-06-21 08:01:12"
+ "time": "2016-05-12 18:03:57"
},
{
"name": "phpunit/php-token-stream",
@@ -3640,16 +3590,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "4.8.24",
+ "version": "4.8.26",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a1066c562c52900a142a0e2bbf0582994671385e"
+ "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e",
- "reference": "a1066c562c52900a142a0e2bbf0582994671385e",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74",
+ "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74",
"shasum": ""
},
"require": {
@@ -3663,7 +3613,7 @@
"phpunit/php-code-coverage": "~2.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
- "phpunit/php-timer": ">=1.0.6",
+ "phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.1",
"sebastian/diff": "~1.2",
@@ -3708,7 +3658,7 @@
"testing",
"xunit"
],
- "time": "2016-03-14 06:16:08"
+ "time": "2016-05-17 03:09:28"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -3884,16 +3834,16 @@
},
{
"name": "sebastian/environment",
- "version": "1.3.5",
+ "version": "1.3.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
+ "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
- "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716",
+ "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716",
"shasum": ""
},
"require": {
@@ -3930,7 +3880,7 @@
"environment",
"hhvm"
],
- "time": "2016-02-26 18:40:46"
+ "time": "2016-05-17 03:18:57"
},
{
"name": "sebastian/exporter",
@@ -4139,7 +4089,7 @@
},
{
"name": "symfony/class-loader",
- "version": "v3.0.4",
+ "version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
@@ -4195,7 +4145,7 @@
},
{
"name": "symfony/css-selector",
- "version": "v3.0.4",
+ "version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -4248,16 +4198,16 @@
},
{
"name": "symfony/dom-crawler",
- "version": "v3.0.4",
+ "version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f"
+ "reference": "49b588841225b205700e5122fa01911cabada857"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/18a06d7a9af41718c20764a674a0ebba3bc40d1f",
- "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/49b588841225b205700e5122fa01911cabada857",
+ "reference": "49b588841225b205700e5122fa01911cabada857",
"shasum": ""
},
"require": {
@@ -4300,11 +4250,11 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2016-03-23 13:23:25"
+ "time": "2016-04-12 18:09:53"
},
{
"name": "symfony/yaml",
- "version": "v3.0.4",
+ "version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
diff --git a/config/app.php b/config/app.php
index 8ba2732c0d..79a9e67ed6 100644
--- a/config/app.php
+++ b/config/app.php
@@ -1,4 +1,5 @@
[
'RabobankDescription',
diff --git a/config/database.php b/config/database.php
index 9467c969e6..40a8db36e2 100644
--- a/config/database.php
+++ b/config/database.php
@@ -1,4 +1,7 @@
storage_path('app'),
],
- 'upload' => [
+ 'upload' => [
'driver' => 'local',
'root' => storage_path('upload'),
],
- 'export' => [
+ 'export' => [
'driver' => 'local',
'root' => storage_path('export'),
],
+ 'database' => [
+ 'driver' => 'local',
+ 'root' => storage_path('database'),
+ ],
'ftp' => [
'driver' => 'ftp',
diff --git a/config/firefly.php b/config/firefly.php
index 3c7e8b8b19..4a045e1f6a 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -1,10 +1,10 @@
'chartjs',
- 'version' => '3.8.4',
- 'index_periods' => ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom'],
- 'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
+ 'version' => '3.9.0',
'csv_import_enabled' => true,
'maxUploadSize' => 5242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
@@ -16,20 +16,7 @@ return [
// mt940 FireflyIII Export Exporter MtExporter
],
'default_export_format' => 'csv',
-
- 'piggy_bank_periods' => [
- 'week' => 'Week',
- 'month' => 'Month',
- 'quarter' => 'Quarter',
- 'year' => 'Year',
- ],
- 'periods_to_text' => [
- 'weekly' => 'A week',
- 'monthly' => 'A month',
- 'quarterly' => 'A quarter',
- 'half-year' => 'Six months',
- 'yearly' => 'A year',
- ],
+ 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
'accountRoles' => [
'defaultAsset' => 'Default asset account',
@@ -38,32 +25,17 @@ return [
'ccAsset' => 'Credit card',
],
- 'range_to_text' => [
- '1D' => 'day',
- '1W' => 'week',
- '1M' => 'month',
- '3M' => 'three months',
- '6M' => 'half year',
- 'custom' => '(custom)',
- ],
'ccTypes' => [
'monthlyFull' => 'Full payment every month',
],
- 'range_to_name' => [
- '1D' => 'one day',
- '1W' => 'one week',
- '1M' => 'one month',
- '3M' => 'three months',
- '6M' => 'six months',
- '1Y' => 'one year',
- ],
'range_to_repeat_freq' => [
'1D' => 'weekly',
'1W' => 'weekly',
'1M' => 'monthly',
'3M' => 'quarterly',
'6M' => 'half-year',
- 'custom' => 'monthly',
+ '1Y' => 'yearly',
+ 'custom' => 'custom',
],
'subTitlesByIdentifier' =>
[
@@ -110,21 +82,9 @@ return [
'languages' => [
'en_US' => ['name_locale' => 'English', 'name_english' => 'English', 'complete' => true],
'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch', 'complete' => true],
- 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portugese (Brazil)', 'complete' => false],
+ 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)', 'complete' => true],
'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French', 'complete' => false],
],
- 'lang' => [
- 'en_US' => 'English',
- 'nl_NL' => 'Nederlands',
- 'fr_FR' => 'Français',
- 'pt_BR' => 'Português do Brasil',
- ],
- 'locales' => [
- 'en_US' => ['en', 'English', 'en_US', 'en_US.utf8'],
- 'nl_NL' => ['nl', 'Dutch', 'nl_NL', 'nl_NL.utf8'],
- 'pt_BR' => ['pt_BR', 'pt_BR.utf8'],
- 'fr_FR' => ['fr_FR', 'fr_FR.utf8'],
- ],
'transactionTypesByWhat' => [
'expenses' => ['Withdrawal'],
'withdrawal' => ['Withdrawal'],
@@ -143,42 +103,31 @@ return [
],
- 'month' => [
- 'en_US' => '%B %Y',
- 'nl_NL' => '%B %Y',
- 'fr_FR' => '%B %Y',
- 'pt_BR' => '%B %Y',
- ],
- 'monthAndDay' => [
- 'en_US' => '%B %e, %Y',
- 'nl_NL' => '%e %B %Y',
- 'fr_FR' => '%B %e, %Y',
- 'pt_BR' => '%B %e, %Y',
- ],
'bindables' => [
// models
- 'account' => 'FireflyIII\Models\Account',
- 'attachment' => 'FireflyIII\Models\Attachment',
- 'bill' => 'FireflyIII\Models\Bill',
- 'budget' => 'FireflyIII\Models\Budget',
- 'category' => 'FireflyIII\Models\Category',
- 'currency' => 'FireflyIII\Models\TransactionCurrency',
- 'limitrepetition' => 'FireflyIII\Models\LimitRepetition',
- 'piggyBank' => 'FireflyIII\Models\PiggyBank',
- 'tj' => 'FireflyIII\Models\TransactionJournal',
- 'tag' => 'FireflyIII\Models\Tag',
- 'rule' => 'FireflyIII\Models\Rule',
- 'ruleGroup' => 'FireflyIII\Models\RuleGroup',
- 'jobKey' => 'FireflyIII\Models\ExportJob',
+ 'account' => 'FireflyIII\Models\Account',
+ 'attachment' => 'FireflyIII\Models\Attachment',
+ 'bill' => 'FireflyIII\Models\Bill',
+ 'budget' => 'FireflyIII\Models\Budget',
+ 'category' => 'FireflyIII\Models\Category',
+ 'currency' => 'FireflyIII\Models\TransactionCurrency',
+ 'limitrepetition' => 'FireflyIII\Models\LimitRepetition',
+ 'piggyBank' => 'FireflyIII\Models\PiggyBank',
+ 'tj' => 'FireflyIII\Models\TransactionJournal',
+ 'unfinishedJournal' => 'FireflyIII\Support\Binder\UnfinishedJournal',
+ 'tag' => 'FireflyIII\Models\Tag',
+ 'rule' => 'FireflyIII\Models\Rule',
+ 'ruleGroup' => 'FireflyIII\Models\RuleGroup',
+ 'jobKey' => 'FireflyIII\Models\ExportJob',
// lists
- 'accountList' => 'FireflyIII\Support\Binder\AccountList',
- 'budgetList' => 'FireflyIII\Support\Binder\BudgetList',
- 'journalList' => 'FireflyIII\Support\Binder\JournalList',
- 'categoryList' => 'FireflyIII\Support\Binder\CategoryList',
+ 'accountList' => 'FireflyIII\Support\Binder\AccountList',
+ 'budgetList' => 'FireflyIII\Support\Binder\BudgetList',
+ 'journalList' => 'FireflyIII\Support\Binder\JournalList',
+ 'categoryList' => 'FireflyIII\Support\Binder\CategoryList',
// others
- 'start_date' => 'FireflyIII\Support\Binder\Date',
- 'end_date' => 'FireflyIII\Support\Binder\Date',
+ 'start_date' => 'FireflyIII\Support\Binder\Date',
+ 'end_date' => 'FireflyIII\Support\Binder\Date',
],
'rule-triggers' => [
diff --git a/config/mail.php b/config/mail.php
index 0fc18bc022..1027fe9952 100644
--- a/config/mail.php
+++ b/config/mail.php
@@ -1,4 +1,7 @@
[
diff --git a/config/upgrade.php b/config/upgrade.php
index 819b830b98..27059b92db 100644
--- a/config/upgrade.php
+++ b/config/upgrade.php
@@ -1,4 +1,7 @@
define(
FireflyIII\User::class, function (Faker\Generator $faker) {
diff --git a/database/migrations/2014_06_27_163032_create_users_table.php b/database/migrations/2014_06_27_163032_create_users_table.php
index 1bd8824f42..57f44c51cd 100644
--- a/database/migrations/2014_06_27_163032_create_users_table.php
+++ b/database/migrations/2014_06_27_163032_create_users_table.php
@@ -1,4 +1,7 @@
unique(['budget_id', 'startdate', 'repeat_freq'], 'unique_bl_combi');
+ }
+ );
+
+
+ $backup = $this->backupRepeatFreqsFromString();
+
+ // drop string and create enum field
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->dropColumn('repeat_freq');
+ }
+ );
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->enum('repeat_freq', ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly']);
+ }
+ );
+
+ // restore backup. Change unknowns to "monthly".
+ $this->restoreRepeatFreqsToEnum($backup);
+
+ // drop budget <> transaction table:
+ Schema::dropIfExists('budget_transaction');
+
+ // drop category <> transaction table:
+ Schema::dropIfExists('category_transaction');
+
+ }
+
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ // // remove an index.
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->dropForeign('bid_foreign');
+ }
+ );
+
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->dropUnique('unique_limit');
+ $table->dropUnique('unique_bl_combi');
+ }
+ );
+
+
+ // recreate foreign key:
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->foreign('budget_id', 'bid_foreign')->references('id')->on('budgets')->onDelete('cascade');
+ }
+ );
+
+
+ // backup values
+ $backup = $this->backupRepeatFreqsFromEnum();
+
+ // drop enum and create varchar field
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->dropColumn('repeat_freq');
+ }
+ );
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->string('repeat_freq', 20)->default('monthly');
+ }
+ );
+
+ // put data back:
+ $this->restoreRepeatFreqsToVarchar($backup);
+
+
+ // create it again, correctly.
+ Schema::table(
+ 'budget_limits', function (Blueprint $table) {
+ $table->unique(['budget_id', 'startdate', 'repeat_freq'], 'unique_limit');
+ }
+ );
+
+ // create NEW table for transactions <> budgets
+ Schema::create(
+ 'budget_transaction', function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('budget_id')->unsigned();
+ $table->integer('transaction_id')->unsigned();
+ $table->foreign('budget_id')->references('id')->on('budgets')->onDelete('cascade');
+ $table->foreign('transaction_id')->references('id')->on('transactions')->onDelete('cascade');
+ $table->unique(['budget_id', 'transaction_id'], 'budid_tid_unique');
+ }
+ );
+
+ // create NEW table for transactions <> categories
+ Schema::create(
+ 'category_transaction', function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('category_id')->unsigned();
+ $table->integer('transaction_id')->unsigned();
+ $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
+ $table->foreign('transaction_id')->references('id')->on('transactions')->onDelete('cascade');
+ $table->unique(['category_id', 'transaction_id'], 'catid_tid_unique');
+ }
+ );
+ }
+
+ /**
+ * @return array
+ */
+ private function backupRepeatFreqsFromEnum(): array
+ {
+ $backup = [];
+ $set = BudgetLimit::get();
+ /** @var BudgetLimit $entry */
+ foreach ($set as $entry) {
+ $backup[$entry->id] = $entry->repeat_freq;
+ }
+
+ return $backup;
+ }
+
+ /**
+ * Same routine.
+ *
+ * @return array
+ */
+ private function backupRepeatFreqsFromString()
+ {
+ return $this->backupRepeatFreqsFromEnum();
+ }
+
+ /**
+ * @param array $backup
+ *
+ * @return bool
+ */
+ private function restoreRepeatFreqsToEnum(array $backup): bool
+ {
+ foreach ($backup as $id => $repeatFreq) {
+ $budgetLimit = BudgetLimit::find($id);
+ if (!in_array($repeatFreq, ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'])) {
+ $repeatFreq = 'monthly';
+ }
+ $budgetLimit->repeat_freq = $repeatFreq;
+ $budgetLimit->save();
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $backup
+ *
+ * @return bool
+ */
+ private function restoreRepeatFreqsToVarchar(array $backup): bool
+ {
+ foreach ($backup as $id => $repeatFreq) {
+ $budgetLimit = BudgetLimit::find($id);
+ $budgetLimit->repeat_freq = $repeatFreq;
+ $budgetLimit->save();
+ }
+
+ return true;
+ }
+}
diff --git a/database/seeds/AccountTypeSeeder.php b/database/seeds/AccountTypeSeeder.php
index 7b2b56e021..76e22d8c37 100644
--- a/database/seeds/AccountTypeSeeder.php
+++ b/database/seeds/AccountTypeSeeder.php
@@ -1,4 +1,6 @@
call('TransactionCurrencySeeder');
$this->call('TransactionTypeSeeder');
$this->call('PermissionSeeder');
-
- // set up basic test data (as little as possible):
- if (App::environment() == 'testing' || App::environment() == 'local') {
- $this->call('TestDataSeeder');
- }
+ $this->call('TestDataSeeder');
}
}
diff --git a/database/seeds/PermissionSeeder.php b/database/seeds/PermissionSeeder.php
index c6ef9688f3..7ef4e1db75 100644
--- a/database/seeds/PermissionSeeder.php
+++ b/database/seeds/PermissionSeeder.php
@@ -1,4 +1,7 @@
start = Carbon::create()->subYears(2)->startOfYear();
- $this->end = Carbon::now();
-
}
/**
@@ -39,56 +31,18 @@ class TestDataSeeder extends Seeder
*/
public function run()
{
- // start by creating all users:
- // method will return the first user.
- $user = TestData::createUsers();
+ $disk = Storage::disk('database');
+ $env = App::environment();
+ Log::debug('Environment is ' . $env);
+ $fileName = 'seed.' . $env . '.json';
+ if ($disk->exists($fileName)) {
+ Log::debug('Now seeding ' . $fileName);
+ $file = json_decode($disk->get($fileName), true);
+ // run the file:
+ TestData::run($file);
- // create all kinds of static data:
- TestData::createAssetAccounts($user);
- TestData::createBills($user);
- TestData::createBudgets($user);
- TestData::createCategories($user);
- TestData::createPiggybanks($user);
- TestData::createExpenseAccounts($user);
- TestData::createRevenueAccounts($user);
- TestData::createAttachments($user, $this->start);
- TestData::openingBalanceSavings($user, $this->start);
- TestData::createRules($user);
-
- // loop from start to end, create dynamic info.
- $current = clone $this->start;
- while ($current < $this->end) {
- $month = $current->format('F Y');
- // create salaries:
- TestData::createIncome($user, 'Salary ' . $month, $current, strval(rand(2000, 2100)));
-
- // pay bills:
- TestData::createRent($user, 'Rent for ' . $month, $current, '800');
- TestData::createWater($user, 'Water bill for ' . $month, $current, '15');
- TestData::createTV($user, 'TV bill for ' . $month, $current, '60');
- TestData::createPower($user, 'Power bill for ' . $month, $current, '120');
-
- // pay daily groceries:
- TestData::createGroceries($user, $current);
-
- // create tag (each type of tag, for date):
- TestData::createTags($user, $current);
-
- // go out for drinks:
- TestData::createDrinksAndOthers($user, $current);
-
- // save money every month:
- TestData::createSavings($user, $current);
-
- // buy gas for the car every month:
- TestData::createCar($user, $current);
-
- // create budget limits.
- TestData::createBudgetLimit($user, $current, 'Groceries', '400');
- TestData::createBudgetLimit($user, $current, 'Bills', '1000');
- TestData::createBudgetLimit($user, $current, 'Car', '100');
-
- $current->addMonth();
+ return;
}
+ Log::info('No seed file (' . $fileName . ') for environment ' . $env);
}
}
diff --git a/database/seeds/TransactionCurrencySeeder.php b/database/seeds/TransactionCurrencySeeder.php
index 7b7486ce12..9a0292200f 100644
--- a/database/seeds/TransactionCurrencySeeder.php
+++ b/database/seeds/TransactionCurrencySeeder.php
@@ -1,4 +1,5 @@
'EUR', 'name' => 'Euro', 'symbol' => '€']);
TransactionCurrency::create(['code' => 'USD', 'name' => 'US Dollar', 'symbol' => '$']);
TransactionCurrency::create(['code' => 'HUF', 'name' => 'Hungarian forint', 'symbol' => 'Ft']);
+ TransactionCurrency::create(['code' => 'BRL', 'name' => 'Real', 'symbol' => 'R$']);
}
}
diff --git a/database/seeds/TransactionTypeSeeder.php b/database/seeds/TransactionTypeSeeder.php
index 4f7a6e13d6..6b6c1f0c22 100644
--- a/database/seeds/TransactionTypeSeeder.php
+++ b/database/seeds/TransactionTypeSeeder.php
@@ -1,4 +1,6 @@
1) {
+ colspan = colspanAttr;
+ }
+ cellCount += colspan;
+ });
+
+ // Add the placeholder UI - note that this is the item's content, so TD rather than TR
+ ui.placeholder.html(' ');
+ }
+ }
+ ).disableSelection();
+ } else {
+ console.log('its null');
+ }
+
+});
+
+function sortStop(event, ui) {
+ "use strict";
+ var current = $(ui.item);
+ console.log('sort stop');
+ var thisDate = current.data('date');
+ var originalBG = current.css('backgroundColor');
+
+
+ if (current.prev().data('date') !== thisDate && current.next().data('date') !== thisDate) {
+ // animate something with color:
+ current.animate({backgroundColor: "#d9534f"}, 200, function () {
+ $(this).animate({backgroundColor: originalBG}, 200);
+ });
+
+ return false;
+ }
+
+ // do update
+ var list = $('tr[data-date="' + thisDate + '"]');
+ var submit = [];
+ $.each(list, function (i, v) {
+ var row = $(v);
+ var id = row.data('id');
+ submit.push(id);
+ });
+
+ // do extra animation when done?
+ $.post('transaction/reorder', {items: submit, date: thisDate, _token: token});
+
+ current.animate({backgroundColor: "#5cb85c"}, 200, function () {
+ $(this).animate({backgroundColor: originalBG}, 200);
+ });
+}
diff --git a/public/js/ff/charts.js b/public/js/ff/charts.js
index 7e30624236..7201e1f0cb 100644
--- a/public/js/ff/charts.js
+++ b/public/js/ff/charts.js
@@ -32,23 +32,6 @@ var colourSet = [
];
-// Settings object that controls default parameters for library methods:
-accounting.settings = {
- currency: {
- symbol: currencySymbol, // default currency symbol is '$'
- format: "%s %v", // controls output: %s = symbol, %v = value/number (can be object: see below)
- decimal: mon_decimal_point, // decimal point separator
- thousand: mon_thousands_sep, // thousands separator
- precision: frac_digits // decimal places
- },
- number: {
- precision: 0, // default precision on numbers is 0
- thousand: ",",
- decimal: "."
- }
-};
-
-
var fillColors = [];
var strokePointHighColors = [];
@@ -156,8 +139,8 @@ var defaultColumnOptions = {
callback: function (tickValue, index, ticks) {
"use strict";
return accounting.formatMoney(tickValue);
-
- }
+ },
+ beginAtZero: true
}
}]
},
diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js
index 3130714722..5ce868262a 100644
--- a/public/js/ff/firefly.js
+++ b/public/js/ff/firefly.js
@@ -96,19 +96,20 @@ function currencySelect(e) {
return false;
-
- //var code = target.data('code');
- //var fieldType = target.data('field');
- //var menu = $('.' + fieldType + 'CurrencyDropdown');
- //
- //var symbolHolder = $('#' + fieldType + 'CurrentSymbol');
- //symbolHolder.text(symbol);
- //$('input[name="' + fieldType + '_currency_id"]').val(id);
- //
- // close dropdown (hack hack)
- //menu.click();
-
-
- //return false;
}
+// Settings object that controls default parameters for library methods:
+accounting.settings = {
+ currency: {
+ symbol: currencySymbol, // default currency symbol is '$'
+ format: "%s %v", // controls output: %s = symbol, %v = value/number (can be object: see below)
+ decimal: mon_decimal_point, // decimal point separator
+ thousand: mon_thousands_sep, // thousands separator
+ precision: frac_digits // decimal places
+ },
+ number: {
+ precision: 0, // default precision on numbers is 0
+ thousand: ",",
+ decimal: "."
+ }
+};
diff --git a/public/js/ff/reports/default/year.js b/public/js/ff/reports/default/year.js
index 8a39be3ef3..f342c7e1dc 100644
--- a/public/js/ff/reports/default/year.js
+++ b/public/js/ff/reports/default/year.js
@@ -18,10 +18,15 @@ function drawChart() {
lineChart('chart/report/net-worth/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'net-worth');
columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart');
columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart');
- stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets');
- stackedColumnChart('chart/category/spent-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-spent-in-period');
- stackedColumnChart('chart/category/earned-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-earned-in-period');
+ // in a loop
+ $.each($('.budget_year_chart'), function (i, v) {
+ var holder = $(v);
+ var id = holder.attr('id');
+ var budgetId = holder.data('budget');
+ columnChart('chart/budget/period/' + budgetId + '/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, id);
+
+ });
}
diff --git a/public/js/ff/split/journal/from-store.js b/public/js/ff/split/journal/from-store.js
new file mode 100644
index 0000000000..4cbb57d3d7
--- /dev/null
+++ b/public/js/ff/split/journal/from-store.js
@@ -0,0 +1,49 @@
+/*
+ * from-store.js
+ * Copyright (C) 2016 thegrumpydictator@gmail.com
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+/* globals globalSum */
+
+$(function () {
+ "use strict";
+ $('.btn-do-split').click(cloneRow);
+ $('input[name="amount[]"]').on('input', calculateSum)
+});
+
+function cloneRow() {
+ "use strict";
+ var source = $('.initial-row').clone();
+ var count = $('.split-table tbody tr').length + 1;
+ source.removeClass('initial-row');
+ source.find('.count').text('#' + count);
+ source.find('input[name="amount[]"]').val("").on('input', calculateSum);
+
+ $('.split-table tbody').append(source);
+
+ calculateSum();
+
+ return false;
+}
+
+function calculateSum() {
+ "use strict";
+ var sum = 0;
+ var set = $('input[name="amount[]"]');
+ for (var i = 0; i < set.length; i++) {
+ var current = $(set[i]);
+ sum += (current.val() == "" ? 0 : parseFloat(current.val()));
+
+ }
+ console.log("Sum is now " + sum);
+ $('.amount-warning').remove();
+ if (sum != originalSum) {
+ console.log(sum + ' does not match ' + originalSum);
+ var holder = $('#journal_amount_holder');
+ var par = holder.find('p.form-control-static');
+ var amount = $('').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par);
+ }
+}
\ No newline at end of file
diff --git a/public/js/ff/transactions/create-edit.js b/public/js/ff/transactions/create-edit.js
index 8791d8a8fc..2561dbe90d 100644
--- a/public/js/ff/transactions/create-edit.js
+++ b/public/js/ff/transactions/create-edit.js
@@ -8,9 +8,18 @@
/* globals what:true, $, doSwitch, txt, middleCrumbName, title,button, middleCrumbUrl, piggiesLength, breadcrumbs */
$(document).ready(function () {
"use strict";
- if ($('input[name="expense_account"]').length > 0) {
+
+ // the destination account name is always an expense account name.
+ if ($('input[name="destination_account_name"]').length > 0) {
$.getJSON('json/expense-accounts').done(function (data) {
- $('input[name="expense_account"]').typeahead({source: data});
+ $('input[name="destination_account_name"]').typeahead({source: data});
+ });
+ }
+
+ // also for multi input
+ if ($('input[name="destination_account_name[]"]').length > 0) {
+ $.getJSON('json/expense-accounts').done(function (data) {
+ $('input[name="destination_account_name[]"]').typeahead({source: data});
});
}
@@ -27,21 +36,54 @@ $(document).ready(function () {
});
}
- if ($('input[name="revenue_account"]').length > 0) {
+ // the source account name is always a revenue account name.
+ if ($('input[name="source_account_name"]').length > 0) {
$.getJSON('json/revenue-accounts').done(function (data) {
- $('input[name="revenue_account"]').typeahead({source: data});
+ $('input[name="source_account_name"]').typeahead({source: data});
});
}
+ // also for multi-input:
+ if ($('input[name="source_account_name[]"]').length > 0) {
+ $.getJSON('json/revenue-accounts').done(function (data) {
+ $('input[name="source_account_name[]"]').typeahead({source: data});
+ });
+ }
+ // and for split:
+ if ($('input[name="journal_source_account_name"]').length > 0) {
+ $.getJSON('json/revenue-accounts').done(function (data) {
+ $('input[name="journal_source_account_name"]').typeahead({source: data});
+ });
+ }
+
if ($('input[name="description"]').length > 0 && what !== undefined) {
$.getJSON('json/transaction-journals/' + what).done(function (data) {
$('input[name="description"]').typeahead({source: data});
});
}
+ // also for multi input:
+ if ($('input[name="description[]"]').length > 0 && what !== undefined) {
+ $.getJSON('json/transaction-journals/' + what).done(function (data) {
+ $('input[name="description[]"]').typeahead({source: data});
+ });
+ }
+ // and for the (rare) journal_description:
+ if ($('input[name="journal_description"]').length > 0 && what !== undefined) {
+ $.getJSON('json/transaction-journals/' + what).done(function (data) {
+ $('input[name="journal_description"]').typeahead({source: data});
+ });
+ }
if ($('input[name="category"]').length > 0) {
$.getJSON('json/categories').done(function (data) {
$('input[name="category"]').typeahead({source: data});
});
}
+
+ // also for multi input:
+ if ($('input[name="category[]"]').length > 0) {
+ $.getJSON('json/categories').done(function (data) {
+ $('input[name="category[]"]').typeahead({source: data});
+ });
+ }
});
\ No newline at end of file
diff --git a/public/js/ff/transactions/create.js b/public/js/ff/transactions/create.js
index f446f19fd1..f3fc98a09c 100644
--- a/public/js/ff/transactions/create.js
+++ b/public/js/ff/transactions/create.js
@@ -33,43 +33,60 @@ function updateForm() {
"use strict";
$('input[name="what"]').val(what);
-
switch (what) {
case 'withdrawal':
- $('#account_id_holder').show();
- $('#expense_account_holder').show();
- $('#revenue_account_holder').hide();
- $('#account_from_id_holder').hide();
- $('#account_to_id_holder').hide();
+ // show source_id and dest_name:
+ $('#source_account_id_holder').show();
+ $('#destination_account_name_holder').show();
+
+ // hide others:
+ $('#source_account_name_holder').hide();
+ $('#destination_account_id_holder').hide();
+
+ // show budget:
$('#budget_id_holder').show();
+
+ // hide piggy bank:
$('#piggy_bank_id_holder').hide();
-
- if ($('#ffInput_revenue_account').val().length > 0) {
- $('#ffInput_expense_account').val($('#ffInput_revenue_account').val());
+ // copy destination account name to
+ // source account name:
+ if ($('#ffInput_destination_account_name').val().length > 0) {
+ $('#ffInput_source_account_name').val($('#ffInput_destination_account_name').val());
}
break;
case 'deposit':
- $('#account_id_holder').show();
- $('#expense_account_holder').hide();
- $('#revenue_account_holder').show();
- $('#account_from_id_holder').hide();
- $('#account_to_id_holder').hide();
+ // show source_name and dest_id:
+ $('#source_account_name_holder').show();
+ $('#destination_account_id_holder').show();
+
+ // hide others:
+ $('#source_account_id_holder').hide();
+ $('#destination_account_name_holder').hide();
+
+ // hide budget
$('#budget_id_holder').hide();
+
+ // hide piggy bank
$('#piggy_bank_id_holder').hide();
- if ($('#ffInput_expense_account').val().length > 0) {
- $('#ffInput_revenue_account').val($('#ffInput_expense_account').val());
+ if ($('#ffInput_source_account_name').val().length > 0) {
+ $('#ffInput_destination_account_name').val($('#ffInput_source_account_name').val());
}
break;
case 'transfer':
- $('#account_id_holder').hide();
- $('#expense_account_holder').hide();
- $('#revenue_account_holder').hide();
- $('#account_from_id_holder').show();
- $('#account_to_id_holder').show();
+ // show source_id and dest_id:
+ $('#source_account_id_holder').show();
+ $('#destination_account_id_holder').show();
+
+ // hide others:
+ $('#source_account_name_holder').hide();
+ $('#destination_account_name_holder').hide();
+
+
+ // hide budget
$('#budget_id_holder').hide();
if (piggiesLength === 0) {
$('#piggy_bank_id_holder').hide();
diff --git a/public/js/lib/Chart.bundle.min.js b/public/js/lib/Chart.bundle.min.js
index bd2f96b501..3b96e43d2c 100755
--- a/public/js/lib/Chart.bundle.min.js
+++ b/public/js/lib/Chart.bundle.min.js
@@ -1,21 +1,16 @@
-!function t(e,i,a){function s(n,r){if(!i[n]){if(!e[n]){var l="function"==typeof require&&require;if(!r&&l)return l(n,!0);if(o)return o(n,!0);var h=new Error("Cannot find module '"+n+"'");throw h.code="MODULE_NOT_FOUND",h}var c=i[n]={exports:{}};e[n][0].call(c.exports,function(t){var i=e[n][1][t];return s(i?i:t)},c,c.exports,t,e,i,a)}return i[n].exports}for(var o="function"==typeof require&&require,n=0;ne&&(e+=360),a=(r+l)/2,i=l==r?0:.5>=a?h/(l+r):h/(2-l-r),[e,100*i,100*a]}function s(t){var e,i,a,s=t[0],o=t[1],n=t[2],r=Math.min(s,o,n),l=Math.max(s,o,n),h=l-r;return i=0==l?0:h/l*1e3/10,l==r?e=0:s==l?e=(o-n)/h:o==l?e=2+(n-s)/h:n==l&&(e=4+(s-o)/h),e=Math.min(60*e,360),0>e&&(e+=360),a=l/255*1e3/10,[e,i,a]}function o(t){var e=t[0],i=t[1],s=t[2],o=a(t)[0],n=1/255*Math.min(e,Math.min(i,s)),s=1-1/255*Math.max(e,Math.max(i,s));return[o,100*n,100*s]}function n(t){var e,i,a,s,o=t[0]/255,n=t[1]/255,r=t[2]/255;return s=Math.min(1-o,1-n,1-r),e=(1-o-s)/(1-s)||0,i=(1-n-s)/(1-s)||0,a=(1-r-s)/(1-s)||0,[100*e,100*i,100*a,100*s]}function l(t){return X[JSON.stringify(t)]}function h(t){var e=t[0]/255,i=t[1]/255,a=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,a=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92;var s=.4124*e+.3576*i+.1805*a,o=.2126*e+.7152*i+.0722*a,n=.0193*e+.1192*i+.9505*a;return[100*s,100*o,100*n]}function c(t){var e,i,a,s=h(t),o=s[0],n=s[1],r=s[2];return o/=95.047,n/=100,r/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*n-16,i=500*(o-n),a=200*(n-r),[e,i,a]}function u(t){return z(c(t))}function d(t){var e,i,a,s,o,n=t[0]/360,r=t[1]/100,l=t[2]/100;if(0==r)return o=255*l,[o,o,o];i=.5>l?l*(1+r):l+r-l*r,e=2*l-i,s=[0,0,0];for(var h=0;3>h;h++)a=n+1/3*-(h-1),0>a&&a++,a>1&&a--,o=1>6*a?e+6*(i-e)*a:1>2*a?i:2>3*a?e+(i-e)*(2/3-a)*6:e,s[h]=255*o;return s}function f(t){var e,i,a=t[0],s=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,s*=1>=o?o:2-o,i=(o+s)/2,e=2*s/(o+s),[a,100*e,100*i])}function m(t){return o(d(t))}function p(t){return n(d(t))}function v(t){return l(d(t))}function x(t){var e=t[0]/60,i=t[1]/100,a=t[2]/100,s=Math.floor(e)%6,o=e-Math.floor(e),n=255*a*(1-i),r=255*a*(1-i*o),l=255*a*(1-i*(1-o)),a=255*a;switch(s){case 0:return[a,l,n];case 1:return[r,a,n];case 2:return[n,a,l];case 3:return[n,r,a];case 4:return[l,n,a];case 5:return[a,n,r]}}function y(t){var e,i,a=t[0],s=t[1]/100,o=t[2]/100;return i=(2-s)*o,e=s*o,e/=1>=i?i:2-i,e=e||0,i/=2,[a,100*e,100*i]}function k(t){return o(x(t))}function _(t){return n(x(t))}function D(t){return l(x(t))}function S(t){var e,i,a,s,o=t[0]/360,n=t[1]/100,l=t[2]/100,h=n+l;switch(h>1&&(n/=h,l/=h),e=Math.floor(6*o),i=1-l,a=6*o-e,0!=(1&e)&&(a=1-a),s=n+a*(i-n),e){default:case 6:case 0:r=i,g=s,b=n;break;case 1:r=s,g=i,b=n;break;case 2:r=n,g=i,b=s;break;case 3:r=n,g=s,b=i;break;case 4:r=s,g=n,b=i;break;case 5:r=i,g=n,b=s}return[255*r,255*g,255*b]}function w(t){return a(S(t))}function C(t){return s(S(t))}function M(t){return n(S(t))}function A(t){return l(S(t))}function I(t){var e,i,a,s=t[0]/100,o=t[1]/100,n=t[2]/100,r=t[3]/100;return e=1-Math.min(1,s*(1-r)+r),i=1-Math.min(1,o*(1-r)+r),a=1-Math.min(1,n*(1-r)+r),[255*e,255*i,255*a]}function T(t){return a(I(t))}function F(t){return s(I(t))}function P(t){return o(I(t))}function O(t){return l(I(t))}function V(t){var e,i,a,s=t[0]/100,o=t[1]/100,n=t[2]/100;return e=3.2406*s+-1.5372*o+n*-.4986,i=s*-.9689+1.8758*o+.0415*n,a=.0557*s+o*-.204+1.057*n,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,a=a>.0031308?1.055*Math.pow(a,1/2.4)-.055:a=12.92*a,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),a=Math.min(Math.max(0,a),1),[255*e,255*i,255*a]}function W(t){var e,i,a,s=t[0],o=t[1],n=t[2];return s/=95.047,o/=100,n/=108.883,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*o-16,i=500*(s-o),a=200*(o-n),[e,i,a]}function L(t){return z(W(t))}function R(t){var e,i,a,s,o=t[0],n=t[1],r=t[2];return 8>=o?(i=100*o/903.3,s=7.787*(i/100)+16/116):(i=100*Math.pow((o+16)/116,3),s=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(n/500+s-16/116)/7.787:95.047*Math.pow(n/500+s,3),a=.008859>=a/108.883?a=108.883*(s-r/200-16/116)/7.787:108.883*Math.pow(s-r/200,3),[e,i,a]}function z(t){var e,i,a,s=t[0],o=t[1],n=t[2];return e=Math.atan2(n,o),i=360*e/2/Math.PI,0>i&&(i+=360),a=Math.sqrt(o*o+n*n),[s,a,i]}function B(t){return V(R(t))}function Y(t){var e,i,a,s=t[0],o=t[1],n=t[2];return a=n/360*2*Math.PI,e=o*Math.cos(a),i=o*Math.sin(a),[s,e,i]}function N(t){return R(Y(t))}function H(t){return B(Y(t))}function E(t){return J[t]}function U(t){return a(E(t))}function j(t){return s(E(t))}function q(t){return o(E(t))}function G(t){return n(E(t))}function Z(t){return c(E(t))}function Q(t){return h(E(t))}e.exports={rgb2hsl:a,rgb2hsv:s,rgb2hwb:o,rgb2cmyk:n,rgb2keyword:l,rgb2xyz:h,rgb2lab:c,rgb2lch:u,hsl2rgb:d,hsl2hsv:f,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:v,hsv2rgb:x,hsv2hsl:y,hsv2hwb:k,hsv2cmyk:_,hsv2keyword:D,hwb2rgb:S,hwb2hsl:w,hwb2hsv:C,hwb2cmyk:M,hwb2keyword:A,cmyk2rgb:I,cmyk2hsl:T,cmyk2hsv:F,cmyk2hwb:P,cmyk2keyword:O,keyword2rgb:E,keyword2hsl:U,keyword2hsv:j,keyword2hwb:q,keyword2cmyk:G,keyword2lab:Z,keyword2xyz:Q,xyz2rgb:V,xyz2lab:W,xyz2lch:L,lab2xyz:R,lab2rgb:B,lab2lch:z,lch2lab:Y,lch2xyz:N,lch2rgb:H};var J={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},X={};for(var $ in J)X[JSON.stringify(J[$])]=$},{}],2:[function(t,e,i){var a=t("./conversions"),s=function(){return new h};for(var o in a){s[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),a[t](e)}}(o);var n=/(\w+)2(\w+)/.exec(o),r=n[1],l=n[2];s[r]=s[r]||{},s[r][l]=s[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=a[t](e);if("string"==typeof i||void 0===i)return i;for(var s=0;se||t[3]&&t[3]<1?u(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function u(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function d(t,e){if(1>e||t[3]&&t[3]<1)return f(t,e);var i=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgb("+i+"%, "+a+"%, "+s+"%)"}function f(t,e){var i=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgba("+i+"%, "+a+"%, "+s+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?m(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function m(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function b(t){return k[t.slice(0,3)]}function v(t,e,i){return Math.min(Math.max(e,t),i)}function x(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y=t("color-name");e.exports={getRgba:a,getHsla:s,getRgb:n,getHsl:r,getHwb:o,getAlpha:l,hexString:h,rgbString:c,rgbaString:u,percentString:d,percentaString:f,hslString:g,hslaString:m,hwbString:p,keyword:b};var k={};for(var _ in y)k[y[_]]=_},{"color-name":4}],4:[function(t,e,i){e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},{}],5:[function(t,e,i){var a=t("color-convert"),s=t("color-string"),o=function(t){if(t instanceof o)return t;if(!(this instanceof o))return new o(t);if(this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},"string"==typeof t){var e=s.getRgba(t);if(e)this.setValues("rgb",e);else if(e=s.getHsla(t))this.setValues("hsl",e);else{if(!(e=s.getHwb(t)))throw new Error('Unable to parse color from string "'+t+'"');this.setValues("hwb",e)}}else if("object"==typeof t){var e=t;if(void 0!==e.r||void 0!==e.red)this.setValues("rgb",e);else if(void 0!==e.l||void 0!==e.lightness)this.setValues("hsl",e);else if(void 0!==e.v||void 0!==e.value)this.setValues("hsv",e);else if(void 0!==e.w||void 0!==e.whiteness)this.setValues("hwb",e);else{if(void 0===e.c&&void 0===e.cyan)throw new Error("Unable to parse color from object "+JSON.stringify(t));this.setValues("cmyk",e)}}};o.prototype={rgb:function(t){return this.setSpace("rgb",arguments)},hsl:function(t){return this.setSpace("hsl",arguments)},hsv:function(t){return this.setSpace("hsv",arguments)},hwb:function(t){return this.setSpace("hwb",arguments)},cmyk:function(t){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){return 1!==this.values.alpha?this.values.hwb.concat([this.values.alpha]):this.values.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values.rgb;return t.concat([this.values.alpha])},hslaArray:function(){var t=this.values.hsl;return t.concat([this.values.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return s.hexString(this.values.rgb)},rgbString:function(){return s.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return s.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return s.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return s.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return s.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return s.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return s.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){return this.values.rgb[0]<<16|this.values.rgb[1]<<8|this.values.rgb[2]},luminosity:function(){for(var t=this.values.rgb,e=[],i=0;i=a?a/12.92:Math.pow((a+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),i=t.luminosity();return e>i?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){return this.values.hsl[2]+=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},darken:function(t){return this.values.hsl[2]-=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},saturate:function(t){return this.values.hsl[1]+=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},desaturate:function(t){return this.values.hsl[1]-=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},whiten:function(t){return this.values.hwb[1]+=this.values.hwb[1]*t,this.setValues("hwb",this.values.hwb),this},blacken:function(t){return this.values.hwb[2]+=this.values.hwb[2]*t,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){return this.setValues("alpha",this.values.alpha-this.values.alpha*t),this},opaquer:function(t){return this.setValues("alpha",this.values.alpha+this.values.alpha*t),this},rotate:function(t){var e=this.values.hsl[0];return e=(e+t)%360,e=0>e?360+e:e,this.values.hsl[0]=e,this.setValues("hsl",this.values.hsl),this},mix:function(t,e){e=1-(null==e?.5:e);for(var i=2*e-1,a=this.alpha()-t.alpha(),s=((i*a==-1?i:(i+a)/(1+i*a))+1)/2,o=1-s,n=this.rgbArray(),r=t.rgbArray(),l=0;l0)for(i in Ri)a=Ri[i],s=e[a],"undefined"!=typeof s&&(t[a]=s);return t}function m(t){g(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),zi===!1&&(zi=!0,i.updateOffset(this),zi=!1)}function p(t){return t instanceof m||null!=t&&null!=t._isAMomentObject}function b(t){return 0>t?Math.ceil(t):Math.floor(t)}function v(t){var e=+t,i=0;return 0!==e&&isFinite(e)&&(i=b(e)),i}function x(t,e,i){var a,s=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),n=0;for(a=0;s>a;a++)(i&&t[a]!==e[a]||!i&&v(t[a])!==v(e[a]))&&n++;return n+o}function y(){}function k(t){return t?t.toLowerCase().replace("_","-"):t}function _(t){for(var e,i,a,s,o=0;o0;){if(a=D(s.slice(0,e).join("-")))return a;if(i&&i.length>=e&&x(s,i,!0)>=e-1)break;e--}o++}return null}function D(i){var a=null;if(!Bi[i]&&"undefined"!=typeof e&&e&&e.exports)try{a=Li._abbr,t("./locale/"+i),S(a)}catch(s){}return Bi[i]}function S(t,e){var i;return t&&(i="undefined"==typeof e?C(t):w(t,e),i&&(Li=i)),Li._abbr}function w(t,e){return null!==e?(e.abbr=t,Bi[t]=Bi[t]||new y,Bi[t].set(e),S(t),Bi[t]):(delete Bi[t],null)}function C(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Li;if(!s(t)){if(e=D(t))return e;t=[t]}return _(t)}function M(t,e){var i=t.toLowerCase();Yi[i]=Yi[i+"s"]=Yi[e]=t}function A(t){return"string"==typeof t?Yi[t]||Yi[t.toLowerCase()]:void 0}function I(t){var e,i,a={};for(i in t)r(t,i)&&(e=A(i),e&&(a[e]=t[i]));return a}function T(t,e){return function(a){return null!=a?(P(this,t,a),i.updateOffset(this,e),this):F(this,t)}}function F(t,e){return t._d["get"+(t._isUTC?"UTC":"")+e]()}function P(t,e,i){return t._d["set"+(t._isUTC?"UTC":"")+e](i)}function O(t,e){var i;if("object"==typeof t)for(i in t)this.set(i,t[i]);else if(t=A(t),"function"==typeof this[t])return this[t](e);return this}function V(t,e,i){var a=""+Math.abs(t),s=e-a.length,o=t>=0;return(o?i?"+":"":"-")+Math.pow(10,Math.max(0,s)).toString().substr(1)+a}function W(t,e,i,a){var s=a;"string"==typeof a&&(s=function(){return this[a]()}),t&&(Ui[t]=s),e&&(Ui[e[0]]=function(){return V(s.apply(this,arguments),e[1],e[2])}),i&&(Ui[i]=function(){return this.localeData().ordinal(s.apply(this,arguments),t)})}function L(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function R(t){var e,i,a=t.match(Ni);for(e=0,i=a.length;i>e;e++)Ui[a[e]]?a[e]=Ui[a[e]]:a[e]=L(a[e]);return function(s){var o="";for(e=0;i>e;e++)o+=a[e]instanceof Function?a[e].call(s,t):a[e];return o}}function z(t,e){return t.isValid()?(e=B(e,t.localeData()),Ei[e]=Ei[e]||R(e),Ei[e](t)):t.localeData().invalidDate()}function B(t,e){function i(t){return e.longDateFormat(t)||t}var a=5;for(Hi.lastIndex=0;a>=0&&Hi.test(t);)t=t.replace(Hi,i),Hi.lastIndex=0,a-=1;return t}function Y(t){return"function"==typeof t&&"[object Function]"===Object.prototype.toString.call(t)}function N(t,e,i){oa[t]=Y(e)?e:function(t){return t&&i?i:e}}function H(t,e){return r(oa,t)?oa[t](e._strict,e._locale):new RegExp(E(t))}function E(t){return t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,a,s){return e||i||a||s}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function U(t,e){var i,a=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(a=function(t,i){i[e]=v(t)}),i=0;ia;a++){if(s=h([2e3,a]),i&&!this._longMonthsParse[a]&&(this._longMonthsParse[a]=new RegExp("^"+this.months(s,"").replace(".","")+"$","i"),this._shortMonthsParse[a]=new RegExp("^"+this.monthsShort(s,"").replace(".","")+"$","i")),i||this._monthsParse[a]||(o="^"+this.months(s,"")+"|^"+this.monthsShort(s,""),this._monthsParse[a]=new RegExp(o.replace(".",""),"i")),i&&"MMMM"===e&&this._longMonthsParse[a].test(t))return a;if(i&&"MMM"===e&&this._shortMonthsParse[a].test(t))return a;if(!i&&this._monthsParse[a].test(t))return a}}function X(t,e){var i;return"string"==typeof e&&(e=t.localeData().monthsParse(e),"number"!=typeof e)?t:(i=Math.min(t.date(),G(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,i),t)}function $(t){return null!=t?(X(this,t),i.updateOffset(this,!0),this):F(this,"Month")}function K(){return G(this.year(),this.month())}function tt(t){var e,i=t._a;return i&&-2===u(t).overflow&&(e=i[la]<0||i[la]>11?la:i[ha]<1||i[ha]>G(i[ra],i[la])?ha:i[ca]<0||i[ca]>24||24===i[ca]&&(0!==i[ua]||0!==i[da]||0!==i[fa])?ca:i[ua]<0||i[ua]>59?ua:i[da]<0||i[da]>59?da:i[fa]<0||i[fa]>999?fa:-1,u(t)._overflowDayOfYear&&(ra>e||e>ha)&&(e=ha),u(t).overflow=e),t}function et(t){i.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function it(t,e){var i=!0;return l(function(){return i&&(et(t+"\n"+(new Error).stack),i=!1),e.apply(this,arguments)},e)}function at(t,e){pa[t]||(et(e),pa[t]=!0)}function st(t){var e,i,a=t._i,s=ba.exec(a);if(s){for(u(t).iso=!0,e=0,i=va.length;i>e;e++)if(va[e][1].exec(a)){t._f=va[e][0];break}for(e=0,i=xa.length;i>e;e++)if(xa[e][1].exec(a)){t._f+=(s[6]||" ")+xa[e][0];break}a.match(ia)&&(t._f+="Z"),Dt(t)}else t._isValid=!1}function ot(t){var e=ya.exec(t._i);return null!==e?void(t._d=new Date(+e[1])):(st(t),void(t._isValid===!1&&(delete t._isValid,i.createFromInputFallback(t))))}function nt(t,e,i,a,s,o,n){var r=new Date(t,e,i,a,s,o,n);return 1970>t&&r.setFullYear(t),r}function rt(t){var e=new Date(Date.UTC.apply(null,arguments));return 1970>t&&e.setUTCFullYear(t),e}function lt(t){return ht(t)?366:365}function ht(t){return t%4===0&&t%100!==0||t%400===0}function ct(){return ht(this.year())}function ut(t,e,i){var a,s=i-e,o=i-t.day();return o>s&&(o-=7),s-7>o&&(o+=7),a=Ft(t).add(o,"d"),{week:Math.ceil(a.dayOfYear()/7),year:a.year()}}function dt(t){return ut(t,this._week.dow,this._week.doy).week}function ft(){return this._week.dow}function gt(){return this._week.doy}function mt(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function pt(t){var e=ut(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function bt(t,e,i,a,s){var o,n=6+s-a,r=rt(t,0,1+n),l=r.getUTCDay();return s>l&&(l+=7),i=null!=i?1*i:s,o=1+n+7*(e-1)-l+i,{year:o>0?t:t-1,dayOfYear:o>0?o:lt(t-1)+o}}function vt(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function xt(t,e,i){return null!=t?t:null!=e?e:i}function yt(t){var e=new Date;return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function kt(t){var e,i,a,s,o=[];if(!t._d){for(a=yt(t),t._w&&null==t._a[ha]&&null==t._a[la]&&_t(t),t._dayOfYear&&(s=xt(t._a[ra],a[ra]),t._dayOfYear>lt(s)&&(u(t)._overflowDayOfYear=!0),i=rt(s,0,t._dayOfYear),t._a[la]=i.getUTCMonth(),t._a[ha]=i.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=a[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[ca]&&0===t._a[ua]&&0===t._a[da]&&0===t._a[fa]&&(t._nextDay=!0,t._a[ca]=0),t._d=(t._useUTC?rt:nt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[ca]=24)}}function _t(t){var e,i,a,s,o,n,r;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,n=4,i=xt(e.GG,t._a[ra],ut(Ft(),1,4).year),a=xt(e.W,1),s=xt(e.E,1)):(o=t._locale._week.dow,n=t._locale._week.doy,i=xt(e.gg,t._a[ra],ut(Ft(),o,n).year),a=xt(e.w,1),null!=e.d?(s=e.d,o>s&&++a):s=null!=e.e?e.e+o:o),r=bt(i,a,s,n,o),t._a[ra]=r.year,t._dayOfYear=r.dayOfYear}function Dt(t){if(t._f===i.ISO_8601)return void st(t);t._a=[],u(t).empty=!0;var e,a,s,o,n,r=""+t._i,l=r.length,h=0;for(s=B(t._f,t._locale).match(Ni)||[],e=0;e0&&u(t).unusedInput.push(n),r=r.slice(r.indexOf(a)+a.length),h+=a.length),Ui[o]?(a?u(t).empty=!1:u(t).unusedTokens.push(o),q(o,a,t)):t._strict&&!a&&u(t).unusedTokens.push(o);u(t).charsLeftOver=l-h,r.length>0&&u(t).unusedInput.push(r),u(t).bigHour===!0&&t._a[ca]<=12&&t._a[ca]>0&&(u(t).bigHour=void 0),t._a[ca]=St(t._locale,t._a[ca],t._meridiem),kt(t),tt(t)}function St(t,e,i){var a;return null==i?e:null!=t.meridiemHour?t.meridiemHour(e,i):null!=t.isPM?(a=t.isPM(i),a&&12>e&&(e+=12),a||12!==e||(e=0),e):e}function wt(t){var e,i,a,s,o;if(0===t._f.length)return u(t).invalidFormat=!0,void(t._d=new Date(NaN));for(s=0;so)&&(a=o,i=e));l(t,i||e)}function Ct(t){if(!t._d){var e=I(t._i);t._a=[e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],kt(t)}}function Mt(t){var e=new m(tt(At(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function At(t){var e=t._i,i=t._f;return t._locale=t._locale||C(t._l),null===e||void 0===i&&""===e?f({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),p(e)?new m(tt(e)):(s(i)?wt(t):i?Dt(t):o(e)?t._d=e:It(t),t))}function It(t){var e=t._i;void 0===e?t._d=new Date:o(e)?t._d=new Date(+e):"string"==typeof e?ot(t):s(e)?(t._a=n(e.slice(0),function(t){return parseInt(t,10)}),kt(t)):"object"==typeof e?Ct(t):"number"==typeof e?t._d=new Date(e):i.createFromInputFallback(t)}function Tt(t,e,i,a,s){var o={};return"boolean"==typeof i&&(a=i,i=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=s,o._l=i,o._i=t,o._f=e,o._strict=a,Mt(o)}function Ft(t,e,i,a){return Tt(t,e,i,a,!1)}function Pt(t,e){var i,a;if(1===e.length&&s(e[0])&&(e=e[0]),!e.length)return Ft();for(i=e[0],a=1;at&&(t=-t,i="-"),i+V(~~(t/60),2)+e+V(~~t%60,2)})}function zt(t){var e=(t||"").match(ia)||[],i=e[e.length-1]||[],a=(i+"").match(wa)||["-",0,0],s=+(60*a[1])+v(a[2]);return"+"===a[0]?s:-s}function Bt(t,e){var a,s;return e._isUTC?(a=e.clone(),s=(p(t)||o(t)?+t:+Ft(t))-+a,a._d.setTime(+a._d+s),i.updateOffset(a,!1),a):Ft(t).local()}function Yt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Nt(t,e){var a,s=this._offset||0;return null!=t?("string"==typeof t&&(t=zt(t)),Math.abs(t)<16&&(t=60*t),!this._isUTC&&e&&(a=Yt(this)),this._offset=t,this._isUTC=!0,null!=a&&this.add(a,"m"),s!==t&&(!e||this._changeInProgress?ae(this,$t(t-s,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,i.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?s:Yt(this)}function Ht(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Et(t){return this.utcOffset(0,t)}function Ut(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Yt(this),"m")),this}function jt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(zt(this._i)),this}function qt(t){return t=t?Ft(t).utcOffset():0,(this.utcOffset()-t)%60===0}function Gt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Zt(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var t={};if(g(t,this),t=At(t),t._a){var e=t._isUTC?h(t._a):Ft(t._a);this._isDSTShifted=this.isValid()&&x(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Qt(){return!this._isUTC}function Jt(){return this._isUTC}function Xt(){return this._isUTC&&0===this._offset}function $t(t,e){var i,a,s,o=t,n=null;return Lt(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(n=Ca.exec(t))?(i="-"===n[1]?-1:1,o={y:0,d:v(n[ha])*i,h:v(n[ca])*i,m:v(n[ua])*i,s:v(n[da])*i,ms:v(n[fa])*i}):(n=Ma.exec(t))?(i="-"===n[1]?-1:1,o={y:Kt(n[2],i),M:Kt(n[3],i),d:Kt(n[4],i),h:Kt(n[5],i),m:Kt(n[6],i),s:Kt(n[7],i),w:Kt(n[8],i)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(s=ee(Ft(o.from),Ft(o.to)),o={},o.ms=s.milliseconds,o.M=s.months),a=new Wt(o),Lt(t)&&r(t,"_locale")&&(a._locale=t._locale),a}function Kt(t,e){var i=t&&parseFloat(t.replace(",","."));return(isNaN(i)?0:i)*e}function te(t,e){var i={milliseconds:0,months:0};return i.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(i.months,"M").isAfter(e)&&--i.months,i.milliseconds=+e-+t.clone().add(i.months,"M"),i}function ee(t,e){var i;return e=Bt(e,t),t.isBefore(e)?i=te(t,e):(i=te(e,t),i.milliseconds=-i.milliseconds,i.months=-i.months),i}function ie(t,e){return function(i,a){var s,o;return null===a||isNaN(+a)||(at(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=i,i=a,a=o),i="string"==typeof i?+i:i,s=$t(i,a),ae(this,s,t),this}}function ae(t,e,a,s){var o=e._milliseconds,n=e._days,r=e._months;s=null==s?!0:s,o&&t._d.setTime(+t._d+o*a),n&&P(t,"Date",F(t,"Date")+n*a),r&&X(t,F(t,"Month")+r*a),s&&i.updateOffset(t,n||r)}function se(t,e){var i=t||Ft(),a=Bt(i,this).startOf("day"),s=this.diff(a,"days",!0),o=-6>s?"sameElse":-1>s?"lastWeek":0>s?"lastDay":1>s?"sameDay":2>s?"nextDay":7>s?"nextWeek":"sameElse";return this.format(e&&e[o]||this.localeData().calendar(o,this,Ft(i)))}function oe(){return new m(this)}function ne(t,e){var i;return e=A("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=p(t)?t:Ft(t),+this>+t):(i=p(t)?+t:+Ft(t),i<+this.clone().startOf(e))}function re(t,e){var i;return e=A("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=p(t)?t:Ft(t),+t>+this):(i=p(t)?+t:+Ft(t),+this.clone().endOf(e)e-o?(i=t.clone().add(s-1,"months"),a=(e-o)/(o-i)):(i=t.clone().add(s+1,"months"),a=(e-o)/(i-o)),-(s+a)}function de(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function fe(){var t=this.clone().utc();return 0e;e++)if(this._weekdaysParse[e]||(i=Ft([2e3,1]).day(e),a="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[e]=new RegExp(a.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e}function Ee(t){var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=ze(t,this.localeData()),this.add(t-e,"d")):e}function Ue(t){var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function je(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)}function qe(t,e){W(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function Ge(t,e){return e._meridiemParse}function Ze(t){return"p"===(t+"").toLowerCase().charAt(0)}function Qe(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"}function Je(t,e){e[fa]=v(1e3*("0."+t))}function Xe(){return this._isUTC?"UTC":""}function $e(){return this._isUTC?"Coordinated Universal Time":""}function Ke(t){return Ft(1e3*t)}function ti(){return Ft.apply(null,arguments).parseZone()}function ei(t,e,i){var a=this._calendar[t];return"function"==typeof a?a.call(e,i):a}function ii(t){var e=this._longDateFormat[t],i=this._longDateFormat[t.toUpperCase()];return e||!i?e:(this._longDateFormat[t]=i.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function ai(){return this._invalidDate}function si(t){return this._ordinal.replace("%d",t)}function oi(t){return t}function ni(t,e,i,a){var s=this._relativeTime[i];return"function"==typeof s?s(t,e,i,a):s.replace(/%d/i,t)}function ri(t,e){var i=this._relativeTime[t>0?"future":"past"];return"function"==typeof i?i(e):i.replace(/%s/i,e)}function li(t){var e,i;for(i in t)e=t[i],"function"==typeof e?this[i]=e:this["_"+i]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function hi(t,e,i,a){var s=C(),o=h().set(a,e);return s[i](o,t)}function ci(t,e,i,a,s){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return hi(t,e,i,s);var o,n=[];for(o=0;a>o;o++)n[o]=hi(t,o,i,s);return n}function ui(t,e){return ci(t,e,"months",12,"month")}function di(t,e){return ci(t,e,"monthsShort",12,"month")}function fi(t,e){return ci(t,e,"weekdays",7,"day")}function gi(t,e){return ci(t,e,"weekdaysShort",7,"day")}function mi(t,e){return ci(t,e,"weekdaysMin",7,"day")}function pi(){var t=this._data;return this._milliseconds=Ja(this._milliseconds),this._days=Ja(this._days),this._months=Ja(this._months),t.milliseconds=Ja(t.milliseconds),t.seconds=Ja(t.seconds),t.minutes=Ja(t.minutes),t.hours=Ja(t.hours),t.months=Ja(t.months),t.years=Ja(t.years),this}function bi(t,e,i,a){var s=$t(e,i);return t._milliseconds+=a*s._milliseconds,t._days+=a*s._days,t._months+=a*s._months,t._bubble()}function vi(t,e){return bi(this,t,e,1)}function xi(t,e){return bi(this,t,e,-1)}function yi(t){return 0>t?Math.floor(t):Math.ceil(t)}function ki(){var t,e,i,a,s,o=this._milliseconds,n=this._days,r=this._months,l=this._data;return o>=0&&n>=0&&r>=0||0>=o&&0>=n&&0>=r||(o+=864e5*yi(Di(r)+n),n=0,r=0),l.milliseconds=o%1e3,t=b(o/1e3),l.seconds=t%60,e=b(t/60),l.minutes=e%60,i=b(e/60),l.hours=i%24,n+=b(i/24),s=b(_i(n)),r+=s,n-=yi(Di(s)),a=b(r/12),r%=12,l.days=n,l.months=r,l.years=a,this}function _i(t){return 4800*t/146097}function Di(t){return 146097*t/4800}function Si(t){var e,i,a=this._milliseconds;if(t=A(t),"month"===t||"year"===t)return e=this._days+a/864e5,i=this._months+_i(e),"month"===t?i:i/12;switch(e=this._days+Math.round(Di(this._months)),t){case"week":return e/7+a/6048e5;case"day":return e+a/864e5;case"hour":return 24*e+a/36e5;case"minute":return 1440*e+a/6e4;case"second":return 86400*e+a/1e3;case"millisecond":return Math.floor(864e5*e)+a;default:throw new Error("Unknown unit "+t)}}function wi(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*v(this._months/12)}function Ci(t){return function(){return this.as(t)}}function Mi(t){return t=A(t),this[t+"s"]()}function Ai(t){return function(){return this._data[t]}}function Ii(){return b(this.days()/7)}function Ti(t,e,i,a,s){return s.relativeTime(e||1,!!i,t,a)}function Fi(t,e,i){var a=$t(t).abs(),s=ds(a.as("s")),o=ds(a.as("m")),n=ds(a.as("h")),r=ds(a.as("d")),l=ds(a.as("M")),h=ds(a.as("y")),c=s0,c[4]=i,Ti.apply(null,c)}function Pi(t,e){return void 0===fs[t]?!1:void 0===e?fs[t]:(fs[t]=e,!0)}function Oi(t){var e=this.localeData(),i=Fi(this,!t,e);return t&&(i=e.pastFuture(+this,i)),e.postformat(i)}function Vi(){var t,e,i,a=gs(this._milliseconds)/1e3,s=gs(this._days),o=gs(this._months);t=b(a/60),e=b(t/60),a%=60,t%=60,i=b(o/12),o%=12;var n=i,r=o,l=s,h=e,c=t,u=a,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(n?n+"Y":"")+(r?r+"M":"")+(l?l+"D":"")+(h||c||u?"T":"")+(h?h+"H":"")+(c?c+"M":"")+(u?u+"S":""):"P0D"}var Wi,Li,Ri=i.momentProperties=[],zi=!1,Bi={},Yi={},Ni=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Hi=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ei={},Ui={},ji=/\d/,qi=/\d\d/,Gi=/\d{3}/,Zi=/\d{4}/,Qi=/[+-]?\d{6}/,Ji=/\d\d?/,Xi=/\d{1,3}/,$i=/\d{1,4}/,Ki=/[+-]?\d{1,6}/,ta=/\d+/,ea=/[+-]?\d+/,ia=/Z|[+-]\d\d:?\d\d/gi,aa=/[+-]?\d+(\.\d{1,3})?/,sa=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,oa={},na={},ra=0,la=1,ha=2,ca=3,ua=4,da=5,fa=6;W("M",["MM",2],"Mo",function(){return this.month()+1}),W("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),W("MMMM",0,0,function(t){return this.localeData().months(this,t)}),M("month","M"),N("M",Ji),N("MM",Ji,qi),N("MMM",sa),N("MMMM",sa),U(["M","MM"],function(t,e){e[la]=v(t)-1}),U(["MMM","MMMM"],function(t,e,i,a){var s=i._locale.monthsParse(t,a,i._strict);null!=s?e[la]=s:u(i).invalidMonth=t});var ga="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ma="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),pa={};i.suppressDeprecationWarnings=!1;var ba=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,va=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],xa=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ya=/^\/?Date\((\-?\d+)/i;i.createFromInputFallback=it("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),W(0,["YY",2],0,function(){return this.year()%100}),W(0,["YYYY",4],0,"year"),W(0,["YYYYY",5],0,"year"),W(0,["YYYYYY",6,!0],0,"year"),M("year","y"),N("Y",ea),N("YY",Ji,qi),N("YYYY",$i,Zi),N("YYYYY",Ki,Qi),N("YYYYYY",Ki,Qi),U(["YYYYY","YYYYYY"],ra),U("YYYY",function(t,e){e[ra]=2===t.length?i.parseTwoDigitYear(t):v(t)}),U("YY",function(t,e){e[ra]=i.parseTwoDigitYear(t)}),i.parseTwoDigitYear=function(t){return v(t)+(v(t)>68?1900:2e3)};var ka=T("FullYear",!1);W("w",["ww",2],"wo","week"),W("W",["WW",2],"Wo","isoWeek"),M("week","w"),M("isoWeek","W"),N("w",Ji),N("ww",Ji,qi),N("W",Ji),N("WW",Ji,qi),j(["w","ww","W","WW"],function(t,e,i,a){e[a.substr(0,1)]=v(t)});var _a={dow:0,doy:6};W("DDD",["DDDD",3],"DDDo","dayOfYear"),M("dayOfYear","DDD"),N("DDD",Xi),N("DDDD",Gi),U(["DDD","DDDD"],function(t,e,i){i._dayOfYear=v(t)}),i.ISO_8601=function(){};var Da=it("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Ft.apply(null,arguments);return this>t?this:t}),Sa=it("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Ft.apply(null,arguments);return t>this?this:t});Rt("Z",":"),Rt("ZZ",""),N("Z",ia),N("ZZ",ia),U(["Z","ZZ"],function(t,e,i){i._useUTC=!0,i._tzm=zt(t)});var wa=/([\+\-]|\d\d)/gi;i.updateOffset=function(){};var Ca=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Ma=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;$t.fn=Wt.prototype;var Aa=ie(1,"add"),Ia=ie(-1,"subtract");i.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Ta=it("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});W(0,["gg",2],0,function(){return this.weekYear()%100}),W(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Fe("gggg","weekYear"),Fe("ggggg","weekYear"),Fe("GGGG","isoWeekYear"),Fe("GGGGG","isoWeekYear"),M("weekYear","gg"),M("isoWeekYear","GG"),N("G",ea),N("g",ea),N("GG",Ji,qi),N("gg",Ji,qi),N("GGGG",$i,Zi),N("gggg",$i,Zi),N("GGGGG",Ki,Qi),N("ggggg",Ki,Qi),j(["gggg","ggggg","GGGG","GGGGG"],function(t,e,i,a){e[a.substr(0,2)]=v(t)}),j(["gg","GG"],function(t,e,a,s){e[s]=i.parseTwoDigitYear(t)}),W("Q",0,0,"quarter"),M("quarter","Q"),N("Q",ji),U("Q",function(t,e){e[la]=3*(v(t)-1)}),W("D",["DD",2],"Do","date"),M("date","D"),N("D",Ji),N("DD",Ji,qi),N("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),U(["D","DD"],ha),U("Do",function(t,e){e[ha]=v(t.match(Ji)[0],10)});var Fa=T("Date",!0);W("d",0,"do","day"),W("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),W("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),W("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),W("e",0,0,"weekday"),W("E",0,0,"isoWeekday"),M("day","d"),M("weekday","e"),M("isoWeekday","E"),N("d",Ji),N("e",Ji),N("E",Ji),N("dd",sa),N("ddd",sa),N("dddd",sa),j(["dd","ddd","dddd"],function(t,e,i){var a=i._locale.weekdaysParse(t);null!=a?e.d=a:u(i).invalidWeekday=t}),j(["d","e","E"],function(t,e,i,a){e[a]=v(t)});var Pa="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Oa="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Va="Su_Mo_Tu_We_Th_Fr_Sa".split("_");W("H",["HH",2],0,"hour"),W("h",["hh",2],0,function(){return this.hours()%12||12}),qe("a",!0),qe("A",!1),M("hour","h"),N("a",Ge),N("A",Ge),N("H",Ji),N("h",Ji),N("HH",Ji,qi),N("hh",Ji,qi),U(["H","HH"],ca),U(["a","A"],function(t,e,i){i._isPm=i._locale.isPM(t),i._meridiem=t}),U(["h","hh"],function(t,e,i){e[ca]=v(t),u(i).bigHour=!0});var Wa=/[ap]\.?m?\.?/i,La=T("Hours",!0);W("m",["mm",2],0,"minute"),M("minute","m"),N("m",Ji),N("mm",Ji,qi),U(["m","mm"],ua);var Ra=T("Minutes",!1);W("s",["ss",2],0,"second"),M("second","s"),N("s",Ji),N("ss",Ji,qi),U(["s","ss"],da);var za=T("Seconds",!1);W("S",0,0,function(){return~~(this.millisecond()/100)}),W(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),W(0,["SSS",3],0,"millisecond"),W(0,["SSSS",4],0,function(){return 10*this.millisecond()}),W(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),W(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),W(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),W(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),W(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),M("millisecond","ms"),N("S",Xi,ji),N("SS",Xi,qi),N("SSS",Xi,Gi);var Ba;for(Ba="SSSS";Ba.length<=9;Ba+="S")N(Ba,ta);for(Ba="S";Ba.length<=9;Ba+="S")U(Ba,Je);var Ya=T("Milliseconds",!1);W("z",0,0,"zoneAbbr"),W("zz",0,0,"zoneName");var Na=m.prototype;Na.add=Aa,Na.calendar=se,Na.clone=oe,Na.diff=ce,Na.endOf=_e,Na.format=ge,Na.from=me,Na.fromNow=pe,Na.to=be,Na.toNow=ve,Na.get=O,Na.invalidAt=Te,Na.isAfter=ne,Na.isBefore=re,Na.isBetween=le,Na.isSame=he,Na.isValid=Ae,Na.lang=Ta,Na.locale=xe,Na.localeData=ye,Na.max=Sa,Na.min=Da,Na.parsingFlags=Ie,Na.set=O,Na.startOf=ke,Na.subtract=Ia,Na.toArray=Ce,Na.toObject=Me,Na.toDate=we,Na.toISOString=fe,Na.toJSON=fe,Na.toString=de,Na.unix=Se,Na.valueOf=De,Na.year=ka,Na.isLeapYear=ct,Na.weekYear=Oe,Na.isoWeekYear=Ve,Na.quarter=Na.quarters=Re,Na.month=$,Na.daysInMonth=K,Na.week=Na.weeks=mt,Na.isoWeek=Na.isoWeeks=pt,Na.weeksInYear=Le,Na.isoWeeksInYear=We,Na.date=Fa,Na.day=Na.days=Ee,Na.weekday=Ue,Na.isoWeekday=je,Na.dayOfYear=vt,Na.hour=Na.hours=La,Na.minute=Na.minutes=Ra,Na.second=Na.seconds=za,Na.millisecond=Na.milliseconds=Ya,
-Na.utcOffset=Nt,Na.utc=Et,Na.local=Ut,Na.parseZone=jt,Na.hasAlignedHourOffset=qt,Na.isDST=Gt,Na.isDSTShifted=Zt,Na.isLocal=Qt,Na.isUtcOffset=Jt,Na.isUtc=Xt,Na.isUTC=Xt,Na.zoneAbbr=Xe,Na.zoneName=$e,Na.dates=it("dates accessor is deprecated. Use date instead.",Fa),Na.months=it("months accessor is deprecated. Use month instead",$),Na.years=it("years accessor is deprecated. Use year instead",ka),Na.zone=it("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Ht);var Ha=Na,Ea={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Ua={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},ja="Invalid date",qa="%d",Ga=/\d{1,2}/,Za={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Qa=y.prototype;Qa._calendar=Ea,Qa.calendar=ei,Qa._longDateFormat=Ua,Qa.longDateFormat=ii,Qa._invalidDate=ja,Qa.invalidDate=ai,Qa._ordinal=qa,Qa.ordinal=si,Qa._ordinalParse=Ga,Qa.preparse=oi,Qa.postformat=oi,Qa._relativeTime=Za,Qa.relativeTime=ni,Qa.pastFuture=ri,Qa.set=li,Qa.months=Z,Qa._months=ga,Qa.monthsShort=Q,Qa._monthsShort=ma,Qa.monthsParse=J,Qa.week=dt,Qa._week=_a,Qa.firstDayOfYear=gt,Qa.firstDayOfWeek=ft,Qa.weekdays=Be,Qa._weekdays=Pa,Qa.weekdaysMin=Ne,Qa._weekdaysMin=Va,Qa.weekdaysShort=Ye,Qa._weekdaysShort=Oa,Qa.weekdaysParse=He,Qa.isPM=Ze,Qa._meridiemParse=Wa,Qa.meridiem=Qe,S("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,i=1===v(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+i}}),i.lang=it("moment.lang is deprecated. Use moment.locale instead.",S),i.langData=it("moment.langData is deprecated. Use moment.localeData instead.",C);var Ja=Math.abs,Xa=Ci("ms"),$a=Ci("s"),Ka=Ci("m"),ts=Ci("h"),es=Ci("d"),is=Ci("w"),as=Ci("M"),ss=Ci("y"),os=Ai("milliseconds"),ns=Ai("seconds"),rs=Ai("minutes"),ls=Ai("hours"),hs=Ai("days"),cs=Ai("months"),us=Ai("years"),ds=Math.round,fs={s:45,m:45,h:22,d:26,M:11},gs=Math.abs,ms=Wt.prototype;ms.abs=pi,ms.add=vi,ms.subtract=xi,ms.as=Si,ms.asMilliseconds=Xa,ms.asSeconds=$a,ms.asMinutes=Ka,ms.asHours=ts,ms.asDays=es,ms.asWeeks=is,ms.asMonths=as,ms.asYears=ss,ms.valueOf=wi,ms._bubble=ki,ms.get=Mi,ms.milliseconds=os,ms.seconds=ns,ms.minutes=rs,ms.hours=ls,ms.days=hs,ms.weeks=Ii,ms.months=cs,ms.years=us,ms.humanize=Oi,ms.toISOString=Vi,ms.toString=Vi,ms.toJSON=Vi,ms.locale=xe,ms.localeData=ye,ms.toIsoString=it("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Vi),ms.lang=Ta,W("X",0,0,"unix"),W("x",0,0,"valueOf"),N("x",ea),N("X",aa),U("X",function(t,e,i){i._d=new Date(1e3*parseFloat(t,10))}),U("x",function(t,e,i){i._d=new Date(v(t))}),i.version="2.10.6",a(Ft),i.fn=Ha,i.min=Ot,i.max=Vt,i.utc=h,i.unix=Ke,i.months=ui,i.isDate=o,i.locale=S,i.invalid=f,i.duration=$t,i.isMoment=p,i.weekdays=fi,i.parseZone=ti,i.localeData=C,i.isDuration=Lt,i.monthsShort=di,i.weekdaysMin=mi,i.defineLocale=w,i.weekdaysShort=gi,i.normalizeUnits=A,i.relativeTimeThreshold=Pi;var ps=i;return ps})},{}],7:[function(t,e,i){/*!
+/*!
* Chart.js
* http://chartjs.org/
- * Version: 2.0.2
+ * Version: 2.1.3
*
- * Copyright 2015 Nick Downie
+ * Copyright 2016 Nick Downie
* Released under the MIT license
- * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
*/
-var a=t("./core/core.js")();t("./core/core.helpers")(a),t("./core/core.element")(a),t("./core/core.animation")(a),t("./core/core.controller")(a),t("./core/core.datasetController")(a),t("./core/core.layoutService")(a),t("./core/core.legend")(a),t("./core/core.scale")(a),t("./core/core.scaleService")(a),t("./core/core.title")(a),t("./core/core.tooltip")(a),t("./controllers/controller.bar")(a),t("./controllers/controller.bubble")(a),t("./controllers/controller.doughnut")(a),t("./controllers/controller.line")(a),t("./controllers/controller.polarArea")(a),t("./controllers/controller.radar")(a),t("./scales/scale.category")(a),t("./scales/scale.linear")(a),t("./scales/scale.logarithmic")(a),t("./scales/scale.radialLinear")(a),t("./scales/scale.time")(a),t("./elements/element.arc")(a),t("./elements/element.line")(a),t("./elements/element.point")(a),t("./elements/element.rectangle")(a),t("./charts/Chart.Bar")(a),t("./charts/Chart.Bubble")(a),t("./charts/Chart.Doughnut")(a),t("./charts/Chart.Line")(a),t("./charts/Chart.PolarArea")(a),t("./charts/Chart.Radar")(a),t("./charts/Chart.Scatter")(a),window.Chart=e.exports=a},{"./charts/Chart.Bar":8,"./charts/Chart.Bubble":9,"./charts/Chart.Doughnut":10,"./charts/Chart.Line":11,"./charts/Chart.PolarArea":12,"./charts/Chart.Radar":13,"./charts/Chart.Scatter":14,"./controllers/controller.bar":15,"./controllers/controller.bubble":16,"./controllers/controller.doughnut":17,"./controllers/controller.line":18,"./controllers/controller.polarArea":19,"./controllers/controller.radar":20,"./core/core.animation":21,"./core/core.controller":22,"./core/core.datasetController":23,"./core/core.element":24,"./core/core.helpers":25,"./core/core.js":26,"./core/core.layoutService":27,"./core/core.legend":28,"./core/core.scale":29,"./core/core.scaleService":30,"./core/core.title":31,"./core/core.tooltip":32,"./elements/element.arc":33,"./elements/element.line":34,"./elements/element.point":35,"./elements/element.rectangle":36,"./scales/scale.category":37,"./scales/scale.linear":38,"./scales/scale.logarithmic":39,"./scales/scale.radialLinear":40,"./scales/scale.time":41}],8:[function(t,e,i){"use strict";e.exports=function(t){t.Bar=function(e,i){return i.type="bar",new t(e,i)}}},{}],9:[function(t,e,i){"use strict";e.exports=function(t){t.Bubble=function(e,i){return i.type="bubble",new t(e,i)}}},{}],10:[function(t,e,i){"use strict";e.exports=function(t){t.Doughnut=function(e,i){return i.type="doughnut",new t(e,i)}}},{}],11:[function(t,e,i){"use strict";e.exports=function(t){t.Line=function(e,i){return i.type="line",new t(e,i)}}},{}],12:[function(t,e,i){"use strict";e.exports=function(t){t.PolarArea=function(e,i){return i.type="polarArea",new t(e,i)}}},{}],13:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={aspectRatio:1};t.Radar=function(a,s){return s.options=e.configMerge(i,s.options),s.type="radar",new t(a,s)}}},{}],14:[function(t,e,i){"use strict";e.exports=function(t){var e={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-1"}],yAxes:[{type:"linear",position:"left",id:"y-axis-1"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){return"("+t.xLabel+", "+t.yLabel+")"}}}};t.defaults.scatter=e,t.controllers.scatter=t.controllers.line,t.Scatter=function(e,i){return i.type="scatter",new t(e,i)}}},{}],15:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bar={hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}},t.controllers.bar=t.DatasetController.extend({initialize:function(e,i){t.DatasetController.prototype.initialize.call(this,e,i),this.getDataset().bar=!0},getBarCount:function(){var t=0;return e.each(this.chart.data.datasets,function(i){e.isDatasetVisible(i)&&i.bar&&++t}),t},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:e}),a=this.getBarCount();this.updateElement(i,e,!0,a),this.getDataset().metaData.splice(e,0,i)},update:function(t){var i=this.getBarCount();e.each(this.getDataset().metaData,function(e,a){this.updateElement(e,a,t,i)},this)},updateElement:function(t,i,a,s){var o,n=this.getScaleForId(this.getDataset().xAxisID),r=this.getScaleForId(this.getDataset().yAxisID);o=r.min<0&&r.max<0?r.getPixelForValue(r.max):r.min>0&&r.max>0?r.getPixelForValue(r.min):r.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:n,_yScale:r,_datasetIndex:this.index,_index:i,_model:{x:this.calculateBarX(i,this.index),y:a?o:this.calculateBarY(i,this.index),label:this.chart.data.labels[i],datasetLabel:this.getDataset().label,base:a?o:this.calculateBarBase(this.index,i),width:this.calculateBarWidth(s),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),borderSkipped:t.custom&&t.custom.borderSkipped?t.custom.borderSkipped:this.chart.options.elements.rectangle.borderSkipped,borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}}),t.pivot()},calculateBarBase:function(t,i){var a=(this.getScaleForId(this.getDataset().xAxisID),this.getScaleForId(this.getDataset().yAxisID)),s=0;if(a.options.stacked){var o=this.chart.data.datasets[t].data[i];if(0>o)for(var n=0;t>n;n++){var r=this.chart.data.datasets[n];e.isDatasetVisible(r)&&r.yAxisID===a.id&&r.bar&&(s+=r.data[i]<0?r.data[i]:0)}else for(var l=0;t>l;l++){var h=this.chart.data.datasets[l];e.isDatasetVisible(h)&&h.yAxisID===a.id&&h.bar&&(s+=h.data[i]>0?h.data[i]:0)}return a.getPixelForValue(s)}return s=a.getPixelForValue(a.min),a.beginAtZero||a.min<=0&&a.max>=0||a.min>=0&&a.max<=0?s=a.getPixelForValue(0,0):a.min<0&&a.max<0&&(s=a.getPixelForValue(a.max)),s},getRuler:function(){var t=this.getScaleForId(this.getDataset().xAxisID),e=(this.getScaleForId(this.getDataset().yAxisID),this.getBarCount()),i=function(){for(var e=t.getPixelForTick(1)-t.getPixelForTick(0),i=2;ia;++a)e.isDatasetVisible(this.chart.data.datasets[a])&&this.chart.data.datasets[a].bar&&++i;return i},calculateBarX:function(t,e){var i=(this.getScaleForId(this.getDataset().yAxisID),this.getScaleForId(this.getDataset().xAxisID)),a=this.getBarIndex(e),s=this.getRuler(),o=i.getPixelForValue(null,t,e,this.chart.isCombo);return o-=this.chart.isCombo?s.tickWidth/2:0,i.options.stacked?o+s.categoryWidth/2+s.categorySpacing:o+s.barWidth/2+s.categorySpacing+s.barWidth*a+s.barSpacing/2+s.barSpacing*a},calculateBarY:function(t,i){var a=(this.getScaleForId(this.getDataset().xAxisID),this.getScaleForId(this.getDataset().yAxisID)),s=this.getDataset().data[t];if(a.options.stacked){for(var o=0,n=0,r=0;i>r;r++){var l=this.chart.data.datasets[r];e.isDatasetVisible(l)&&l.bar&&l.yAxisID===a.id&&(l.data[t]<0?n+=l.data[t]||0:o+=l.data[t]||0)}return 0>s?a.getPixelForValue(n+s):a.getPixelForValue(o+s)}return a.getPixelForValue(s)},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){var a=this.getDataset().data[e];null===a||void 0===a||isNaN(a)||t.transition(i).draw()},this)},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}})}},{}],16:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bubble={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){var i=e.datasets[t.datasetIndex].label||"",a=e.datasets[t.datasetIndex].data[t.index];return i+": ("+a.x+", "+a.y+", "+a.r+")"}}}},t.controllers.bubble=t.DatasetController.extend({addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i)},update:function(t){var i,a=this.getDataset().metaData,s=this.getScaleForId(this.getDataset().yAxisID);this.getScaleForId(this.getDataset().xAxisID);i=s.min<0&&s.max<0?s.getPixelForValue(s.max):s.min>0&&s.max>0?s.getPixelForValue(s.min):s.getPixelForValue(0),e.each(a,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,a){var s,o=this.getScaleForId(this.getDataset().yAxisID),n=this.getScaleForId(this.getDataset().xAxisID);s=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:n,_yScale:o,_datasetIndex:this.index,_index:i,_model:{x:a?n.getPixelForDecimal(.5):n.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:a?s:o.getPixelForValue(this.getDataset().data[i],i,this.index),radius:a?0:t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[i]),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y),t.pivot()},getRadius:function(t){return t.r||this.chart.options.elements.point.radius},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i),t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.hoverRadius,a,this.chart.options.elements.point.hoverRadius)+this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],17:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.doughnut={animation:{animateRotate:!0,animateScale:!1},aspectRatio:1,hover:{mode:"single"},legendCallback:function(t){var e=[];if(e.push(''),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){return t.labels.length&&t.datasets.length?t.labels.map(function(e,i){return{text:e,fillStyle:t.datasets[0].backgroundColor[i],hidden:isNaN(t.datasets[0].data[i]),index:i}}):[]}},onClick:function(t,i){e.each(this.chart.data.datasets,function(t){t.metaHiddenData=t.metaHiddenData||[];var e=i.index;isNaN(t.data[e])?isNaN(t.metaHiddenData[e])||(t.data[e]=t.metaHiddenData[e]):(t.metaHiddenData[e]=t.data[e],t.data[e]=NaN)}),this.chart.update()}},cutoutPercentage:50,rotation:Math.PI*-.5,circumference:2*Math.PI,tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+e.datasets[t.datasetIndex].data[t.index]}}}},t.defaults.pie=e.clone(t.defaults.doughnut),e.extend(t.defaults.pie,{cutoutPercentage:0}),t.controllers.doughnut=t.controllers.pie=t.DatasetController.extend({linkScales:function(){},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(i,a){this.getDataset().metaData=this.getDataset().metaData||[];var s=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i});a&&e.isArray(this.getDataset().backgroundColor)&&this.getDataset().backgroundColor.splice(i,0,a),this.updateElement(s,i,!0),this.getDataset().metaData.splice(i,0,s)},getVisibleDatasetCount:function(){return e.where(this.chart.data.datasets,function(t){return e.isDatasetVisible(t)}).length},getRingIndex:function(t){for(var i=0,a=0;t>a;++a)e.isDatasetVisible(this.chart.data.datasets[a])&&++i;return i},update:function(t){var i=this.chart.chartArea.right-this.chart.chartArea.left-this.chart.options.elements.arc.borderWidth,a=this.chart.chartArea.bottom-this.chart.chartArea.top-this.chart.options.elements.arc.borderWidth,s=Math.min(i,a),o={x:0,y:0};if(this.chart.options.circumference&&this.chart.options.circumference<2*Math.PI){var n=this.chart.options.rotation%(2*Math.PI);n+=2*Math.PI*(n>=Math.PI?-1:n<-Math.PI?1:0);var r=n+this.chart.options.circumference,l={x:Math.cos(n),y:Math.sin(n)},h={x:Math.cos(r),y:Math.sin(r)},c=0>=n&&r>=0||n<=2*Math.PI&&2*Math.PI<=r,u=n<=.5*Math.PI&&.5*Math.PI<=r||n<=2.5*Math.PI&&2.5*Math.PI<=r,d=n<=-Math.PI&&-Math.PI<=r||n<=Math.PI&&Math.PI<=r,f=n<=.5*-Math.PI&&.5*-Math.PI<=r||n<=1.5*Math.PI&&1.5*Math.PI<=r,g=this.chart.options.cutoutPercentage/100,m={x:d?-1:Math.min(l.x*(l.x<0?1:g),h.x*(h.x<0?1:g)),y:f?-1:Math.min(l.y*(l.y<0?1:g),h.y*(h.y<0?1:g))},p={x:c?1:Math.max(l.x*(l.x>0?1:g),h.x*(h.x>0?1:g)),y:u?1:Math.max(l.y*(l.y>0?1:g),h.y*(h.y>0?1:g))},b={width:.5*(p.x-m.x),height:.5*(p.y-m.y)};s=Math.min(i/b.width,a/b.height),o={x:(p.x+m.x)*-.5,y:(p.y+m.y)*-.5}}this.chart.outerRadius=Math.max(s/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.getVisibleDatasetCount(),this.chart.offsetX=o.x*this.chart.outerRadius,this.chart.offsetY=o.y*this.chart.outerRadius,this.getDataset().total=0,e.each(this.getDataset().data,function(t){isNaN(t)||(this.getDataset().total+=Math.abs(t))},this),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.getRingIndex(this.index),this.innerRadius=this.outerRadius-this.chart.radiusLength,e.each(this.getDataset().metaData,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,a){var s=(this.chart.chartArea.left+this.chart.chartArea.right)/2,o=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,n=this.chart.options.rotation||Math.PI*-.5,r=this.chart.options.rotation||Math.PI*-.5,l=a&&this.chart.options.animation.animateRotate?0:this.calculateCircumference(this.getDataset().data[i])*((this.chart.options.circumference||2*Math.PI)/(2*Math.PI)),h=a&&this.chart.options.animation.animateScale?0:this.innerRadius,c=a&&this.chart.options.animation.animateScale?0:this.outerRadius;e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_model:{x:s+this.chart.offsetX,y:o+this.chart.offsetY,startAngle:n,endAngle:r,circumference:l,outerRadius:c,innerRadius:h,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor,i,this.chart.options.elements.arc.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.getDataset().label,i,this.chart.data.labels[i])}}),a||(0===i?t._model.startAngle=this.chart.options.rotation||Math.PI*-.5:t._model.startAngle=this.getDataset().metaData[i-1]._model.endAngle,t._model.endAngle=t._model.startAngle+t._model.circumference),t.pivot()},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},calculateCircumference:function(t){return this.getDataset().total>0&&!isNaN(t)?1.999999*Math.PI*(t/this.getDataset().total):0}})}},{}],18:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.line={showLines:!0,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],this.getDataset().metaDataset=this.getDataset().metaDataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:this.getDataset().metaData}),e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},update:function(t){var i,a=this.getDataset().metaDataset,s=this.getDataset().metaData,o=this.getScaleForId(this.getDataset().yAxisID);this.getScaleForId(this.getDataset().xAxisID);i=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),this.chart.options.showLines&&(a._scale=o,a._datasetIndex=this.index,a._children=s,a._model={tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,scaleTop:o.top,scaleBottom:o.bottom,scaleZero:i},a.pivot()),e.each(s,function(e,i){this.updateElement(e,i,t)},this),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},getPointBackgroundColor:function(t,i){var a=this.chart.options.elements.point.backgroundColor,s=this.getDataset();return t.custom&&t.custom.backgroundColor?a=t.custom.backgroundColor:s.pointBackgroundColor?a=e.getValueAtIndexOrDefault(s.pointBackgroundColor,i,a):s.backgroundColor&&(a=s.backgroundColor),a},getPointBorderColor:function(t,i){var a=this.chart.options.elements.point.borderColor,s=this.getDataset();return t.custom&&t.custom.borderColor?a=t.custom.borderColor:s.pointBorderColor?a=e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,a):s.borderColor&&(a=s.borderColor),a},getPointBorderWidth:function(t,i){var a=this.chart.options.elements.point.borderWidth,s=this.getDataset();return t.custom&&void 0!==t.custom.borderWidth?a=t.custom.borderWidth:void 0!==s.pointBorderWidth?a=e.getValueAtIndexOrDefault(s.pointBorderWidth,i,a):void 0!==s.borderWidth&&(a=s.borderWidth),a},updateElement:function(t,i,a){var s,o=this.getScaleForId(this.getDataset().yAxisID),n=this.getScaleForId(this.getDataset().xAxisID);s=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),t._chart=this.chart.chart,t._xScale=n,t._yScale=o,t._datasetIndex=this.index,t._index=i,t._model={x:n.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:a?s:this.calculatePointY(this.getDataset().data[i],i,this.index,this.chart.isCombo),tension:t.custom&&t.custom.tension?t.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),backgroundColor:this.getPointBackgroundColor(t,i),borderColor:this.getPointBorderColor(t,i),borderWidth:this.getPointBorderWidth(t,i),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)},t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},calculatePointY:function(t,i,a,s){var o=(this.getScaleForId(this.getDataset().xAxisID),this.getScaleForId(this.getDataset().yAxisID));if(o.options.stacked){for(var n=0,r=0,l=0;a>l;l++){var h=this.chart.data.datasets[l];"line"===h.type&&e.isDatasetVisible(h)&&(h.data[i]<0?r+=h.data[i]||0:n+=h.data[i]||0)}return 0>t?o.getPixelForValue(r+t):o.getPixelForValue(n+t)}return o.getPixelForValue(t)},updateBezierControlPoints:function(){e.each(this.getDataset().metaData,function(t,i){var a=e.splineCurve(e.previousItem(this.getDataset().metaData,i)._model,t._model,e.nextItem(this.getDataset().metaData,i)._model,t._model.tension);t._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t.pivot()},this)},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t){t.transition(i)}),this.chart.options.showLines&&this.getDataset().metaDataset.transition(i).draw(),e.each(this.getDataset().metaData,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,a,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=this.getPointBackgroundColor(t,i),t._model.borderColor=this.getPointBorderColor(t,i),t._model.borderWidth=this.getPointBorderWidth(t,i)}})}},{}],19:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.polarArea={scale:{type:"radialLinear",lineArc:!0},animateRotate:!0,animateScale:!0,aspectRatio:1,legendCallback:function(t){var e=[];if(e.push(''),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){return t.labels.length&&t.datasets.length?t.labels.map(function(e,i){return{text:e,fillStyle:t.datasets[0].backgroundColor[i],hidden:isNaN(t.datasets[0].data[i]),index:i}}):[]}},onClick:function(t,i){e.each(this.chart.data.datasets,function(t){t.metaHiddenData=t.metaHiddenData||[];var e=i.index;isNaN(t.data[e])?isNaN(t.metaHiddenData[e])||(t.data[e]=t.metaHiddenData[e]):(t.metaHiddenData[e]=t.data[e],t.data[e]=NaN)}),this.chart.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}},t.controllers.polarArea=t.DatasetController.extend({linkScales:function(){},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i)},getVisibleDatasetCount:function(){return e.where(this.chart.data.datasets,function(t){return e.isDatasetVisible(t)}).length},update:function(t){var i=Math.min(this.chart.chartArea.right-this.chart.chartArea.left,this.chart.chartArea.bottom-this.chart.chartArea.top);this.chart.outerRadius=Math.max((i-this.chart.options.elements.arc.borderWidth/2)/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.getVisibleDatasetCount(),this.getDataset().total=0,e.each(this.getDataset().data,function(t){this.getDataset().total+=Math.abs(t)},this),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.index,this.innerRadius=this.outerRadius-this.chart.radiusLength,e.each(this.getDataset().metaData,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,a){for(var s=this.calculateCircumference(this.getDataset().data[i]),o=(this.chart.chartArea.left+this.chart.chartArea.right)/2,n=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,r=0,l=0;i>l;++l)isNaN(this.getDataset().data[l])||++r;
-var h=-.5*Math.PI+s*r,c=h+s,u={x:o,y:n,innerRadius:0,outerRadius:this.chart.options.animateScale?0:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),startAngle:this.chart.options.animateRotate?Math.PI*-.5:h,endAngle:this.chart.options.animateRotate?Math.PI*-.5:c,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])};e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:a?u:{x:o,y:n,innerRadius:0,outerRadius:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),startAngle:h,endAngle:c,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])}}),t.pivot()},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},calculateCircumference:function(t){if(isNaN(t))return 0;var i=e.where(this.getDataset().data,function(t){return isNaN(t)}).length;return 2*Math.PI/(this.getDataset().data.length-i)}})}},{}],20:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({linkScales:function(){},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],this.getDataset().metaDataset=this.getDataset().metaDataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:this.getDataset().metaData,_loop:!0}),e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_model:{x:0,y:0}})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i),this.updateBezierControlPoints()},update:function(t){var i,a=this.getDataset().metaDataset,s=this.getDataset().metaData,o=this.chart.scale;i=o.min<0&&o.max<0?o.getPointPositionForValue(0,o.max):o.min>0&&o.max>0?o.getPointPositionForValue(0,o.min):o.getPointPositionForValue(0,0),e.extend(this.getDataset().metaDataset,{_datasetIndex:this.index,_children:this.getDataset().metaData,_model:{tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,scaleTop:o.top,scaleBottom:o.bottom,scaleZero:i}}),this.getDataset().metaDataset.pivot(),e.each(s,function(e,i){this.updateElement(e,i,t)},this),this.updateBezierControlPoints()},updateElement:function(t,i,a){var s=this.chart.scale.getPointPositionForValue(i,this.getDataset().data[i]);e.extend(t,{_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:{x:a?this.chart.scale.xCenter:s.x,y:a?this.chart.scale.yCenter:s.y,tension:t.custom&&t.custom.tension?t.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){e.each(this.getDataset().metaData,function(t,i){var a=e.splineCurve(e.previousItem(this.getDataset().metaData,i,!0)._model,t._model,e.nextItem(this.getDataset().metaData,i,!0)._model,t._model.tension);t._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t.pivot()},this)},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i)}),this.getDataset().metaDataset.transition(i).draw(),e.each(this.getDataset().metaData,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,a,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],21:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,i,a){a||(t.animating=!0);for(var s=0;s1&&(e=Math.floor(this.dropFrames),this.dropFrames=this.dropFrames%1);for(var i=0;ithis.animations[i].animationObject.numSteps&&(this.animations[i].animationObject.currentStep=this.animations[i].animationObject.numSteps),this.animations[i].animationObject.render(this.animations[i].chartInstance,this.animations[i].animationObject),this.animations[i].animationObject.onAnimationProgress&&this.animations[i].animationObject.onAnimationProgress.call&&this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].animationObject.currentStep===this.animations[i].animationObject.numSteps?(this.animations[i].animationObject.onAnimationComplete&&this.animations[i].animationObject.onAnimationComplete.call&&this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].chartInstance.animating=!1,this.animations.splice(i,1)):++i;var a=Date.now(),s=(a-t)/this.frameDuration;this.dropFrames+=s,this.animations.length>0&&this.requestAnimationFrame()}}}},{}],22:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.types={},t.instances={},t.controllers={},t.Controller=function(i){return this.chart=i,this.config=i.config,this.options=this.config.options=e.configMerge(t.defaults.global,t.defaults[this.config.type],this.config.options||{}),this.id=e.uid(),Object.defineProperty(this,"data",{get:function(){return this.config.data}}),t.instances[this.id]=this,this.options.responsive&&this.resize(!0),this.initialize(),this},e.extend(t.Controller.prototype,{initialize:function(){return this.bindEvents(),this.ensureScalesHaveIDs(),this.buildOrUpdateControllers(),this.buildScales(),this.buildSurroundingItems(),this.updateLayout(),this.resetElements(),this.initToolTip(),this.update(),this},clear:function(){return e.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var i=this.chart.canvas,a=e.getMaximumWidth(this.chart.canvas),s=this.options.maintainAspectRatio&&isNaN(this.chart.aspectRatio)===!1&&isFinite(this.chart.aspectRatio)&&0!==this.chart.aspectRatio?a/this.chart.aspectRatio:e.getMaximumHeight(this.chart.canvas),o=this.chart.width!==a||this.chart.height!==s;return o?(i.width=this.chart.width=a,i.height=this.chart.height=s,e.retinaScale(this.chart),t||(this.stop(),this.update(this.options.responsiveAnimationDuration)),this):this},ensureScalesHaveIDs:function(){var t="x-axis-",i="y-axis-";this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(e,i){e.id=e.id||t+i}),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(t,e){t.id=t.id||i+e}))},buildScales:function(){if(this.scales={},this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(i,a){var s=e.getValueOrDefault(i.type,"category"),o=t.scaleService.getScaleConstructor(s);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(i,a){var s=e.getValueOrDefault(i.type,"linear"),o=t.scaleService.getScaleConstructor(s);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this)),this.options.scale){var i=t.scaleService.getScaleConstructor(this.options.scale.type);if(i){var a=new i({ctx:this.chart.ctx,options:this.options.scale,chart:this});this.scale=a,this.scales.radialScale=a}}t.scaleService.addScalesToLayout(this)},buildSurroundingItems:function(){this.options.title&&(this.titleBlock=new t.Title({ctx:this.chart.ctx,options:this.options.title,chart:this}),t.layoutService.addBox(this,this.titleBlock)),this.options.legend&&(this.legend=new t.Legend({ctx:this.chart.ctx,options:this.options.legend,chart:this}),t.layoutService.addBox(this,this.legend))},updateLayout:function(){t.layoutService.update(this,this.chart.width,this.chart.height)},buildOrUpdateControllers:function(){var i=[],a=[];if(e.each(this.data.datasets,function(e,s){e.type||(e.type=this.config.type);var o=e.type;i.push(o),e.controller?e.controller.updateIndex(s):(e.controller=new t.controllers[o](this,s),a.push(e.controller))},this),i.length>1)for(var s=1;s0&&(e=this.data.datasets[e[0]._datasetIndex].metaData),e},generateLegend:function(){return this.options.legendCallback(this)},destroy:function(){this.clear(),e.unbindEvents(this,this.events),e.removeResizeListener(this.chart.canvas.parentNode);var i=this.chart.canvas;i.width=this.chart.width,i.height=this.chart.height,void 0!==this.chart.originalDevicePixelRatio&&this.chart.ctx.scale(1/this.chart.originalDevicePixelRatio,1/this.chart.originalDevicePixelRatio),i.style.width=this.chart.originalCanvasStyleWidth,i.style.height=this.chart.originalCanvasStyleHeight,delete t.instances[this.id]},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)},initToolTip:function(){this.tooltip=new t.Tooltip({_chart:this.chart,_chartInstance:this,_data:this.data,_options:this.options},this)},bindEvents:function(){e.bindEvents(this,this.options.events,function(t){this.eventHandler(t)})},eventHandler:function(t){if(this.lastActive=this.lastActive||[],this.lastTooltipActive=this.lastTooltipActive||[],"mouseout"===t.type)this.active=[],this.tooltipActive=[];else{var i=this,a=function(e){switch(e){case"single":return i.getElementAtEvent(t);case"label":return i.getElementsAtEvent(t);case"dataset":return i.getDatasetAtEvent(t);default:return t}};this.active=a(this.options.hover.mode),this.tooltipActive=a(this.options.tooltips.mode)}this.options.hover.onHover&&this.options.hover.onHover.call(this,this.active),("mouseup"===t.type||"click"===t.type)&&(this.options.onClick&&this.options.onClick.call(this,t,this.active),this.legend&&this.legend.handleEvent&&this.legend.handleEvent(t));if(this.lastActive.length)switch(this.options.hover.mode){case"single":this.data.datasets[this.lastActive[0]._datasetIndex].controller.removeHoverStyle(this.lastActive[0],this.lastActive[0]._datasetIndex,this.lastActive[0]._index);break;case"label":case"dataset":for(var s=0;st)this.getDataset().metaData.splice(t,e-t);else if(t>e)for(var i=e;t>i;++i)this.addElementAndReset(i)},addElements:e.noop,addElementAndReset:e.noop,draw:e.noop,removeHoverStyle:e.noop,setHoverStyle:e.noop,update:e.noop}),t.DatasetController.extend=e.inherits}},{}],24:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.elements={},t.Element=function(t){e.extend(this,t),this.initialize.apply(this,arguments)},e.extend(t.Element.prototype,{initialize:function(){},pivot:function(){return this._view||(this._view=e.clone(this._model)),this._start=e.clone(this._view),this},transition:function(t){return this._view||(this._view=e.clone(this._model)),1===t?(this._view=this._model,this._start=null,this):(this._start||this.pivot(),e.each(this._model,function(i,a){if("_"!==a[0]&&this._model.hasOwnProperty(a))if(this._view.hasOwnProperty(a))if(i===this._view[a]);else if("string"==typeof i)try{var s=e.color(this._start[a]).mix(e.color(this._model[a]),t);this._view[a]=s.rgbString()}catch(o){this._view[a]=i}else if("number"==typeof i){var n=void 0!==this._start[a]&&isNaN(this._start[a])===!1?this._start[a]:0;this._view[a]=(this._model[a]-n)*t+n}else this._view[a]=i;else"number"!=typeof i||isNaN(this._view[a])?this._view[a]=i:this._view[a]=i*t;else;},this),this)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return e.isNumber(this._model.x)&&e.isNumber(this._model.y)}}),t.Element.extend=e.inherits}},{}],25:[function(t,e,i){"use strict";var a=t("chartjs-color");e.exports=function(t){function e(t,e,i){var a;return"string"==typeof t?(a=parseInt(t,10),-1!=t.indexOf("%")&&(a=a/100*e.parentNode[i])):a=t,a}function i(t,i,a){var s,o=document.defaultView.getComputedStyle(t)[i],n=document.defaultView.getComputedStyle(t.parentNode)[i],r=null!==o&&"none"!==o,l=null!==n&&"none"!==n;return(r||l)&&(s=Math.min(r?e(o,t,a):Number.POSITIVE_INFINITY,l?e(n,t.parentNode,a):Number.POSITIVE_INFINITY)),s}var s=t.helpers={};s.each=function(t,e,i,a){var o,n;if(s.isArray(t))if(n=t.length,a)for(o=n-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;n>o;o++)e.call(i,t[o],o);else if("object"==typeof t){var r=Object.keys(t);for(n=r.length,o=0;n>o;o++)e.call(i,t[r[o]],r[o])}},s.clone=function(t){var e={};return s.each(t,function(i,a){t.hasOwnProperty(a)&&(s.isArray(i)?e[a]=i.slice(0):"object"==typeof i&&null!==i?e[a]=s.clone(i):e[a]=i)}),e},s.extend=function(t){for(var e=arguments.length,i=[],a=1;e>a;a++)i.push(arguments[a]);return s.each(i,function(e){s.each(e,function(i,a){e.hasOwnProperty(a)&&(t[a]=i)})}),t},s.configMerge=function(e){var i=s.clone(e);return s.each(Array.prototype.slice.call(arguments,1),function(e){s.each(e,function(a,o){if(e.hasOwnProperty(o))if("scales"===o)i[o]=s.scaleMerge(i.hasOwnProperty(o)?i[o]:{},a);else if("scale"===o)i[o]=s.configMerge(i.hasOwnProperty(o)?i[o]:{},t.scaleService.getScaleDefaults(a.type),a);else if(i.hasOwnProperty(o)&&s.isArray(i[o])&&s.isArray(a)){var n=i[o];s.each(a,function(t,e){e=a[o].length||!a[o][i].type?a[o].push(s.configMerge(r,e)):e.type&&e.type!==a[o][i].type?a[o][i]=s.configMerge(a[o][i],r,e):a[o][i]=s.configMerge(a[o][i],e)}):(a[o]=[],s.each(e,function(e){var i=s.getValueOrDefault(e.type,"xAxes"===o?"category":"linear");a[o].push(s.configMerge(t.scaleService.getScaleDefaults(i),e))})):a.hasOwnProperty(o)&&"object"==typeof a[o]&&null!==a[o]&&"object"==typeof e?a[o]=s.configMerge(a[o],e):a[o]=e)}),a},s.getValueAtIndexOrDefault=function(t,e,i){return void 0===t||null===t?i:s.isArray(t)?e=0;a--){var s=t[a];if(e(s))return s}},s.inherits=function(t){var e=this,i=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},a=function(){this.constructor=i};return a.prototype=e.prototype,i.prototype=new a,i.extend=s.inherits,t&&s.extend(i.prototype,t),i.__super__=e.prototype,i},s.noop=function(){},s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),s.warn=function(t){console&&"function"==typeof console.warn&&console.warn(t)},s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},s.almostEquals=function(t,e,i){return Math.abs(t-e)0?1:-1)},s.log10=function(t){return Math.log10?Math.log10(t):Math.log(t)/Math.LN10},s.toRadians=function(t){return t*(Math.PI/180)},s.toDegrees=function(t){return t*(180/Math.PI)},s.getAngleFromPoint=function(t,e){var i=e.x-t.x,a=e.y-t.y,s=Math.sqrt(i*i+a*a),o=Math.atan2(a,i);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:s}},s.aliasPixel=function(t){return t%2===0?0:.5},s.splineCurve=function(t,e,i,a){var s=t.skip?e:t,o=e,n=i.skip?e:i,r=Math.sqrt(Math.pow(o.x-s.x,2)+Math.pow(o.y-s.y,2)),l=Math.sqrt(Math.pow(n.x-o.x,2)+Math.pow(n.y-o.y,2)),h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;var u=a*h,d=a*c;return{previous:{x:o.x-u*(n.x-s.x),y:o.y-u*(n.y-s.y)},next:{x:o.x+d*(n.x-s.x),y:o.y+d*(n.y-s.y)}}},s.nextItem=function(t,e,i){return i?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},s.previousItem=function(t,e,i){return i?0>=e?t[t.length-1]:t[e-1]:0>=e?t[0]:t[e-1]},s.niceNum=function(t,e){var i,a=Math.floor(s.log10(t)),o=t/Math.pow(10,a);return i=e?1.5>o?1:3>o?2:7>o?5:10:1>=o?1:2>=o?2:5>=o?5:10,i*Math.pow(10,a)};var o=s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,a=1;return 0===t?0:1===(t/=1)?1:(i||(i=.3),at?-.5*(a*Math.pow(2,10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)):a*Math.pow(2,-10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-o.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?1*(7.5625*t*t):2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*o.easeInBounce(2*t):.5*o.easeOutBounce(2*t-1)+.5}};s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){
-return window.setTimeout(t,1e3/60)}}(),s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),s.getRelativePosition=function(t,e){var i,a,o=t.originalEvent||t,n=t.currentTarget||t.srcElement,r=n.getBoundingClientRect();o.touches&&o.touches.length>0?(i=o.touches[0].clientX,a=o.touches[0].clientY):(i=o.clientX,a=o.clientY);var l=parseFloat(s.getStyle(n,"padding-left")),h=parseFloat(s.getStyle(n,"padding-top")),c=parseFloat(s.getStyle(n,"padding-right")),u=parseFloat(s.getStyle(n,"padding-bottom")),d=r.right-r.left-l-c,f=r.bottom-r.top-h-u;return i=Math.round((i-r.left-l)/d*n.width/e.currentDevicePixelRatio),a=Math.round((a-r.top-h)/f*n.height/e.currentDevicePixelRatio),{x:i,y:a}},s.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i},s.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=s.noop},s.bindEvents=function(t,e,i){t.events||(t.events={}),s.each(e,function(e){t.events[e]=function(){i.apply(t,arguments)},s.addEvent(t.chart.canvas,e,t.events[e])})},s.unbindEvents=function(t,e){s.each(e,function(e,i){s.removeEvent(t.chart.canvas,i,e)})},s.getConstraintWidth=function(t){return i(t,"max-width","clientWidth")},s.getConstraintHeight=function(t){return i(t,"max-height","clientHeight")},s.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(s.getStyle(e,"padding-left"))+parseInt(s.getStyle(e,"padding-right")),a=e.clientWidth-i,o=s.getConstraintWidth(t);return void 0!==o&&(a=Math.min(a,o)),a},s.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(s.getStyle(e,"padding-top"))+parseInt(s.getStyle(e,"padding-bottom")),a=e.clientHeight-i,o=s.getConstraintHeight(t);return void 0!==o&&(a=Math.min(a,o)),a},s.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},s.retinaScale=function(t){var e=t.ctx,i=t.canvas.width,a=t.canvas.height,s=t.currentDevicePixelRatio=window.devicePixelRatio||1;1!==s&&(e.canvas.height=a*s,e.canvas.width=i*s,e.scale(s,s),t.originalDevicePixelRatio=t.originalDevicePixelRatio||s),e.canvas.style.width=i+"px",e.canvas.style.height=a+"px"},s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},s.fontString=function(t,e,i){return e+" "+t+"px "+i},s.longestText=function(t,e,i,a){a=a||{},a.data=a.data||{},a.garbageCollect=a.garbageCollect||[],a.font!==e&&(a.data={},a.garbageCollect=[],a.font=e),t.font=e;var o=0;s.each(i,function(e){if(void 0!==e&&null!==e){var i=a.data[e];i||(i=a.data[e]=t.measureText(e).width,a.garbageCollect.push(e)),i>o&&(o=i)}});var n=a.garbageCollect.length/2;if(n>i.length){for(var r=0;n>r;r++)delete a.data[a.garbageCollect[r]];a.garbageCollect.splice(0,n)}return o},s.drawRoundedRectangle=function(t,e,i,a,s,o){t.beginPath(),t.moveTo(e+o,i),t.lineTo(e+a-o,i),t.quadraticCurveTo(e+a,i,e+a,i+o),t.lineTo(e+a,i+s-o),t.quadraticCurveTo(e+a,i+s,e+a-o,i+s),t.lineTo(e+o,i+s),t.quadraticCurveTo(e,i+s,e,i+s-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath()},s.color=function(e){return a?a(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.log("Color.js not found!"),e)},s.addResizeListener=function(t,e){var i=document.createElement("iframe"),a="chartjs-hidden-iframe";i.classlist?i.classlist.add(a):i.setAttribute("class",a),i.style.width="100%",i.style.display="block",i.style.border=0,i.style.height=0,i.style.margin=0,i.style.position="absolute",i.style.left=0,i.style.right=0,i.style.top=0,i.style.bottom=0,t.insertBefore(i,t.firstChild),(i.contentWindow||i).onresize=function(){e&&e()}},s.removeResizeListener=function(t){var e=t.querySelector(".chartjs-hidden-iframe");e&&e.parentNode.removeChild(e)},s.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(t)},s.pushAllIfDefined=function(t,e){"undefined"!=typeof t&&(s.isArray(t)?e.push.apply(e,t):e.push(t))},s.isDatasetVisible=function(t){return!t.hidden},s.callCallback=function(t,e,i){t&&"function"==typeof t.call&&t.apply(i,e)}}},{"chartjs-color":5}],26:[function(t,e,i){"use strict";e.exports=function(){var t=function(e,i){this.config=i,e.length&&e[0].getContext&&(e=e[0]),e.getContext&&(e=e.getContext("2d")),this.ctx=e,this.canvas=e.canvas,this.width=e.canvas.width||parseInt(t.helpers.getStyle(e.canvas,"width"))||t.helpers.getMaximumWidth(e.canvas),this.height=e.canvas.height||parseInt(t.helpers.getStyle(e.canvas,"height"))||t.helpers.getMaximumHeight(e.canvas),this.aspectRatio=this.width/this.height,(isNaN(this.aspectRatio)||isFinite(this.aspectRatio)===!1)&&(this.aspectRatio=void 0!==i.aspectRatio?i.aspectRatio:2),this.originalCanvasStyleWidth=e.canvas.style.width,this.originalCanvasStyleHeight=e.canvas.style.height,t.helpers.retinaScale(this),i&&(this.controller=new t.Controller(this));var a=this;return t.helpers.addResizeListener(e.canvas.parentNode,function(){a.controller&&a.controller.config.options.responsive&&a.controller.resize()}),this.controller?this.controller:this};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('');for(var i=0;i'),t.data.datasets[i].label&&e.push(t.data.datasets[i].label),e.push("");return e.push("
"),e.join("")}}},t}},{}],27:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,i,a){function s(t){var e,i=t.isHorizontal();i?(e=t.update(t.options.fullWidth?m:k,y),_-=e.height):(e=t.update(x,v),k-=e.width),D.push({horizontal:i,minSize:e,box:t})}function o(t){var i=e.findNextWhere(D,function(e){return e.box===t});if(i)if(t.isHorizontal()){var a={left:S,right:w,top:0,bottom:0};t.update(t.options.fullWidth?m:k,p/2,a)}else t.update(i.minSize.width,_)}function n(t){var i=e.findNextWhere(D,function(e){return e.box===t}),a={left:0,right:0,top:C,bottom:M};i&&t.update(i.minSize.width,_,a)}function r(t){t.isHorizontal()?(t.left=t.options.fullWidth?l:S,t.right=t.options.fullWidth?i-l:S+k,t.top=F,t.bottom=F+t.height,F=t.bottom):(t.left=T,t.right=T+t.width,t.top=C,t.bottom=C+_,T=t.right)}if(t){var l=0,h=0,c=e.where(t.boxes,function(t){return"left"===t.options.position}),u=e.where(t.boxes,function(t){return"right"===t.options.position}),d=e.where(t.boxes,function(t){return"top"===t.options.position}),f=e.where(t.boxes,function(t){return"bottom"===t.options.position}),g=e.where(t.boxes,function(t){return"chartArea"===t.options.position});d.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),f.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var m=i-2*l,p=a-2*h,b=m/2,v=p/2,x=(i-b)/(c.length+u.length),y=(a-v)/(d.length+f.length),k=m,_=p,D=[];e.each(c.concat(u,d,f),s);var S=l,w=l,C=h,M=h;e.each(c.concat(u),o),e.each(c,function(t){S+=t.width}),e.each(u,function(t){w+=t.width}),e.each(d.concat(f),o),e.each(d,function(t){C+=t.height}),e.each(f,function(t){M+=t.height}),e.each(c.concat(u),n),S=l,w=l,C=h,M=h,e.each(c,function(t){S+=t.width}),e.each(u,function(t){w+=t.width}),e.each(d,function(t){C+=t.height}),e.each(f,function(t){M+=t.height});var A=a-C-M,I=i-S-w;(I!==k||A!==_)&&(e.each(c,function(t){t.height=A}),e.each(u,function(t){t.height=A}),e.each(d,function(t){t.width=I}),e.each(f,function(t){t.width=I}),_=A,k=I);var T=l,F=h;e.each(c.concat(d),r),T+=k,F+=_,e.each(u,r),e.each(f,r),t.chartArea={left:S,top:C,right:S+k,bottom:C+_},e.each(g,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(k,_)})}}}}},{}],28:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var i=this.chart.data.datasets[e.datasetIndex];i.hidden=!i.hidden,this.chart.update()},labels:{boxWidth:40,padding:10,generateLabels:function(t){return e.isArray(t.datasets)?t.datasets.map(function(t,e){return{text:t.label,fillStyle:t.backgroundColor,hidden:t.hidden,lineCap:t.borderCapStyle,lineDash:t.borderDash,lineDashOffset:t.borderDashOffset,lineJoin:t.borderJoinStyle,lineWidth:t.borderWidth,strokeStyle:t.borderColor,datasetIndex:e}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){e.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:e.noop,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:e.noop,beforeSetDimensions:e.noop,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:e.noop,beforeBuildLabels:e.noop,buildLabels:function(){this.legendItems=this.options.labels.generateLabels.call(this,this.chart.data),this.options.reverse&&this.legendItems.reverse()},afterBuildLabels:e.noop,beforeFit:e.noop,fit:function(){var i=this.ctx,a=e.getValueOrDefault(this.options.labels.fontSize,t.defaults.global.defaultFontSize),s=e.getValueOrDefault(this.options.labels.fontStyle,t.defaults.global.defaultFontStyle),o=e.getValueOrDefault(this.options.labels.fontFamily,t.defaults.global.defaultFontFamily),n=e.fontString(a,s,o);if(this.legendHitBoxes=[],this.isHorizontal()?this.minSize.width=this.maxWidth:this.minSize.width=this.options.display?10:0,this.isHorizontal()?this.minSize.height=this.options.display?10:0:this.minSize.height=this.maxHeight,this.options.display&&this.isHorizontal()){this.lineWidths=[0];var r=this.legendItems.length?a+this.options.labels.padding:0;i.textAlign="left",i.textBaseline="top",i.font=n,e.each(this.legendItems,function(t,e){var s=this.options.labels.boxWidth+a/2+i.measureText(t.text).width;this.lineWidths[this.lineWidths.length-1]+s+this.options.labels.padding>=this.width&&(r+=a+this.options.labels.padding,this.lineWidths[this.lineWidths.length]=this.left),this.legendHitBoxes[e]={left:0,top:0,width:s,height:a},this.lineWidths[this.lineWidths.length-1]+=s+this.options.labels.padding},this),this.minSize.height+=r}this.width=this.minSize.width,this.height=this.minSize.height},afterFit:e.noop,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){if(this.options.display){var i=this.ctx,a={x:this.left+(this.width-this.lineWidths[0])/2,y:this.top+this.options.labels.padding,line:0},s=e.getValueOrDefault(this.options.labels.fontColor,t.defaults.global.defaultFontColor),o=e.getValueOrDefault(this.options.labels.fontSize,t.defaults.global.defaultFontSize),n=e.getValueOrDefault(this.options.labels.fontStyle,t.defaults.global.defaultFontStyle),r=e.getValueOrDefault(this.options.labels.fontFamily,t.defaults.global.defaultFontFamily),l=e.fontString(o,n,r);this.isHorizontal()&&(i.textAlign="left",i.textBaseline="top",i.lineWidth=.5,i.strokeStyle=s,i.fillStyle=s,i.font=l,e.each(this.legendItems,function(e,s){var n=i.measureText(e.text).width,r=this.options.labels.boxWidth+o/2+n;a.x+r>=this.width&&(a.y+=o+this.options.labels.padding,a.line++,a.x=this.left+(this.width-this.lineWidths[a.line])/2),i.save();var l=function(t,e){return void 0!==t?t:e};i.fillStyle=l(e.fillStyle,t.defaults.global.defaultColor),i.lineCap=l(e.lineCap,t.defaults.global.elements.line.borderCapStyle),i.lineDashOffset=l(e.lineDashOffset,t.defaults.global.elements.line.borderDashOffset),i.lineJoin=l(e.lineJoin,t.defaults.global.elements.line.borderJoinStyle),i.lineWidth=l(e.lineWidth,t.defaults.global.elements.line.borderWidth),i.strokeStyle=l(e.strokeStyle,t.defaults.global.defaultColor),i.setLineDash&&i.setLineDash(l(e.lineDash,t.defaults.global.elements.line.borderDash)),i.strokeRect(a.x,a.y,this.options.labels.boxWidth,o),i.fillRect(a.x,a.y,this.options.labels.boxWidth,o),i.restore(),this.legendHitBoxes[s].left=a.x,this.legendHitBoxes[s].top=a.y,i.fillText(e.text,this.options.labels.boxWidth+o/2+a.x,a.y),e.hidden&&(i.beginPath(),i.lineWidth=2,i.moveTo(this.options.labels.boxWidth+o/2+a.x,a.y+o/2),i.lineTo(this.options.labels.boxWidth+o/2+a.x+n,a.y+o/2),i.stroke()),a.x+=r+this.options.labels.padding},this))}},handleEvent:function(t){var i=e.getRelativePosition(t,this.chart.chart);if(i.x>=this.left&&i.x<=this.right&&i.y>=this.top&&i.y<=this.bottom)for(var a=0;a=s.left&&i.x<=s.left+s.width&&i.y>=s.top&&i.y<=s.top+s.height){this.options.onClick&&this.options.onClick.call(this,t,this.legendItems[a]);break}}}})}},{}],29:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.scale={display:!0,gridLines:{display:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",offsetGridLines:!1},scaleLabel:{labelString:"",display:!1},ticks:{beginAtZero:!1,maxRotation:50,mirror:!1,padding:10,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,callback:function(t){return""+t}}},t.Scale=t.Element.extend({beforeUpdate:function(){e.callCallback(this.options.beforeUpdate,[this])},update:function(t,i,a){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=i,this.margins=e.extend({left:0,right:0,top:0,bottom:0},a),this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this.beforeBuildTicks(),this.buildTicks(),this.afterBuildTicks(),this.beforeTickToLabelConversion(),this.convertTicksToLabels(),this.afterTickToLabelConversion(),this.beforeCalculateTickRotation(),this.calculateTickRotation(),this.afterCalculateTickRotation(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:function(){e.callCallback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){e.callCallback(this.options.beforeSetDimensions,[this])},setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0},afterSetDimensions:function(){e.callCallback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){e.callCallback(this.options.beforeDataLimits,[this])},determineDataLimits:e.noop,afterDataLimits:function(){e.callCallback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){e.callCallback(this.options.beforeBuildTicks,[this])},buildTicks:e.noop,afterBuildTicks:function(){e.callCallback(this.options.afterBuildTicks,[this])},beforeTickToLabelConversion:function(){e.callCallback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){this.ticks=this.ticks.map(function(t,e,i){return this.options.ticks.userCallback?this.options.ticks.userCallback(t,e,i):this.options.ticks.callback(t,e,i)},this)},afterTickToLabelConversion:function(){e.callCallback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){e.callCallback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),a=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),s=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),o=e.fontString(i,a,s);this.ctx.font=o;var n,r=this.ctx.measureText(this.ticks[0]).width,l=this.ctx.measureText(this.ticks[this.ticks.length-1]).width;if(this.labelRotation=0,this.paddingRight=0,this.paddingLeft=0,this.options.display&&this.isHorizontal()){this.paddingRight=l/2+3,this.paddingLeft=r/2+3,this.longestTextCache||(this.longestTextCache={});for(var h,c,u=e.longestText(this.ctx,o,this.ticks,this.longestTextCache),d=u,f=this.getPixelForTick(1)-this.getPixelForTick(0)-6;d>f&&this.labelRotationthis.yLabelWidth&&(this.paddingLeft=n+i/2),this.paddingRight=i/2,c*u>this.maxHeight){this.labelRotation--;break}this.labelRotation++,d=h*u}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0))},afterCalculateTickRotation:function(){e.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){e.callCallback(this.options.beforeFit,[this])},fit:function(){this.minSize={width:0,height:0};var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),a=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),s=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),o=e.fontString(i,a,s),n=e.getValueOrDefault(this.options.scaleLabel.fontSize,t.defaults.global.defaultFontSize),r=e.getValueOrDefault(this.options.scaleLabel.fontStyle,t.defaults.global.defaultFontStyle),l=e.getValueOrDefault(this.options.scaleLabel.fontFamily,t.defaults.global.defaultFontFamily);e.fontString(n,r,l);if(this.isHorizontal()?this.minSize.width=this.isFullWidth()?this.maxWidth-this.margins.left-this.margins.right:this.maxWidth:this.minSize.width=this.options.gridLines.display&&this.options.display?10:0,this.isHorizontal()?this.minSize.height=this.options.gridLines.display&&this.options.display?10:0:this.minSize.height=this.maxHeight,this.options.scaleLabel.display&&(this.isHorizontal()?this.minSize.height+=1.5*n:this.minSize.width+=1.5*n),this.options.ticks.display&&this.options.display){this.longestTextCache||(this.longestTextCache={});var h=e.longestText(this.ctx,o,this.ticks,this.longestTextCache);if(this.isHorizontal()){this.longestLabelWidth=h;var c=Math.sin(e.toRadians(this.labelRotation))*this.longestLabelWidth+1.5*i;this.minSize.height=Math.min(this.maxHeight,this.minSize.height+c),this.ctx.font=o;var u=this.ctx.measureText(this.ticks[0]).width,d=this.ctx.measureText(this.ticks[this.ticks.length-1]).width,f=Math.cos(e.toRadians(this.labelRotation)),g=Math.sin(e.toRadians(this.labelRotation));this.paddingLeft=0!==this.labelRotation?f*u+3:u/2+3,this.paddingRight=0!==this.labelRotation?g*(i/2)+3:d/2+3}else{var m=this.maxWidth-this.minSize.width;this.options.ticks.mirror||(h+=this.options.ticks.padding),m>h?this.minSize.width+=h:this.minSize.width=this.maxWidth,this.paddingTop=i/2,this.paddingBottom=i/2}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingTop=Math.max(this.paddingTop-this.margins.top,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0),this.paddingBottom=Math.max(this.paddingBottom-this.margins.bottom,0)),this.width=this.minSize.width,this.height=this.minSize.height},afterFit:function(){e.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function i(t){return null===t||"undefined"==typeof t?NaN:"number"==typeof t&&isNaN(t)?NaN:"object"==typeof t?t instanceof Date?t:i(this.isHorizontal()?t.x:t.y):t},getLabelForIndex:e.noop,getPixelForValue:e.noop,getPixelForTick:function(t,e){if(this.isHorizontal()){var i=this.width-(this.paddingLeft+this.paddingRight),a=i/Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1),s=a*t+this.paddingLeft;e&&(s+=a/2);var o=this.left+Math.round(s);return o+=this.isFullWidth()?this.margins.left:0}var n=this.height-(this.paddingTop+this.paddingBottom);return this.top+t*(n/(this.ticks.length-1))},getPixelForDecimal:function(t){if(this.isHorizontal()){var e=this.width-(this.paddingLeft+this.paddingRight),i=e*t+this.paddingLeft,a=this.left+Math.round(i);return a+=this.isFullWidth()?this.margins.left:0}return this.top+t*this.height},draw:function(i){if(this.options.display){var a,s,o,n,r,l=0!==this.labelRotation,h=this.options.ticks.autoSkip;this.options.ticks.maxTicksLimit&&(r=this.options.ticks.maxTicksLimit);var c=e.getValueOrDefault(this.options.ticks.fontColor,t.defaults.global.defaultFontColor),u=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),d=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),f=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),g=e.fontString(u,d,f),m=e.getValueOrDefault(this.options.scaleLabel.fontColor,t.defaults.global.defaultFontColor),p=e.getValueOrDefault(this.options.scaleLabel.fontSize,t.defaults.global.defaultFontSize),b=e.getValueOrDefault(this.options.scaleLabel.fontStyle,t.defaults.global.defaultFontStyle),v=e.getValueOrDefault(this.options.scaleLabel.fontFamily,t.defaults.global.defaultFontFamily),x=e.fontString(p,b,v),y=Math.cos(e.toRadians(this.labelRotation)),k=(Math.sin(e.toRadians(this.labelRotation)),this.longestLabelWidth*y);if(this.ctx.fillStyle=c,this.isHorizontal()){a=!0;var _="bottom"===this.options.position?this.top:this.bottom-10,D="bottom"===this.options.position?this.top+10:this.bottom;if(s=!1,(k/2+this.options.ticks.autoSkipPadding)*this.ticks.length>this.width-(this.paddingLeft+this.paddingRight)&&(s=1+Math.floor((k/2+this.options.ticks.autoSkipPadding)*this.ticks.length/(this.width-(this.paddingLeft+this.paddingRight)))),r&&this.ticks.length>r)for(;!s||this.ticks.length/(s||1)>r;)s||(s=1),s+=1;h||(s=!1),e.each(this.ticks,function(t,o){var n=this.ticks.length===o+1,r=s>1&&o%s>0||o%s===0&&o+s>this.ticks.length;if((!r||n)&&void 0!==t&&null!==t){var h=this.getPixelForTick(o),c=this.getPixelForTick(o,this.options.gridLines.offsetGridLines);this.options.gridLines.display&&(o===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,a=!0):a&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,a=!1),h+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,_),this.ctx.lineTo(h,D)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,i.top),this.ctx.lineTo(h,i.bottom)),this.ctx.stroke()),this.options.ticks.display&&(this.ctx.save(),this.ctx.translate(c,l?this.top+12:"top"===this.options.position?this.bottom-10:this.top+10),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textAlign=l?"right":"center",this.ctx.textBaseline=l?"middle":"top"===this.options.position?"bottom":"top",this.ctx.fillText(t,0,0),this.ctx.restore())}},this),this.options.scaleLabel.display&&(this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillStyle=m,this.ctx.font=x,o=this.left+(this.right-this.left)/2,n="bottom"===this.options.position?this.bottom-p/2:this.top+p/2,this.ctx.fillText(this.options.scaleLabel.labelString,o,n))}else{a=!0;var S="right"===this.options.position?this.left:this.right-5,w="right"===this.options.position?this.left+5:this.right;if(e.each(this.ticks,function(t,s){if(void 0!==t&&null!==t){var o=this.getPixelForTick(s);if(this.options.gridLines.display&&(s===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,a=!0):a&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,a=!1),o+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(S,o),this.ctx.lineTo(w,o)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(i.left,o),this.ctx.lineTo(i.right,o)),this.ctx.stroke()),this.options.ticks.display){var n,r=this.getPixelForTick(s,this.options.gridLines.offsetGridLines);this.ctx.save(),"left"===this.options.position?this.options.ticks.mirror?(n=this.right+this.options.ticks.padding,this.ctx.textAlign="left"):(n=this.right-this.options.ticks.padding,this.ctx.textAlign="right"):this.options.ticks.mirror?(n=this.left-this.options.ticks.padding,this.ctx.textAlign="right"):(n=this.left+this.options.ticks.padding,this.ctx.textAlign="left"),this.ctx.translate(n,r),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textBaseline="middle",this.ctx.fillText(t,0,0),this.ctx.restore()}}},this),this.options.scaleLabel.display){o="left"===this.options.position?this.left+p/2:this.right-p/2,n=this.top+(this.bottom-this.top)/2;var C="left"===this.options.position?-.5*Math.PI:.5*Math.PI;this.ctx.save(),this.ctx.translate(o,n),this.ctx.rotate(C),this.ctx.textAlign="center",this.ctx.fillStyle=m,this.ctx.font=x,this.ctx.textBaseline="middle",this.ctx.fillText(this.options.scaleLabel.labelString,0,0),this.ctx.restore()}}this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color;var M=this.left,A=this.right,I=this.top,T=this.bottom;this.isHorizontal()?(I=T="top"===this.options.position?this.bottom:this.top,I+=e.aliasPixel(this.ctx.lineWidth),T+=e.aliasPixel(this.ctx.lineWidth)):(M=A="left"===this.options.position?this.right:this.left,M+=e.aliasPixel(this.ctx.lineWidth),A+=e.aliasPixel(this.ctx.lineWidth)),this.ctx.beginPath(),this.ctx.moveTo(M,I),this.ctx.lineTo(A,T),this.ctx.stroke()}}})}},{}],30:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.scaleService={constructors:{},defaults:{},registerScaleType:function(t,i,a){this.constructors[t]=i,this.defaults[t]=e.clone(a)},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0},getScaleDefaults:function(i){return this.defaults.hasOwnProperty(i)?e.scaleMerge(t.defaults.scale,this.defaults[i]):{}},addScalesToLayout:function(i){e.each(i.scales,function(e){t.layoutService.addBox(i,e)})}}}},{}],31:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.title={display:!1,position:"top",fullWidth:!0,fontStyle:"bold",padding:10,text:""},t.Title=t.Element.extend({initialize:function(i){e.extend(this,i),this.options=e.configMerge(t.defaults.global.title,i.options),this.legendHitBoxes=[]},beforeUpdate:e.noop,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:e.noop,beforeSetDimensions:e.noop,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:e.noop,beforeBuildLabels:e.noop,buildLabels:e.noop,afterBuildLabels:e.noop,beforeFit:e.noop,fit:function(){var i=(this.ctx,e.getValueOrDefault(this.options.fontSize,t.defaults.global.defaultFontSize)),a=e.getValueOrDefault(this.options.fontStyle,t.defaults.global.defaultFontStyle),s=e.getValueOrDefault(this.options.fontFamily,t.defaults.global.defaultFontFamily);e.fontString(i,a,s);this.isHorizontal()?this.minSize.width=this.maxWidth:this.minSize.width=0,this.isHorizontal()?this.minSize.height=0:this.minSize.height=this.maxHeight,this.isHorizontal()?this.options.display&&(this.minSize.height+=i+2*this.options.padding):this.options.display&&(this.minSize.width+=i+2*this.options.padding),this.width=this.minSize.width,this.height=this.minSize.height},afterFit:e.noop,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){if(this.options.display){var i,a,s=this.ctx,o=e.getValueOrDefault(this.options.fontColor,t.defaults.global.defaultFontColor),n=e.getValueOrDefault(this.options.fontSize,t.defaults.global.defaultFontSize),r=e.getValueOrDefault(this.options.fontStyle,t.defaults.global.defaultFontStyle),l=e.getValueOrDefault(this.options.fontFamily,t.defaults.global.defaultFontFamily),h=e.fontString(n,r,l);if(s.fillStyle=o,s.font=h,this.isHorizontal())s.textAlign="center",s.textBaseline="middle",i=this.left+(this.right-this.left)/2,a=this.top+(this.bottom-this.top)/2,s.fillText(this.options.text,i,a);else{i="left"===this.options.position?this.left+n/2:this.right-n/2,a=this.top+(this.bottom-this.top)/2;var c="left"===this.options.position?-.5*Math.PI:.5*Math.PI;s.save(),s.translate(i,a),s.rotate(c),s.textAlign="center",s.textBaseline="middle",s.fillText(this.options.text,0,0),s.restore()}}}})}},{}],32:[function(t,e,i){"use strict";e.exports=function(t){function e(t,e){return e&&(i.isArray(e)?t=t.concat(e):t.push(e)),t}var i=t.helpers;t.defaults.global.tooltips={enabled:!0,custom:null,mode:"single",backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleColor:"#fff",titleAlign:"left",bodySpacing:2,bodyColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,yAlign:"center",xAlign:"center",caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",callbacks:{beforeTitle:i.noop,title:function(t,e){var i="";return t.length>0&&(t[0].xLabel?i=t[0].xLabel:e.labels.length>0&&t[0].indexthis._chart.height-t.height&&(this._model.yAlign="bottom");var e,i,a,s,o,n=this,r=(this._chartInstance.chartArea.left+this._chartInstance.chartArea.right)/2,l=(this._chartInstance.chartArea.top+this._chartInstance.chartArea.bottom)/2;"center"===this._model.yAlign?(e=function(t){return r>=t},i=function(t){return t>r}):(e=function(e){return e<=t.width/2},i=function(e){return e>=n._chart.width-t.width/2}),a=function(e){return e+t.width>n._chart.width},s=function(e){return e-t.width<0},o=function(t){return l>=t?"top":"bottom"},e(this._model.x)?(this._model.xAlign="left",a(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y))):i(this._model.x)&&(this._model.xAlign="right",s(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y)))},getBackgroundPoint:function(t,e){var i={x:t.x,y:t.y};return"right"===t.xAlign?i.x-=e.width:"center"===t.xAlign&&(i.x-=e.width/2),"top"===t.yAlign?i.y+=t.caretPadding+t.caretSize:"bottom"===t.yAlign?i.y-=e.height+t.caretPadding+t.caretSize:i.y-=e.height/2,"center"===t.yAlign?"left"===t.xAlign?i.x+=t.caretPadding+t.caretSize:"right"===t.xAlign&&(i.x-=t.caretPadding+t.caretSize):"left"===t.xAlign?i.x-=t.cornerRadius+t.caretPadding:"right"===t.xAlign&&(i.x+=t.cornerRadius+t.caretPadding),i},drawCaret:function(t,e,a,s){var o,n,r,l,h,c,u=this._view,d=this._chart.ctx;"center"===u.yAlign?("left"===u.xAlign?(o=t.x,n=o-u.caretSize,r=o):(o=t.x+e.width,n=o+u.caretSize,r=o),h=t.y+e.height/2,l=h-u.caretSize,c=h+u.caretSize):("left"===u.xAlign?(o=t.x+u.cornerRadius,n=o+u.caretSize,r=n+u.caretSize):"right"===u.xAlign?(o=t.x+e.width-u.cornerRadius,n=o-u.caretSize,r=n-u.caretSize):(n=t.x+e.width/2,o=n-u.caretSize,r=n+u.caretSize),"top"===u.yAlign?(l=t.y,h=l-u.caretSize,c=l):(l=t.y+e.height,h=l+u.caretSize,c=l));var f=i.color(u.backgroundColor);d.fillStyle=f.alpha(a*f.alpha()).rgbString(),d.beginPath(),d.moveTo(o,l),d.lineTo(n,h),d.lineTo(r,c),d.closePath(),d.fill()},drawTitle:function(t,e,a,s){if(e.title.length){a.textAlign=e._titleAlign,a.textBaseline="top";var o=i.color(e.titleColor);a.fillStyle=o.alpha(s*o.alpha()).rgbString(),a.font=i.fontString(e.titleFontSize,e._titleFontStyle,e._titleFontFamily),i.each(e.title,function(i,s){a.fillText(i,t.x,t.y),t.y+=e.titleFontSize+e.titleSpacing,s+1===e.title.length&&(t.y+=e.titleMarginBottom-e.titleSpacing)})}},drawBody:function(t,e,a,s){a.textAlign=e._bodyAlign,a.textBaseline="top";var o=i.color(e.bodyColor);a.fillStyle=o.alpha(s*o.alpha()).rgbString(),a.font=i.fontString(e.bodyFontSize,e._bodyFontStyle,e._bodyFontFamily),i.each(e.beforeBody,function(i){a.fillText(i,t.x,t.y),t.y+=e.bodyFontSize+e.bodySpacing}),i.each(e.body,function(o,n){"single"!==this._options.tooltips.mode&&(a.fillStyle=i.color(e.legendColorBackground).alpha(s).rgbaString(),a.fillRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),a.strokeStyle=i.color(e.labelColors[n].borderColor).alpha(s).rgbaString(),a.strokeRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),a.fillStyle=i.color(e.labelColors[n].backgroundColor).alpha(s).rgbaString(),a.fillRect(t.x+1,t.y+1,e.bodyFontSize-2,e.bodyFontSize-2),a.fillStyle=i.color(e.bodyColor).alpha(s).rgbaString()),a.fillText(o,t.x+("single"!==this._options.tooltips.mode?e.bodyFontSize+2:0),t.y),t.y+=e.bodyFontSize+e.bodySpacing},this),i.each(e.afterBody,function(i){a.fillText(i,t.x,t.y),t.y+=e.bodyFontSize}),t.y-=e.bodySpacing},drawFooter:function(t,e,a,s){if(e.footer.length){t.y+=e.footerMarginTop,a.textAlign=e._footerAlign,a.textBaseline="top";var o=i.color(e.footerColor);a.fillStyle=o.alpha(s*o.alpha()).rgbString(),a.font=i.fontString(e.footerFontSize,e._footerFontStyle,e._footerFontFamily),i.each(e.footer,function(i){a.fillText(i,t.x,t.y),t.y+=e.footerFontSize+e.footerSpacing})}},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var a=e.caretPadding,s=this.getTooltipSize(e),o={x:e.x,y:e.y},n=Math.abs(e.opacity<.001)?0:e.opacity;if(this._options.tooltips.enabled){var r=i.color(e.backgroundColor);t.fillStyle=r.alpha(n*r.alpha()).rgbString(),i.drawRoundedRectangle(t,o.x,o.y,s.width,s.height,e.cornerRadius),t.fill(),this.drawCaret(o,s,n,a),o.x+=e.xPadding,o.y+=e.yPadding,this.drawTitle(o,e,t,n),this.drawBody(o,e,t,n),this.drawFooter(o,e,t,n)}}}})}},{}],33:[function(t,e,i){"use strict";e.exports=function(t,e){var i=t.helpers;t.defaults.global.elements.arc={backgroundColor:t.defaults.global.defaultColor,borderColor:"#fff",borderWidth:2},t.elements.Arc=t.Element.extend({inLabelRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)n;)n+=2*Math.PI;for(;s.angle>n;)s.angle-=2*Math.PI;for(;s.angle=o&&s.angle<=n,l=s.distance>=a.innerRadius&&s.distance<=a.outerRadius;return r&&l}return!1},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t=this._chart.ctx,e=this._view;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,e.startAngle,e.endAngle),t.arc(e.x,e.y,e.innerRadius,e.endAngle,e.startAngle,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],34:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.elements.line={tension:.4,backgroundColor:t.defaults.global.defaultColor,borderWidth:3,borderColor:t.defaults.global.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",fill:!0},t.elements.Line=t.Element.extend({lineToNextPoint:function(t,e,i,a,s){var o=this._chart.ctx;e._view.skip?a.call(this,t,e,i):t._view.skip?s.call(this,t,e,i):0===e._view.tension?o.lineTo(e._view.x,e._view.y):o.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y)},draw:function(){function i(t){n._view.skip||r._view.skip?t&&o.lineTo(a._view.scaleZero.x,a._view.scaleZero.y):o.bezierCurveTo(r._view.controlPointNextX,r._view.controlPointNextY,n._view.controlPointPreviousX,n._view.controlPointPreviousY,n._view.x,n._view.y)}var a=this,s=this._view,o=this._chart.ctx,n=this._children[0],r=this._children[this._children.length-1];o.save(),this._children.length>0&&s.fill&&(o.beginPath(),e.each(this._children,function(t,i){var a=e.previousItem(this._children,i),n=e.nextItem(this._children,i);0===i?(this._loop?o.moveTo(s.scaleZero.x,s.scaleZero.y):o.moveTo(t._view.x,s.scaleZero),t._view.skip?this._loop||o.moveTo(n._view.x,this._view.scaleZero):o.lineTo(t._view.x,t._view.y)):this.lineToNextPoint(a,t,n,function(t,e,i){this._loop?o.lineTo(this._view.scaleZero.x,this._view.scaleZero.y):(o.lineTo(t._view.x,this._view.scaleZero),o.moveTo(i._view.x,this._view.scaleZero))},function(t,e){o.lineTo(e._view.x,e._view.y)})},this),this._loop?i(!0):(o.lineTo(this._children[this._children.length-1]._view.x,s.scaleZero),o.lineTo(this._children[0]._view.x,s.scaleZero)),o.fillStyle=s.backgroundColor||t.defaults.global.defaultColor,o.closePath(),o.fill()),o.lineCap=s.borderCapStyle||t.defaults.global.elements.line.borderCapStyle,o.setLineDash&&o.setLineDash(s.borderDash||t.defaults.global.elements.line.borderDash),o.lineDashOffset=s.borderDashOffset||t.defaults.global.elements.line.borderDashOffset,o.lineJoin=s.borderJoinStyle||t.defaults.global.elements.line.borderJoinStyle,o.lineWidth=s.borderWidth||t.defaults.global.elements.line.borderWidth,o.strokeStyle=s.borderColor||t.defaults.global.defaultColor,o.beginPath(),e.each(this._children,function(t,i){var a=e.previousItem(this._children,i),s=e.nextItem(this._children,i);0===i?o.moveTo(t._view.x,t._view.y):this.lineToNextPoint(a,t,s,function(t,e,i){o.moveTo(i._view.x,i._view.y)},function(t,e){o.moveTo(e._view.x,e._view.y)})},this),this._loop&&this._children.length>0&&i(),o.stroke(),o.restore()}})}},{}],35:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.elements.point={radius:3,pointStyle:"circle",backgroundColor:t.defaults.global.defaultColor,borderWidth:1,borderColor:t.defaults.global.defaultColor,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},t.elements.Point=t.Element.extend({inRange:function(t,e){var i=this._view;if(i){var a=i.hitRadius+i.radius;return Math.pow(t-i.x,2)+Math.pow(e-i.y,2)0){a.strokeStyle=i.borderColor||t.defaults.global.defaultColor,a.lineWidth=e.getValueOrDefault(i.borderWidth,t.defaults.global.elements.point.borderWidth),a.fillStyle=i.backgroundColor||t.defaults.global.defaultColor;var s,o,n=i.radius;switch(i.pointStyle){default:a.beginPath(),a.arc(i.x,i.y,n,0,2*Math.PI),a.closePath(),a.fill();break;case"triangle":a.beginPath();var r=3*n/Math.sqrt(3),l=r*Math.sqrt(3)/2;a.moveTo(i.x-r/2,i.y+l/3),a.lineTo(i.x+r/2,i.y+l/3),a.lineTo(i.x,i.y-2*l/3),a.closePath(),a.fill();break;case"rect":a.fillRect(i.x-1/Math.SQRT2*n,i.y-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n),a.strokeRect(i.x-1/Math.SQRT2*n,i.y-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n);break;case"rectRot":a.translate(i.x,i.y),a.rotate(Math.PI/4),a.fillRect(-1/Math.SQRT2*n,-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n),a.strokeRect(-1/Math.SQRT2*n,-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n),a.setTransform(1,0,0,1,0,0);break;case"cross":a.beginPath(),a.moveTo(i.x,i.y+n),a.lineTo(i.x,i.y-n),a.moveTo(i.x-n,i.y),a.lineTo(i.x+n,i.y),a.closePath();break;case"crossRot":a.beginPath(),s=Math.cos(Math.PI/4)*n,o=Math.sin(Math.PI/4)*n,a.moveTo(i.x-s,i.y-o),a.lineTo(i.x+s,i.y+o),a.moveTo(i.x-s,i.y+o),a.lineTo(i.x+s,i.y-o),a.closePath();break;case"star":a.beginPath(),a.moveTo(i.x,i.y+n),a.lineTo(i.x,i.y-n),a.moveTo(i.x-n,i.y),a.lineTo(i.x+n,i.y),s=Math.cos(Math.PI/4)*n,o=Math.sin(Math.PI/4)*n,a.moveTo(i.x-s,i.y-o),a.lineTo(i.x+s,i.y+o),a.moveTo(i.x-s,i.y+o),a.lineTo(i.x+s,i.y-o),a.closePath();break;case"line":a.beginPath(),a.moveTo(i.x-n,i.y),a.lineTo(i.x+n,i.y),a.closePath();break;case"dash":a.beginPath(),a.moveTo(i.x,i.y),a.lineTo(i.x+n,i.y),a.closePath()}a.stroke()}}}})}},{}],36:[function(t,e,i){"use strict";e.exports=function(t){t.helpers;t.defaults.global.elements.rectangle={backgroundColor:t.defaults.global.defaultColor,borderWidth:0,borderColor:t.defaults.global.defaultColor,borderSkipped:"bottom"},t.elements.Rectangle=t.Element.extend({draw:function(){function t(t){return l[(c+t)%4]}var e=this._chart.ctx,i=this._view,a=i.width/2,s=i.x-a,o=i.x+a,n=i.base-(i.base-i.y),r=i.borderWidth/2;i.borderWidth&&(s+=r,o-=r,n+=r),e.beginPath(),e.fillStyle=i.backgroundColor,e.strokeStyle=i.borderColor,e.lineWidth=i.borderWidth;var l=[[s,i.base],[s,n],[o,n],[o,i.base]],h=["bottom","left","top","right"],c=h.indexOf(i.borderSkipped,0);-1===c&&(c=0),e.moveTo.apply(e,t(0));for(var u=1;4>u;u++)e.lineTo.apply(e,t(u));e.fill(),i.borderWidth&&e.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=this._view,a=!1;return i&&(a=i.y=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.y&&e<=i.base:t>=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.base&&e<=i.y),a},inLabelRange:function(t){var e=this._view;return e?t>=e.x-e.width/2&&t<=e.x+e.width/2:!1},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],37:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"bottom"},a=t.Scale.extend({buildTicks:function(t){this.startIndex=0,this.endIndex=this.chart.data.labels.length;var i;void 0!==this.options.ticks.min&&(i=e.indexOf(this.chart.data.labels,this.options.ticks.min),this.startIndex=-1!==i?i:this.startIndex),void 0!==this.options.ticks.max&&(i=e.indexOf(this.chart.data.labels,this.options.ticks.max),this.endIndex=-1!==i?i:this.endIndex),this.ticks=0===this.startIndex&&this.endIndex===this.chart.data.labels.length?this.chart.data.labels:this.chart.data.labels.slice(this.startIndex,this.endIndex+1)},getLabelForIndex:function(t,e){return this.ticks[t]},getPixelForValue:function(t,e,i,a){var s=Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1);if(this.isHorizontal()){var o=this.width-(this.paddingLeft+this.paddingRight),n=o/s,r=n*(e-this.startIndex)+this.paddingLeft;return this.options.gridLines.offsetGridLines&&a&&(r+=n/2),this.left+Math.round(r)}var l=this.height-(this.paddingTop+this.paddingBottom),h=l/s,c=h*(e-this.startIndex)+this.paddingTop;return this.options.gridLines.offsetGridLines&&a&&(c+=h/2),this.top+Math.round(c)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.startIndex,null,e)}});t.scaleService.registerScaleType("category",a,i)}},{}],38:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(t,i,a){var s=a[1]-a[0];Math.abs(s)>1&&t!==Math.floor(t)&&(s=t-Math.floor(t));var o=e.log10(Math.abs(s)),n="";if(0!==t){var r=-1*Math.floor(o);r=Math.max(Math.min(r,20),0),n=t.toFixed(r)}else n="0";return n}}},a=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={},i=!1,a=!1;e.each(this.chart.data.datasets,function(s){void 0===t[s.type]&&(t[s.type]={positiveValues:[],negativeValues:[]});var o=t[s.type].positiveValues,n=t[s.type].negativeValues;e.isDatasetVisible(s)&&(this.isHorizontal()?s.xAxisID===this.id:s.yAxisID===this.id)&&e.each(s.data,function(t,e){var s=+this.getRightValue(t);isNaN(s)||(o[e]=o[e]||0,n[e]=n[e]||0,this.options.relativePoints?o[e]=100:0>s?(a=!0,n[e]+=s):(i=!0,o[e]+=s))},this)},this),e.each(t,function(t){var i=t.positiveValues.concat(t.negativeValues),a=e.min(i),s=e.max(i);this.min=null===this.min?a:Math.min(this.min,a),this.max=null===this.max?s:Math.max(this.max,s)},this)}else e.each(this.chart.data.datasets,function(t){e.isDatasetVisible(t)&&(this.isHorizontal()?t.xAxisID===this.id:t.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);if(this.options.ticks.beginAtZero){var s=e.sign(this.min),o=e.sign(this.max);0>s&&0>o?this.max=0:s>0&&o>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.min--,this.max++)},buildTicks:function(){this.ticks=[];var i;if(this.isHorizontal())i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.width/50));else{var a=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize);i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.height/(2*a)))}i=Math.max(2,i);var s,o=this.options.ticks.fixedStepSize&&this.options.ticks.fixedStepSize>0||this.options.ticks.stepSize&&this.options.ticks.stepSize>0;if(o)s=e.getValueOrDefault(this.options.ticks.fixedStepSize,this.options.ticks.stepSize);else{var n=e.niceNum(this.max-this.min,!1);s=e.niceNum(n/(i-1),!0)}var r=Math.floor(this.min/s)*s,l=Math.ceil(this.max/s)*s,h=(l-r)/s;h=e.almostEquals(h,Math.round(h),s/1e3)?Math.round(h):Math.ceil(h),this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:r);for(var c=1;h>c;++c)this.ticks.push(r+c*s);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:l),("left"===this.options.position||"right"===this.options.position)&&this.ticks.reverse(),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},convertTicksToLabels:function(){this.ticksAsNumbers=this.ticks.slice(),this.zeroLineIndex=this.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(this)},getPixelForValue:function(t,e,i,a){var s,o=+this.getRightValue(t),n=this.end-this.start;if(this.isHorizontal()){var r=this.width-(this.paddingLeft+this.paddingRight);return s=this.left+r/n*(o-this.start),Math.round(s+this.paddingLeft)}var l=this.height-(this.paddingTop+this.paddingBottom);return s=this.bottom-this.paddingBottom-l/n*(o-this.start),Math.round(s)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticksAsNumbers[t],null,null,e)}});t.scaleService.registerScaleType("linear",a,i)}},{}],39:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(e,i,a){var s=e/Math.pow(10,Math.floor(t.helpers.log10(e)));return 1===s||2===s||5===s||0===i||i===a.length-1?e.toExponential():""}}},a=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={};e.each(this.chart.data.datasets,function(i){e.isDatasetVisible(i)&&(this.isHorizontal()?i.xAxisID===this.id:i.yAxisID===this.id)&&(void 0===t[i.type]&&(t[i.type]=[]),e.each(i.data,function(e,a){var s=t[i.type],o=+this.getRightValue(e);isNaN(o)||(s[a]=s[a]||0,this.options.relativePoints?s[a]=100:s[a]+=o)},this))},this),e.each(t,function(t){var i=e.min(t),a=e.max(t);this.min=null===this.min?i:Math.min(this.min,i),this.max=null===this.max?a:Math.max(this.max,a)},this)}else e.each(this.chart.data.datasets,function(t){e.isDatasetVisible(t)&&(this.isHorizontal()?t.xAxisID===this.id:t.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);this.min=void 0!==this.options.ticks.min?this.options.ticks.min:this.min,this.max=void 0!==this.options.ticks.max?this.options.ticks.max:this.max,this.min===this.max&&(0!==this.min&&null!==this.min?(this.min=Math.pow(10,Math.floor(e.log10(this.min))-1),this.max=Math.pow(10,Math.floor(e.log10(this.max))+1)):(this.min=1,this.max=10))},buildTicks:function(){this.ticks=[];for(var t=void 0!==this.options.ticks.min?this.options.ticks.min:Math.pow(10,Math.floor(e.log10(this.min)));tthis.max&&(this.max=i))},this)},this),this.options.ticks.beginAtZero){var t=e.sign(this.min),i=e.sign(this.max);0>t&&0>i?this.max=0:t>0&&i>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.min--,this.max++)},buildTicks:function(){this.ticks=[];var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),a=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.drawingArea/(1.5*i)));a=Math.max(2,a);var s=e.niceNum(this.max-this.min,!1),o=e.niceNum(s/(a-1),!0),n=Math.floor(this.min/o)*o,r=Math.ceil(this.max/o)*o,l=Math.ceil((r-n)/o);this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:n);for(var h=1;l>h;++h)this.ticks.push(n+h*o);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:r),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),this.zeroLineIndex=this.ticks.indexOf(0)},convertTicksToLabels:function(){t.Scale.prototype.convertTicksToLabels.call(this),this.pointLabels=this.chart.data.labels.map(this.options.pointLabels.callback,this)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var i,a,s,o,n,r,l,h,c,u,d,f,g=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),m=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),p=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),b=e.fontString(g,m,p),v=e.min([this.height/2-g-5,this.width/2]),x=this.width,y=0;for(this.ctx.font=b,a=0;ax&&(x=i.x+o,n=a),i.x-ox&&(x=i.x+s,n=a):a>this.getValueCount()/2&&i.x-s0||this.options.reverse){var o=this.getDistanceFromCenterForValue(this.ticks[s]),n=this.yCenter-o;if(this.options.gridLines.display)if(i.strokeStyle=this.options.gridLines.color,i.lineWidth=this.options.gridLines.lineWidth,this.options.lineArc)i.beginPath(),i.arc(this.xCenter,this.yCenter,o,0,2*Math.PI),i.closePath(),i.stroke();else{i.beginPath();for(var r=0;r=0;a--){if(this.options.angleLines.display){var s=this.getPointPosition(a,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max));i.beginPath(),i.moveTo(this.xCenter,this.yCenter),i.lineTo(s.x,s.y),i.stroke(),i.closePath()}var o=this.getPointPosition(a,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max)+5),n=e.getValueOrDefault(this.options.pointLabels.fontColor,t.defaults.global.defaultFontColor),r=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),l=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),h=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),c=e.fontString(r,l,h);i.font=c,i.fillStyle=n;var u=this.pointLabels.length,d=this.pointLabels.length/2,f=d/2,g=f>a||a>u-f,m=a===f||a===u-f;0===a?i.textAlign="center":a===d?i.textAlign="center":d>a?i.textAlign="left":i.textAlign="right",m?i.textBaseline="middle":g?i.textBaseline="bottom":i.textBaseline="top",i.fillText(this.pointLabels[a]?this.pointLabels[a]:"",o.x,o.y)}}}}});t.scaleService.registerScaleType("radialLinear",a,i)}},{}],41:[function(t,e,i){"use strict";var a=t("moment");a="function"==typeof a?a:window.moment,e.exports=function(t){var e=t.helpers,i={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12]
-},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},s={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},o=t.Scale.extend({initialize:function(){if(!a)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return this.labelMoments[t][e]},determineDataLimits:function(){this.labelMoments=[];var t=[];this.chart.data.labels&&this.chart.data.labels.length>0?(e.each(this.chart.data.labels,function(e,i){var a=this.parseTime(e);this.options.time.round&&a.startOf(this.options.time.round),t.push(a)},this),this.firstTick=a.min.call(this,t),this.lastTick=a.max.call(this,t)):(this.firstTick=null,this.lastTick=null),e.each(this.chart.data.datasets,function(i,s){var o=[];"object"==typeof i.data[0]?e.each(i.data,function(t,e){var i=this.parseTime(this.getRightValue(t));this.options.time.round&&i.startOf(this.options.time.round),o.push(i),this.firstTick=null!==this.firstTick?a.min(this.firstTick,i):i,this.lastTick=null!==this.lastTick?a.max(this.lastTick,i):i},this):o=t,this.labelMoments.push(o)},this),this.options.time.min&&(this.firstTick=this.parseTime(this.options.time.min)),this.options.time.max&&(this.lastTick=this.parseTime(this.options.time.max)),this.firstTick=(this.firstTick||a()).clone(),this.lastTick=(this.lastTick||a()).clone()},buildTicks:function(a){this.ctx.save();var s=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),o=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),n=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),r=e.fontString(s,o,n);if(this.ctx.font=r,this.ticks=[],this.unitScale=1,this.scaleSizeInUnits=0,this.options.time.unit)this.tickUnit=this.options.time.unit||"day",this.displayFormat=this.options.time.displayFormats[this.tickUnit],this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,1);else{var l=this.isHorizontal()?this.width-(this.paddingLeft+this.paddingRight):this.height-(this.paddingTop+this.paddingBottom),h=this.tickFormatFunction(this.firstTick,0,[]),c=this.ctx.measureText(h).width,u=Math.cos(e.toRadians(this.options.ticks.maxRotation)),d=Math.sin(e.toRadians(this.options.ticks.maxRotation));c=c*u+s*d;var f=l/c;this.tickUnit="millisecond",this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.displayFormat=this.options.time.displayFormats[this.tickUnit];for(var g=0,m=i.units[g];g=Math.ceil(this.scaleSizeInUnits/f)){this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,m.steps[p]);break}break}if(m.maxStep===!1||Math.ceil(this.scaleSizeInUnits/f)=0)break;v%this.unitScale===0&&this.ticks.push(x)}(0!==this.ticks[this.ticks.length-1].diff(this.lastTick,this.tickUnit)||0===this.scaleSizeInUnits)&&(this.options.time.max?(this.ticks.push(this.lastTick.clone()),this.scaleSizeInUnits=this.lastTick.diff(this.ticks[0],this.tickUnit,!0)):(this.scaleSizeInUnits=Math.ceil(this.scaleSizeInUnits/this.unitScale)*this.unitScale,this.ticks.push(this.firstTick.clone().add(this.scaleSizeInUnits,this.tickUnit)),this.lastTick=this.ticks[this.ticks.length-1].clone())),this.ctx.restore()},getLabelForIndex:function(t,e){var i=this.chart.data.labels&&te||t[3]&&t[3]<1?d(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function d(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function u(t,e){if(1>e||t[3]&&t[3]<1)return f(t,e);var i=Math.round(t[0]/255*100),s=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+i+"%, "+s+"%, "+a+"%)"}function f(t,e){var i=Math.round(t[0]/255*100),s=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgba("+i+"%, "+s+"%, "+a+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?m(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function m(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function b(t){return k[t.slice(0,3)]}function v(t,e,i){return Math.min(Math.max(e,t),i)}function x(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y=t("color-name");e.exports={getRgba:s,getHsla:a,getRgb:n,getHsl:r,getHwb:o,getAlpha:h,hexString:l,rgbString:c,rgbaString:d,percentString:u,percentaString:f,hslString:g,hslaString:m,hwbString:p,keyword:b};var k={};for(var _ in y)k[y[_]]=_},{"color-name":5}],2:[function(t,e,i){var s=t("color-convert"),a=t("chartjs-color-string"),o=function(t){if(t instanceof o)return t;if(!(this instanceof o))return new o(t);if(this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},"string"==typeof t){var e=a.getRgba(t);if(e)this.setValues("rgb",e);else if(e=a.getHsla(t))this.setValues("hsl",e);else{if(!(e=a.getHwb(t)))throw new Error('Unable to parse color from string "'+t+'"');this.setValues("hwb",e)}}else if("object"==typeof t){var e=t;if(void 0!==e.r||void 0!==e.red)this.setValues("rgb",e);else if(void 0!==e.l||void 0!==e.lightness)this.setValues("hsl",e);else if(void 0!==e.v||void 0!==e.value)this.setValues("hsv",e);else if(void 0!==e.w||void 0!==e.whiteness)this.setValues("hwb",e);else{if(void 0===e.c&&void 0===e.cyan)throw new Error("Unable to parse color from object "+JSON.stringify(t));this.setValues("cmyk",e)}}};o.prototype={rgb:function(t){return this.setSpace("rgb",arguments)},hsl:function(t){return this.setSpace("hsl",arguments)},hsv:function(t){return this.setSpace("hsv",arguments)},hwb:function(t){return this.setSpace("hwb",arguments)},cmyk:function(t){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){return 1!==this.values.alpha?this.values.hwb.concat([this.values.alpha]):this.values.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values.rgb;return t.concat([this.values.alpha])},hslaArray:function(){var t=this.values.hsl;return t.concat([this.values.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return a.hexString(this.values.rgb)},rgbString:function(){return a.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return a.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return a.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return a.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return a.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return a.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return a.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){return this.values.rgb[0]<<16|this.values.rgb[1]<<8|this.values.rgb[2]},luminosity:function(){for(var t=this.values.rgb,e=[],i=0;i=s?s/12.92:Math.pow((s+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),i=t.luminosity();return e>i?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){return this.values.hsl[2]+=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},darken:function(t){return this.values.hsl[2]-=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},saturate:function(t){return this.values.hsl[1]+=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},desaturate:function(t){return this.values.hsl[1]-=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},whiten:function(t){return this.values.hwb[1]+=this.values.hwb[1]*t,this.setValues("hwb",this.values.hwb),this},blacken:function(t){return this.values.hwb[2]+=this.values.hwb[2]*t,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){return this.setValues("alpha",this.values.alpha-this.values.alpha*t),this},opaquer:function(t){return this.setValues("alpha",this.values.alpha+this.values.alpha*t),this},rotate:function(t){var e=this.values.hsl[0];return e=(e+t)%360,e=0>e?360+e:e,this.values.hsl[0]=e,this.setValues("hsl",this.values.hsl),this},mix:function(t,e){e=1-(null==e?.5:e);for(var i=2*e-1,s=this.alpha()-t.alpha(),a=((i*s==-1?i:(i+s)/(1+i*s))+1)/2,o=1-a,n=this.rgbArray(),r=t.rgbArray(),h=0;he&&(e+=360),s=(r+h)/2,i=h==r?0:.5>=s?l/(h+r):l/(2-h-r),[e,100*i,100*s]}function a(t){var e,i,s,a=t[0],o=t[1],n=t[2],r=Math.min(a,o,n),h=Math.max(a,o,n),l=h-r;return i=0==h?0:l/h*1e3/10,h==r?e=0:a==h?e=(o-n)/l:o==h?e=2+(n-a)/l:n==h&&(e=4+(a-o)/l),e=Math.min(60*e,360),0>e&&(e+=360),s=h/255*1e3/10,[e,i,s]}function o(t){var e=t[0],i=t[1],a=t[2],o=s(t)[0],n=1/255*Math.min(e,Math.min(i,a)),a=1-1/255*Math.max(e,Math.max(i,a));return[o,100*n,100*a]}function n(t){var e,i,s,a,o=t[0]/255,n=t[1]/255,r=t[2]/255;return a=Math.min(1-o,1-n,1-r),e=(1-o-a)/(1-a)||0,i=(1-n-a)/(1-a)||0,s=(1-r-a)/(1-a)||0,[100*e,100*i,100*s,100*a]}function h(t){return X[JSON.stringify(t)]}function l(t){var e=t[0]/255,i=t[1]/255,s=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,s=s>.04045?Math.pow((s+.055)/1.055,2.4):s/12.92;var a=.4124*e+.3576*i+.1805*s,o=.2126*e+.7152*i+.0722*s,n=.0193*e+.1192*i+.9505*s;return[100*a,100*o,100*n]}function c(t){var e,i,s,a=l(t),o=a[0],n=a[1],r=a[2];return o/=95.047,n/=100,r/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*n-16,i=500*(o-n),s=200*(n-r),[e,i,s]}function d(t){return B(c(t))}function u(t){var e,i,s,a,o,n=t[0]/360,r=t[1]/100,h=t[2]/100;if(0==r)return o=255*h,[o,o,o];i=.5>h?h*(1+r):h+r-h*r,e=2*h-i,a=[0,0,0];for(var l=0;3>l;l++)s=n+1/3*-(l-1),0>s&&s++,s>1&&s--,o=1>6*s?e+6*(i-e)*s:1>2*s?i:2>3*s?e+(i-e)*(2/3-s)*6:e,a[l]=255*o;return a}function f(t){var e,i,s=t[0],a=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,a*=1>=o?o:2-o,i=(o+a)/2,e=2*a/(o+a),[s,100*e,100*i])}function m(t){return o(u(t))}function p(t){return n(u(t))}function v(t){return h(u(t))}function x(t){var e=t[0]/60,i=t[1]/100,s=t[2]/100,a=Math.floor(e)%6,o=e-Math.floor(e),n=255*s*(1-i),r=255*s*(1-i*o),h=255*s*(1-i*(1-o)),s=255*s;switch(a){case 0:return[s,h,n];case 1:return[r,s,n];case 2:return[n,s,h];case 3:return[n,r,s];case 4:return[h,n,s];case 5:return[s,n,r]}}function y(t){var e,i,s=t[0],a=t[1]/100,o=t[2]/100;return i=(2-a)*o,e=a*o,e/=1>=i?i:2-i,e=e||0,i/=2,[s,100*e,100*i]}function k(t){return o(x(t))}function _(t){return n(x(t))}function S(t){return h(x(t))}function w(t){var e,i,s,a,o=t[0]/360,n=t[1]/100,h=t[2]/100,l=n+h;switch(l>1&&(n/=l,h/=l),e=Math.floor(6*o),i=1-h,s=6*o-e,0!=(1&e)&&(s=1-s),a=n+s*(i-n),e){default:case 6:case 0:r=i,g=a,b=n;break;case 1:r=a,g=i,b=n;break;case 2:r=n,g=i,b=a;break;case 3:r=n,g=a,b=i;break;case 4:r=a,g=n,b=i;break;case 5:r=i,g=n,b=a}return[255*r,255*g,255*b]}function D(t){return s(w(t))}function M(t){return a(w(t))}function C(t){return n(w(t))}function A(t){return h(w(t))}function I(t){var e,i,s,a=t[0]/100,o=t[1]/100,n=t[2]/100,r=t[3]/100;return e=1-Math.min(1,a*(1-r)+r),i=1-Math.min(1,o*(1-r)+r),s=1-Math.min(1,n*(1-r)+r),[255*e,255*i,255*s]}function P(t){return s(I(t))}function T(t){return a(I(t))}function F(t){return o(I(t))}function O(t){return h(I(t))}function V(t){var e,i,s,a=t[0]/100,o=t[1]/100,n=t[2]/100;return e=3.2406*a+-1.5372*o+n*-.4986,i=a*-.9689+1.8758*o+.0415*n,s=.0557*a+o*-.204+1.057*n,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,s=s>.0031308?1.055*Math.pow(s,1/2.4)-.055:s=12.92*s,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),s=Math.min(Math.max(0,s),1),[255*e,255*i,255*s]}function R(t){var e,i,s,a=t[0],o=t[1],n=t[2];return a/=95.047,o/=100,n/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*o-16,i=500*(a-o),s=200*(o-n),[e,i,s]}function W(t){return B(R(t))}function L(t){var e,i,s,a,o=t[0],n=t[1],r=t[2];return 8>=o?(i=100*o/903.3,a=7.787*(i/100)+16/116):(i=100*Math.pow((o+16)/116,3),a=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(n/500+a-16/116)/7.787:95.047*Math.pow(n/500+a,3),s=.008859>=s/108.883?s=108.883*(a-r/200-16/116)/7.787:108.883*Math.pow(a-r/200,3),[e,i,s]}function B(t){var e,i,s,a=t[0],o=t[1],n=t[2];return e=Math.atan2(n,o),i=360*e/2/Math.PI,0>i&&(i+=360),s=Math.sqrt(o*o+n*n),[a,s,i]}function z(t){return V(L(t))}function Y(t){var e,i,s,a=t[0],o=t[1],n=t[2];return s=n/360*2*Math.PI,e=o*Math.cos(s),i=o*Math.sin(s),[a,e,i]}function H(t){return L(Y(t))}function N(t){return z(Y(t))}function E(t){return J[t]}function U(t){return s(E(t))}function j(t){return a(E(t))}function G(t){return o(E(t))}function q(t){return n(E(t))}function Z(t){return c(E(t))}function Q(t){return l(E(t))}e.exports={rgb2hsl:s,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:n,rgb2keyword:h,rgb2xyz:l,rgb2lab:c,rgb2lch:d,hsl2rgb:u,hsl2hsv:f,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:v,hsv2rgb:x,hsv2hsl:y,hsv2hwb:k,hsv2cmyk:_,hsv2keyword:S,hwb2rgb:w,hwb2hsl:D,hwb2hsv:M,hwb2cmyk:C,hwb2keyword:A,cmyk2rgb:I,cmyk2hsl:P,cmyk2hsv:T,cmyk2hwb:F,cmyk2keyword:O,keyword2rgb:E,keyword2hsl:U,keyword2hsv:j,keyword2hwb:G,keyword2cmyk:q,keyword2lab:Z,keyword2xyz:Q,xyz2rgb:V,xyz2lab:R,xyz2lch:W,lab2xyz:L,lab2rgb:z,lab2lch:B,lch2lab:Y,lch2xyz:H,lch2rgb:N};var J={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},X={};for(var $ in J)X[JSON.stringify(J[$])]=$},{}],4:[function(t,e,i){var s=t("./conversions"),a=function(){return new l};for(var o in s){a[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),s[t](e)}}(o);var n=/(\w+)2(\w+)/.exec(o),r=n[1],h=n[2];a[r]=a[r]||{},a[r][h]=a[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=s[t](e);if("string"==typeof i||void 0===i)return i;for(var a=0;a0)for(i in ls)s=ls[i],a=e[s],g(a)||(t[s]=a);return t}function p(t){m(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),cs===!1&&(cs=!0,i.updateOffset(this),cs=!1)}function b(t){return t instanceof p||null!=t&&null!=t._isAMomentObject}function v(t){return 0>t?Math.ceil(t):Math.floor(t)}function x(t){var e=+t,i=0;return 0!==e&&isFinite(e)&&(i=v(e)),i}function y(t,e,i){var s,a=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),n=0;for(s=0;a>s;s++)(i&&t[s]!==e[s]||!i&&x(t[s])!==x(e[s]))&&n++;return n+o}function k(t){i.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function _(t,e){var s=!0;return h(function(){return null!=i.deprecationHandler&&i.deprecationHandler(null,t),s&&(k(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),s=!1),e.apply(this,arguments)},e)}function S(t,e){null!=i.deprecationHandler&&i.deprecationHandler(t,e),ds[t]||(k(e),ds[t]=!0)}function w(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function D(t){return"[object Object]"===Object.prototype.toString.call(t)}function M(t){var e,i;for(i in t)e=t[i],w(e)?this[i]=e:this["_"+i]=e;this._config=t,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function C(t,e){var i,s=h({},t);for(i in e)r(e,i)&&(D(t[i])&&D(e[i])?(s[i]={},h(s[i],t[i]),h(s[i],e[i])):null!=e[i]?s[i]=e[i]:delete s[i]);return s}function A(t){null!=t&&this.set(t)}function I(t){return t?t.toLowerCase().replace("_","-"):t}function P(t){for(var e,i,s,a,o=0;o0;){if(s=T(a.slice(0,e).join("-")))return s;if(i&&i.length>=e&&y(a,i,!0)>=e-1)break;e--}o++}return null}function T(i){var s=null;if(!ms[i]&&"undefined"!=typeof e&&e&&e.exports)try{s=fs._abbr,t("./locale/"+i),F(s)}catch(a){}return ms[i]}function F(t,e){var i;return t&&(i=g(e)?R(t):O(t,e),i&&(fs=i)),fs._abbr}function O(t,e){return null!==e?(e.abbr=t,null!=ms[t]?(S("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),e=C(ms[t]._config,e)):null!=e.parentLocale&&(null!=ms[e.parentLocale]?e=C(ms[e.parentLocale]._config,e):S("parentLocaleUndefined","specified parentLocale is not defined yet")),ms[t]=new A(e),F(t),ms[t]):(delete ms[t],null)}function V(t,e){if(null!=e){var i;null!=ms[t]&&(e=C(ms[t]._config,e)),i=new A(e),i.parentLocale=ms[t],ms[t]=i,F(t)}else null!=ms[t]&&(null!=ms[t].parentLocale?ms[t]=ms[t].parentLocale:null!=ms[t]&&delete ms[t]);return ms[t]}function R(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return fs;if(!a(t)){if(e=T(t))return e;t=[t]}return P(t)}function W(){return us(ms)}function L(t,e){var i=t.toLowerCase();ps[i]=ps[i+"s"]=ps[e]=t}function B(t){return"string"==typeof t?ps[t]||ps[t.toLowerCase()]:void 0}function z(t){var e,i,s={};for(i in t)r(t,i)&&(e=B(i),e&&(s[e]=t[i]));return s}function Y(t,e){return function(s){return null!=s?(N(this,t,s),i.updateOffset(this,e),this):H(this,t)}}function H(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function N(t,e,i){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](i)}function E(t,e){var i;if("object"==typeof t)for(i in t)this.set(i,t[i]);else if(t=B(t),w(this[t]))return this[t](e);return this}function U(t,e,i){var s=""+Math.abs(t),a=e-s.length,o=t>=0;return(o?i?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+s}function j(t,e,i,s){var a=s;"string"==typeof s&&(a=function(){return this[s]()}),t&&(ys[t]=a),e&&(ys[e[0]]=function(){return U(a.apply(this,arguments),e[1],e[2])}),i&&(ys[i]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function G(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function q(t){var e,i,s=t.match(bs);for(e=0,i=s.length;i>e;e++)ys[s[e]]?s[e]=ys[s[e]]:s[e]=G(s[e]);return function(e){var a,o="";for(a=0;i>a;a++)o+=s[a]instanceof Function?s[a].call(e,t):s[a];return o}}function Z(t,e){return t.isValid()?(e=Q(e,t.localeData()),xs[e]=xs[e]||q(e),xs[e](t)):t.localeData().invalidDate()}function Q(t,e){function i(t){return e.longDateFormat(t)||t}var s=5;for(vs.lastIndex=0;s>=0&&vs.test(t);)t=t.replace(vs,i),vs.lastIndex=0,s-=1;return t}function J(t,e,i){Bs[t]=w(e)?e:function(t,s){return t&&i?i:e}}function X(t,e){return r(Bs,t)?Bs[t](e._strict,e._locale):new RegExp($(t))}function $(t){return K(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,s,a){return e||i||s||a}))}function K(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function tt(t,e){var i,s=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(s=function(t,i){i[e]=x(t)}),i=0;is;++s)o=l([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(o,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(o,"").toLocaleLowerCase();return i?"MMM"===e?(a=gs.call(this._shortMonthsParse,n),-1!==a?a:null):(a=gs.call(this._longMonthsParse,n),-1!==a?a:null):"MMM"===e?(a=gs.call(this._shortMonthsParse,n),-1!==a?a:(a=gs.call(this._longMonthsParse,n),-1!==a?a:null)):(a=gs.call(this._longMonthsParse,n),-1!==a?a:(a=gs.call(this._shortMonthsParse,n),-1!==a?a:null))}function rt(t,e,i){var s,a,o;if(this._monthsParseExact)return nt.call(this,t,e,i);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;12>s;s++){if(a=l([2e3,s]),i&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(a,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(a,"").replace(".","")+"$","i")),i||this._monthsParse[s]||(o="^"+this.months(a,"")+"|^"+this.monthsShort(a,""),this._monthsParse[s]=new RegExp(o.replace(".",""),"i")),i&&"MMMM"===e&&this._longMonthsParse[s].test(t))return s;if(i&&"MMM"===e&&this._shortMonthsParse[s].test(t))return s;if(!i&&this._monthsParse[s].test(t))return s}}function ht(t,e){var i;if(!t.isValid())return t;if("string"==typeof e)if(/^\d+$/.test(e))e=x(e);else if(e=t.localeData().monthsParse(e),"number"!=typeof e)return t;return i=Math.min(t.date(),st(t.year(),e)),
+t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,i),t}function lt(t){return null!=t?(ht(this,t),i.updateOffset(this,!0),this):H(this,"Month")}function ct(){return st(this.year(),this.month())}function dt(t){return this._monthsParseExact?(r(this,"_monthsRegex")||ft.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function ut(t){return this._monthsParseExact?(r(this,"_monthsRegex")||ft.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function ft(){function t(t,e){return e.length-t.length}var e,i,s=[],a=[],o=[];for(e=0;12>e;e++)i=l([2e3,e]),s.push(this.monthsShort(i,"")),a.push(this.months(i,"")),o.push(this.months(i,"")),o.push(this.monthsShort(i,""));for(s.sort(t),a.sort(t),o.sort(t),e=0;12>e;e++)s[e]=K(s[e]),a[e]=K(a[e]),o[e]=K(o[e]);this._monthsRegex=new RegExp("^("+o.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+s.join("|")+")","i")}function gt(t){var e,i=t._a;return i&&-2===d(t).overflow&&(e=i[Hs]<0||i[Hs]>11?Hs:i[Ns]<1||i[Ns]>st(i[Ys],i[Hs])?Ns:i[Es]<0||i[Es]>24||24===i[Es]&&(0!==i[Us]||0!==i[js]||0!==i[Gs])?Es:i[Us]<0||i[Us]>59?Us:i[js]<0||i[js]>59?js:i[Gs]<0||i[Gs]>999?Gs:-1,d(t)._overflowDayOfYear&&(Ys>e||e>Ns)&&(e=Ns),d(t)._overflowWeeks&&-1===e&&(e=qs),d(t)._overflowWeekday&&-1===e&&(e=Zs),d(t).overflow=e),t}function mt(t){var e,i,s,a,o,n,r=t._i,h=ta.exec(r)||ea.exec(r);if(h){for(d(t).iso=!0,e=0,i=sa.length;i>e;e++)if(sa[e][1].exec(h[1])){a=sa[e][0],s=sa[e][2]!==!1;break}if(null==a)return void(t._isValid=!1);if(h[3]){for(e=0,i=aa.length;i>e;e++)if(aa[e][1].exec(h[3])){o=(h[2]||" ")+aa[e][0];break}if(null==o)return void(t._isValid=!1)}if(!s&&null!=o)return void(t._isValid=!1);if(h[4]){if(!ia.exec(h[4]))return void(t._isValid=!1);n="Z"}t._f=a+(o||"")+(n||""),Pt(t)}else t._isValid=!1}function pt(t){var e=oa.exec(t._i);return null!==e?void(t._d=new Date(+e[1])):(mt(t),void(t._isValid===!1&&(delete t._isValid,i.createFromInputFallback(t))))}function bt(t,e,i,s,a,o,n){var r=new Date(t,e,i,s,a,o,n);return 100>t&&t>=0&&isFinite(r.getFullYear())&&r.setFullYear(t),r}function vt(t){var e=new Date(Date.UTC.apply(null,arguments));return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function xt(t){return yt(t)?366:365}function yt(t){return t%4===0&&t%100!==0||t%400===0}function kt(){return yt(this.year())}function _t(t,e,i){var s=7+e-i,a=(7+vt(t,0,s).getUTCDay()-e)%7;return-a+s-1}function St(t,e,i,s,a){var o,n,r=(7+i-s)%7,h=_t(t,s,a),l=1+7*(e-1)+r+h;return 0>=l?(o=t-1,n=xt(o)+l):l>xt(t)?(o=t+1,n=l-xt(t)):(o=t,n=l),{year:o,dayOfYear:n}}function wt(t,e,i){var s,a,o=_t(t.year(),e,i),n=Math.floor((t.dayOfYear()-o-1)/7)+1;return 1>n?(a=t.year()-1,s=n+Dt(a,e,i)):n>Dt(t.year(),e,i)?(s=n-Dt(t.year(),e,i),a=t.year()+1):(a=t.year(),s=n),{week:s,year:a}}function Dt(t,e,i){var s=_t(t,e,i),a=_t(t+1,e,i);return(xt(t)-s+a)/7}function Mt(t,e,i){return null!=t?t:null!=e?e:i}function Ct(t){var e=new Date(i.now());return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function At(t){var e,i,s,a,o=[];if(!t._d){for(s=Ct(t),t._w&&null==t._a[Ns]&&null==t._a[Hs]&&It(t),t._dayOfYear&&(a=Mt(t._a[Ys],s[Ys]),t._dayOfYear>xt(a)&&(d(t)._overflowDayOfYear=!0),i=vt(a,0,t._dayOfYear),t._a[Hs]=i.getUTCMonth(),t._a[Ns]=i.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=s[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[Es]&&0===t._a[Us]&&0===t._a[js]&&0===t._a[Gs]&&(t._nextDay=!0,t._a[Es]=0),t._d=(t._useUTC?vt:bt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Es]=24)}}function It(t){var e,i,s,a,o,n,r,h;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,n=4,i=Mt(e.GG,t._a[Ys],wt(Bt(),1,4).year),s=Mt(e.W,1),a=Mt(e.E,1),(1>a||a>7)&&(h=!0)):(o=t._locale._week.dow,n=t._locale._week.doy,i=Mt(e.gg,t._a[Ys],wt(Bt(),o,n).year),s=Mt(e.w,1),null!=e.d?(a=e.d,(0>a||a>6)&&(h=!0)):null!=e.e?(a=e.e+o,(e.e<0||e.e>6)&&(h=!0)):a=o),1>s||s>Dt(i,o,n)?d(t)._overflowWeeks=!0:null!=h?d(t)._overflowWeekday=!0:(r=St(i,s,a,o,n),t._a[Ys]=r.year,t._dayOfYear=r.dayOfYear)}function Pt(t){if(t._f===i.ISO_8601)return void mt(t);t._a=[],d(t).empty=!0;var e,s,a,o,n,r=""+t._i,h=r.length,l=0;for(a=Q(t._f,t._locale).match(bs)||[],e=0;e0&&d(t).unusedInput.push(n),r=r.slice(r.indexOf(s)+s.length),l+=s.length),ys[o]?(s?d(t).empty=!1:d(t).unusedTokens.push(o),it(o,s,t)):t._strict&&!s&&d(t).unusedTokens.push(o);d(t).charsLeftOver=h-l,r.length>0&&d(t).unusedInput.push(r),d(t).bigHour===!0&&t._a[Es]<=12&&t._a[Es]>0&&(d(t).bigHour=void 0),d(t).parsedDateParts=t._a.slice(0),d(t).meridiem=t._meridiem,t._a[Es]=Tt(t._locale,t._a[Es],t._meridiem),At(t),gt(t)}function Tt(t,e,i){var s;return null==i?e:null!=t.meridiemHour?t.meridiemHour(e,i):null!=t.isPM?(s=t.isPM(i),s&&12>e&&(e+=12),s||12!==e||(e=0),e):e}function Ft(t){var e,i,s,a,o;if(0===t._f.length)return d(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;ao)&&(s=o,i=e));h(t,i||e)}function Ot(t){if(!t._d){var e=z(t._i);t._a=n([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),At(t)}}function Vt(t){var e=new p(gt(Rt(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Rt(t){var e=t._i,i=t._f;return t._locale=t._locale||R(t._l),null===e||void 0===i&&""===e?f({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),b(e)?new p(gt(e)):(a(i)?Ft(t):i?Pt(t):o(e)?t._d=e:Wt(t),u(t)||(t._d=null),t))}function Wt(t){var e=t._i;void 0===e?t._d=new Date(i.now()):o(e)?t._d=new Date(e.valueOf()):"string"==typeof e?pt(t):a(e)?(t._a=n(e.slice(0),function(t){return parseInt(t,10)}),At(t)):"object"==typeof e?Ot(t):"number"==typeof e?t._d=new Date(e):i.createFromInputFallback(t)}function Lt(t,e,i,s,a){var o={};return"boolean"==typeof i&&(s=i,i=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=a,o._l=i,o._i=t,o._f=e,o._strict=s,Vt(o)}function Bt(t,e,i,s){return Lt(t,e,i,s,!1)}function zt(t,e){var i,s;if(1===e.length&&a(e[0])&&(e=e[0]),!e.length)return Bt();for(i=e[0],s=1;st&&(t=-t,i="-"),i+U(~~(t/60),2)+e+U(~~t%60,2)})}function jt(t,e){var i=(e||"").match(t)||[],s=i[i.length-1]||[],a=(s+"").match(ca)||["-",0,0],o=+(60*a[1])+x(a[2]);return"+"===a[0]?o:-o}function Gt(t,e){var s,a;return e._isUTC?(s=e.clone(),a=(b(t)||o(t)?t.valueOf():Bt(t).valueOf())-s.valueOf(),s._d.setTime(s._d.valueOf()+a),i.updateOffset(s,!1),s):Bt(t).local()}function qt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Zt(t,e){var s,a=this._offset||0;return this.isValid()?null!=t?("string"==typeof t?t=jt(Rs,t):Math.abs(t)<16&&(t=60*t),!this._isUTC&&e&&(s=qt(this)),this._offset=t,this._isUTC=!0,null!=s&&this.add(s,"m"),a!==t&&(!e||this._changeInProgress?de(this,oe(t-a,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,i.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?a:qt(this):null!=t?this:NaN}function Qt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Jt(t){return this.utcOffset(0,t)}function Xt(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(qt(this),"m")),this}function $t(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(jt(Vs,this._i)),this}function Kt(t){return this.isValid()?(t=t?Bt(t).utcOffset():0,(this.utcOffset()-t)%60===0):!1}function te(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function ee(){if(!g(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),t=Rt(t),t._a){var e=t._isUTC?l(t._a):Bt(t._a);this._isDSTShifted=this.isValid()&&y(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function ie(){return this.isValid()?!this._isUTC:!1}function se(){return this.isValid()?this._isUTC:!1}function ae(){return this.isValid()?this._isUTC&&0===this._offset:!1}function oe(t,e){var i,s,a,o=t,n=null;return Et(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(n=da.exec(t))?(i="-"===n[1]?-1:1,o={y:0,d:x(n[Ns])*i,h:x(n[Es])*i,m:x(n[Us])*i,s:x(n[js])*i,ms:x(n[Gs])*i}):(n=ua.exec(t))?(i="-"===n[1]?-1:1,o={y:ne(n[2],i),M:ne(n[3],i),w:ne(n[4],i),d:ne(n[5],i),h:ne(n[6],i),m:ne(n[7],i),s:ne(n[8],i)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(a=he(Bt(o.from),Bt(o.to)),o={},o.ms=a.milliseconds,o.M=a.months),s=new Nt(o),Et(t)&&r(t,"_locale")&&(s._locale=t._locale),s}function ne(t,e){var i=t&&parseFloat(t.replace(",","."));return(isNaN(i)?0:i)*e}function re(t,e){var i={milliseconds:0,months:0};return i.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(i.months,"M").isAfter(e)&&--i.months,i.milliseconds=+e-+t.clone().add(i.months,"M"),i}function he(t,e){var i;return t.isValid()&&e.isValid()?(e=Gt(e,t),t.isBefore(e)?i=re(t,e):(i=re(e,t),i.milliseconds=-i.milliseconds,i.months=-i.months),i):{milliseconds:0,months:0}}function le(t){return 0>t?-1*Math.round(-1*t):Math.round(t)}function ce(t,e){return function(i,s){var a,o;return null===s||isNaN(+s)||(S(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=i,i=s,s=o),i="string"==typeof i?+i:i,a=oe(i,s),de(this,a,t),this}}function de(t,e,s,a){var o=e._milliseconds,n=le(e._days),r=le(e._months);t.isValid()&&(a=null==a?!0:a,o&&t._d.setTime(t._d.valueOf()+o*s),n&&N(t,"Date",H(t,"Date")+n*s),r&&ht(t,H(t,"Month")+r*s),a&&i.updateOffset(t,n||r))}function ue(t,e){var i=t||Bt(),s=Gt(i,this).startOf("day"),a=this.diff(s,"days",!0),o=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse",n=e&&(w(e[o])?e[o]():e[o]);return this.format(n||this.localeData().calendar(o,this,Bt(i)))}function fe(){return new p(this)}function ge(t,e){var i=b(t)?t:Bt(t);return this.isValid()&&i.isValid()?(e=B(g(e)?"millisecond":e),"millisecond"===e?this.valueOf()>i.valueOf():i.valueOf()e-o?(i=t.clone().add(a-1,"months"),s=(e-o)/(o-i)):(i=t.clone().add(a+1,"months"),s=(e-o)/(i-o)),-(a+s)||0}function _e(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function Se(){var t=this.clone().utc();return 0o&&(e=o),Qe.call(this,t,e,i,s,a))}function Qe(t,e,i,s,a){var o=St(t,e,i,s,a),n=vt(o.year,0,o.dayOfYear);return this.year(n.getUTCFullYear()),this.month(n.getUTCMonth()),this.date(n.getUTCDate()),this}function Je(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Xe(t){return wt(t,this._week.dow,this._week.doy).week}function $e(){return this._week.dow}function Ke(){return this._week.doy}function ti(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function ei(t){var e=wt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function ii(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function si(t,e){return a(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]}function ai(t){return this._weekdaysShort[t.day()]}function oi(t){return this._weekdaysMin[t.day()]}function ni(t,e,i){var s,a,o,n=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;7>s;++s)o=l([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(o,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(o,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(o,"").toLocaleLowerCase();return i?"dddd"===e?(a=gs.call(this._weekdaysParse,n),-1!==a?a:null):"ddd"===e?(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:null):(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:null):"dddd"===e?(a=gs.call(this._weekdaysParse,n),-1!==a?a:(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:null))):"ddd"===e?(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:(a=gs.call(this._weekdaysParse,n),-1!==a?a:(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:null))):(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:(a=gs.call(this._weekdaysParse,n),-1!==a?a:(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:null)))}function ri(t,e,i){var s,a,o;if(this._weekdaysParseExact)return ni.call(this,t,e,i);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;7>s;s++){if(a=l([2e3,1]).day(s),i&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(a,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(a,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(a,"").replace(".",".?")+"$","i")),this._weekdaysParse[s]||(o="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[s]=new RegExp(o.replace(".",""),"i")),i&&"dddd"===e&&this._fullWeekdaysParse[s].test(t))return s;if(i&&"ddd"===e&&this._shortWeekdaysParse[s].test(t))return s;if(i&&"dd"===e&&this._minWeekdaysParse[s].test(t))return s;if(!i&&this._weekdaysParse[s].test(t))return s}}function hi(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=ii(t,this.localeData()),this.add(t-e,"d")):e}function li(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function ci(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function di(t){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||gi.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex}function ui(t){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||gi.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}function fi(t){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||gi.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}function gi(){function t(t,e){return e.length-t.length}var e,i,s,a,o,n=[],r=[],h=[],c=[];for(e=0;7>e;e++)i=l([2e3,1]).day(e),s=this.weekdaysMin(i,""),a=this.weekdaysShort(i,""),o=this.weekdays(i,""),n.push(s),r.push(a),h.push(o),c.push(s),c.push(a),c.push(o);for(n.sort(t),r.sort(t),h.sort(t),c.sort(t),e=0;7>e;e++)r[e]=K(r[e]),h[e]=K(h[e]),c[e]=K(c[e]);this._weekdaysRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+n.join("|")+")","i")}function mi(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function pi(){return this.hours()%12||12}function bi(){return this.hours()||24}function vi(t,e){j(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function xi(t,e){return e._meridiemParse}function yi(t){return"p"===(t+"").toLowerCase().charAt(0)}function ki(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"}function _i(t,e){e[Gs]=x(1e3*("0."+t))}function Si(){return this._isUTC?"UTC":""}function wi(){return this._isUTC?"Coordinated Universal Time":""}function Di(t){return Bt(1e3*t)}function Mi(){return Bt.apply(null,arguments).parseZone()}function Ci(t,e,i){var s=this._calendar[t];return w(s)?s.call(e,i):s}function Ai(t){var e=this._longDateFormat[t],i=this._longDateFormat[t.toUpperCase()];return e||!i?e:(this._longDateFormat[t]=i.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function Ii(){return this._invalidDate}function Pi(t){return this._ordinal.replace("%d",t)}function Ti(t){return t}function Fi(t,e,i,s){var a=this._relativeTime[i];return w(a)?a(t,e,i,s):a.replace(/%d/i,t)}function Oi(t,e){var i=this._relativeTime[t>0?"future":"past"];return w(i)?i(e):i.replace(/%s/i,e)}function Vi(t,e,i,s){var a=R(),o=l().set(s,e);return a[i](o,t)}function Ri(t,e,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return Vi(t,e,i,"month");var s,a=[];for(s=0;12>s;s++)a[s]=Vi(t,s,i,"month");return a}function Wi(t,e,i,s){"boolean"==typeof t?("number"==typeof e&&(i=e,e=void 0),e=e||""):(e=t,i=e,t=!1,"number"==typeof e&&(i=e,e=void 0),e=e||"");var a=R(),o=t?a._week.dow:0;if(null!=i)return Vi(e,(i+o)%7,s,"day");var n,r=[];for(n=0;7>n;n++)r[n]=Vi(e,(n+o)%7,s,"day");return r}function Li(t,e){return Ri(t,e,"months")}function Bi(t,e){return Ri(t,e,"monthsShort")}function zi(t,e,i){return Wi(t,e,i,"weekdays")}function Yi(t,e,i){return Wi(t,e,i,"weekdaysShort")}function Hi(t,e,i){return Wi(t,e,i,"weekdaysMin")}function Ni(){var t=this._data;return this._milliseconds=za(this._milliseconds),this._days=za(this._days),this._months=za(this._months),t.milliseconds=za(t.milliseconds),t.seconds=za(t.seconds),t.minutes=za(t.minutes),t.hours=za(t.hours),t.months=za(t.months),t.years=za(t.years),this}function Ei(t,e,i,s){var a=oe(e,i);return t._milliseconds+=s*a._milliseconds,t._days+=s*a._days,t._months+=s*a._months,t._bubble()}function Ui(t,e){return Ei(this,t,e,1)}function ji(t,e){return Ei(this,t,e,-1)}function Gi(t){return 0>t?Math.floor(t):Math.ceil(t)}function qi(){var t,e,i,s,a,o=this._milliseconds,n=this._days,r=this._months,h=this._data;return o>=0&&n>=0&&r>=0||0>=o&&0>=n&&0>=r||(o+=864e5*Gi(Qi(r)+n),n=0,r=0),h.milliseconds=o%1e3,t=v(o/1e3),h.seconds=t%60,e=v(t/60),h.minutes=e%60,i=v(e/60),h.hours=i%24,n+=v(i/24),a=v(Zi(n)),r+=a,n-=Gi(Qi(a)),s=v(r/12),r%=12,h.days=n,h.months=r,h.years=s,this}function Zi(t){return 4800*t/146097}function Qi(t){return 146097*t/4800}function Ji(t){var e,i,s=this._milliseconds;if(t=B(t),"month"===t||"year"===t)return e=this._days+s/864e5,i=this._months+Zi(e),"month"===t?i:i/12;switch(e=this._days+Math.round(Qi(this._months)),t){case"week":return e/7+s/6048e5;case"day":return e+s/864e5;case"hour":return 24*e+s/36e5;case"minute":return 1440*e+s/6e4;case"second":return 86400*e+s/1e3;case"millisecond":return Math.floor(864e5*e)+s;default:throw new Error("Unknown unit "+t)}}function Xi(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*x(this._months/12)}function $i(t){return function(){return this.as(t)}}function Ki(t){return t=B(t),this[t+"s"]()}function ts(t){return function(){return this._data[t]}}function es(){return v(this.days()/7)}function is(t,e,i,s,a){return a.relativeTime(e||1,!!i,t,s)}function ss(t,e,i){var s=oe(t).abs(),a=eo(s.as("s")),o=eo(s.as("m")),n=eo(s.as("h")),r=eo(s.as("d")),h=eo(s.as("M")),l=eo(s.as("y")),c=a=o&&["m"]||o=n&&["h"]||n=r&&["d"]||r=h&&["M"]||h=l&&["y"]||["yy",l];return c[2]=e,c[3]=+t>0,c[4]=i,is.apply(null,c)}function as(t,e){return void 0===io[t]?!1:void 0===e?io[t]:(io[t]=e,!0)}function os(t){var e=this.localeData(),i=ss(this,!t,e);return t&&(i=e.pastFuture(+this,i)),e.postformat(i)}function ns(){var t,e,i,s=so(this._milliseconds)/1e3,a=so(this._days),o=so(this._months);t=v(s/60),e=v(t/60),s%=60,t%=60,i=v(o/12),o%=12;var n=i,r=o,h=a,l=e,c=t,d=s,u=this.asSeconds();return u?(0>u?"-":"")+"P"+(n?n+"Y":"")+(r?r+"M":"")+(h?h+"D":"")+(l||c||d?"T":"")+(l?l+"H":"")+(c?c+"M":"")+(d?d+"S":""):"P0D"}var rs,hs;hs=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),i=e.length>>>0,s=0;i>s;s++)if(s in e&&t.call(this,e[s],s,e))return!0;return!1};var ls=i.momentProperties=[],cs=!1,ds={};i.suppressDeprecationWarnings=!1,i.deprecationHandler=null;var us;us=Object.keys?Object.keys:function(t){var e,i=[];for(e in t)r(t,e)&&i.push(e);return i};var fs,gs,ms={},ps={},bs=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,vs=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,xs={},ys={},ks=/\d/,_s=/\d\d/,Ss=/\d{3}/,ws=/\d{4}/,Ds=/[+-]?\d{6}/,Ms=/\d\d?/,Cs=/\d\d\d\d?/,As=/\d\d\d\d\d\d?/,Is=/\d{1,3}/,Ps=/\d{1,4}/,Ts=/[+-]?\d{1,6}/,Fs=/\d+/,Os=/[+-]?\d+/,Vs=/Z|[+-]\d\d:?\d\d/gi,Rs=/Z|[+-]\d\d(?::?\d\d)?/gi,Ws=/[+-]?\d+(\.\d{1,3})?/,Ls=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Bs={},zs={},Ys=0,Hs=1,Ns=2,Es=3,Us=4,js=5,Gs=6,qs=7,Zs=8;gs=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e=t?""+t:"+"+t}),j(0,["YY",2],0,function(){return this.year()%100}),j(0,["YYYY",4],0,"year"),j(0,["YYYYY",5],0,"year"),j(0,["YYYYYY",6,!0],0,"year"),L("year","y"),J("Y",Os),J("YY",Ms,_s),J("YYYY",Ps,ws),J("YYYYY",Ts,Ds),J("YYYYYY",Ts,Ds),tt(["YYYYY","YYYYYY"],Ys),tt("YYYY",function(t,e){e[Ys]=2===t.length?i.parseTwoDigitYear(t):x(t)}),tt("YY",function(t,e){e[Ys]=i.parseTwoDigitYear(t)}),tt("Y",function(t,e){e[Ys]=parseInt(t,10)}),i.parseTwoDigitYear=function(t){return x(t)+(x(t)>68?1900:2e3)};var na=Y("FullYear",!0);i.ISO_8601=function(){};var ra=_("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Bt.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:f()}),ha=_("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Bt.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:f()}),la=function(){return Date.now?Date.now():+new Date};Ut("Z",":"),Ut("ZZ",""),J("Z",Rs),J("ZZ",Rs),tt(["Z","ZZ"],function(t,e,i){i._useUTC=!0,i._tzm=jt(Rs,t)});var ca=/([\+\-]|\d\d)/gi;i.updateOffset=function(){};var da=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,ua=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;oe.fn=Nt.prototype;var fa=ce(1,"add"),ga=ce(-1,"subtract");i.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",i.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var ma=_("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});j(0,["gg",2],0,function(){return this.weekYear()%100}),j(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ee("gggg","weekYear"),Ee("ggggg","weekYear"),Ee("GGGG","isoWeekYear"),Ee("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),J("G",Os),J("g",Os),J("GG",Ms,_s),J("gg",Ms,_s),J("GGGG",Ps,ws),J("gggg",Ps,ws),J("GGGGG",Ts,Ds),J("ggggg",Ts,Ds),et(["gggg","ggggg","GGGG","GGGGG"],function(t,e,i,s){e[s.substr(0,2)]=x(t)}),et(["gg","GG"],function(t,e,s,a){e[a]=i.parseTwoDigitYear(t)}),j("Q",0,"Qo","quarter"),L("quarter","Q"),J("Q",ks),tt("Q",function(t,e){e[Hs]=3*(x(t)-1)}),j("w",["ww",2],"wo","week"),j("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),J("w",Ms),J("ww",Ms,_s),J("W",Ms),J("WW",Ms,_s),et(["w","ww","W","WW"],function(t,e,i,s){e[s.substr(0,1)]=x(t)});var pa={dow:0,doy:6};j("D",["DD",2],"Do","date"),L("date","D"),J("D",Ms),J("DD",Ms,_s),J("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),tt(["D","DD"],Ns),tt("Do",function(t,e){e[Ns]=x(t.match(Ms)[0],10)});var ba=Y("Date",!0);j("d",0,"do","day"),j("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),j("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),j("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),j("e",0,0,"weekday"),j("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),J("d",Ms),J("e",Ms),J("E",Ms),J("dd",function(t,e){return e.weekdaysMinRegex(t)}),J("ddd",function(t,e){return e.weekdaysShortRegex(t)}),J("dddd",function(t,e){return e.weekdaysRegex(t)}),et(["dd","ddd","dddd"],function(t,e,i,s){var a=i._locale.weekdaysParse(t,s,i._strict);null!=a?e.d=a:d(i).invalidWeekday=t}),et(["d","e","E"],function(t,e,i,s){e[s]=x(t)});var va="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),xa="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ya="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ka=Ls,_a=Ls,Sa=Ls;j("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),J("DDD",Is),J("DDDD",Ss),tt(["DDD","DDDD"],function(t,e,i){i._dayOfYear=x(t)}),j("H",["HH",2],0,"hour"),j("h",["hh",2],0,pi),j("k",["kk",2],0,bi),j("hmm",0,0,function(){return""+pi.apply(this)+U(this.minutes(),2)}),j("hmmss",0,0,function(){return""+pi.apply(this)+U(this.minutes(),2)+U(this.seconds(),2)}),j("Hmm",0,0,function(){return""+this.hours()+U(this.minutes(),2)}),j("Hmmss",0,0,function(){return""+this.hours()+U(this.minutes(),2)+U(this.seconds(),2)}),vi("a",!0),vi("A",!1),L("hour","h"),J("a",xi),J("A",xi),J("H",Ms),J("h",Ms),J("HH",Ms,_s),J("hh",Ms,_s),J("hmm",Cs),J("hmmss",As),J("Hmm",Cs),J("Hmmss",As),tt(["H","HH"],Es),tt(["a","A"],function(t,e,i){i._isPm=i._locale.isPM(t),i._meridiem=t}),tt(["h","hh"],function(t,e,i){e[Es]=x(t),d(i).bigHour=!0}),tt("hmm",function(t,e,i){var s=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s)),
+d(i).bigHour=!0}),tt("hmmss",function(t,e,i){var s=t.length-4,a=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s,2)),e[js]=x(t.substr(a)),d(i).bigHour=!0}),tt("Hmm",function(t,e,i){var s=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s))}),tt("Hmmss",function(t,e,i){var s=t.length-4,a=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s,2)),e[js]=x(t.substr(a))});var wa=/[ap]\.?m?\.?/i,Da=Y("Hours",!0);j("m",["mm",2],0,"minute"),L("minute","m"),J("m",Ms),J("mm",Ms,_s),tt(["m","mm"],Us);var Ma=Y("Minutes",!1);j("s",["ss",2],0,"second"),L("second","s"),J("s",Ms),J("ss",Ms,_s),tt(["s","ss"],js);var Ca=Y("Seconds",!1);j("S",0,0,function(){return~~(this.millisecond()/100)}),j(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),j(0,["SSS",3],0,"millisecond"),j(0,["SSSS",4],0,function(){return 10*this.millisecond()}),j(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),j(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),j(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),j(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),j(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),L("millisecond","ms"),J("S",Is,ks),J("SS",Is,_s),J("SSS",Is,Ss);var Aa;for(Aa="SSSS";Aa.length<=9;Aa+="S")J(Aa,Fs);for(Aa="S";Aa.length<=9;Aa+="S")tt(Aa,_i);var Ia=Y("Milliseconds",!1);j("z",0,0,"zoneAbbr"),j("zz",0,0,"zoneName");var Pa=p.prototype;Pa.add=fa,Pa.calendar=ue,Pa.clone=fe,Pa.diff=ye,Pa.endOf=Fe,Pa.format=we,Pa.from=De,Pa.fromNow=Me,Pa.to=Ce,Pa.toNow=Ae,Pa.get=E,Pa.invalidAt=He,Pa.isAfter=ge,Pa.isBefore=me,Pa.isBetween=pe,Pa.isSame=be,Pa.isSameOrAfter=ve,Pa.isSameOrBefore=xe,Pa.isValid=ze,Pa.lang=ma,Pa.locale=Ie,Pa.localeData=Pe,Pa.max=ha,Pa.min=ra,Pa.parsingFlags=Ye,Pa.set=E,Pa.startOf=Te,Pa.subtract=ga,Pa.toArray=We,Pa.toObject=Le,Pa.toDate=Re,Pa.toISOString=Se,Pa.toJSON=Be,Pa.toString=_e,Pa.unix=Ve,Pa.valueOf=Oe,Pa.creationData=Ne,Pa.year=na,Pa.isLeapYear=kt,Pa.weekYear=Ue,Pa.isoWeekYear=je,Pa.quarter=Pa.quarters=Je,Pa.month=lt,Pa.daysInMonth=ct,Pa.week=Pa.weeks=ti,Pa.isoWeek=Pa.isoWeeks=ei,Pa.weeksInYear=qe,Pa.isoWeeksInYear=Ge,Pa.date=ba,Pa.day=Pa.days=hi,Pa.weekday=li,Pa.isoWeekday=ci,Pa.dayOfYear=mi,Pa.hour=Pa.hours=Da,Pa.minute=Pa.minutes=Ma,Pa.second=Pa.seconds=Ca,Pa.millisecond=Pa.milliseconds=Ia,Pa.utcOffset=Zt,Pa.utc=Jt,Pa.local=Xt,Pa.parseZone=$t,Pa.hasAlignedHourOffset=Kt,Pa.isDST=te,Pa.isDSTShifted=ee,Pa.isLocal=ie,Pa.isUtcOffset=se,Pa.isUtc=ae,Pa.isUTC=ae,Pa.zoneAbbr=Si,Pa.zoneName=wi,Pa.dates=_("dates accessor is deprecated. Use date instead.",ba),Pa.months=_("months accessor is deprecated. Use month instead",lt),Pa.years=_("years accessor is deprecated. Use year instead",na),Pa.zone=_("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Qt);var Ta=Pa,Fa={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Oa={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Va="Invalid date",Ra="%d",Wa=/\d{1,2}/,La={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Ba=A.prototype;Ba._calendar=Fa,Ba.calendar=Ci,Ba._longDateFormat=Oa,Ba.longDateFormat=Ai,Ba._invalidDate=Va,Ba.invalidDate=Ii,Ba._ordinal=Ra,Ba.ordinal=Pi,Ba._ordinalParse=Wa,Ba.preparse=Ti,Ba.postformat=Ti,Ba._relativeTime=La,Ba.relativeTime=Fi,Ba.pastFuture=Oi,Ba.set=M,Ba.months=at,Ba._months=Js,Ba.monthsShort=ot,Ba._monthsShort=Xs,Ba.monthsParse=rt,Ba._monthsRegex=Ks,Ba.monthsRegex=ut,Ba._monthsShortRegex=$s,Ba.monthsShortRegex=dt,Ba.week=Xe,Ba._week=pa,Ba.firstDayOfYear=Ke,Ba.firstDayOfWeek=$e,Ba.weekdays=si,Ba._weekdays=va,Ba.weekdaysMin=oi,Ba._weekdaysMin=ya,Ba.weekdaysShort=ai,Ba._weekdaysShort=xa,Ba.weekdaysParse=ri,Ba._weekdaysRegex=ka,Ba.weekdaysRegex=di,Ba._weekdaysShortRegex=_a,Ba.weekdaysShortRegex=ui,Ba._weekdaysMinRegex=Sa,Ba.weekdaysMinRegex=fi,Ba.isPM=yi,Ba._meridiemParse=wa,Ba.meridiem=ki,F("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,i=1===x(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+i}}),i.lang=_("moment.lang is deprecated. Use moment.locale instead.",F),i.langData=_("moment.langData is deprecated. Use moment.localeData instead.",R);var za=Math.abs,Ya=$i("ms"),Ha=$i("s"),Na=$i("m"),Ea=$i("h"),Ua=$i("d"),ja=$i("w"),Ga=$i("M"),qa=$i("y"),Za=ts("milliseconds"),Qa=ts("seconds"),Ja=ts("minutes"),Xa=ts("hours"),$a=ts("days"),Ka=ts("months"),to=ts("years"),eo=Math.round,io={s:45,m:45,h:22,d:26,M:11},so=Math.abs,ao=Nt.prototype;ao.abs=Ni,ao.add=Ui,ao.subtract=ji,ao.as=Ji,ao.asMilliseconds=Ya,ao.asSeconds=Ha,ao.asMinutes=Na,ao.asHours=Ea,ao.asDays=Ua,ao.asWeeks=ja,ao.asMonths=Ga,ao.asYears=qa,ao.valueOf=Xi,ao._bubble=qi,ao.get=Ki,ao.milliseconds=Za,ao.seconds=Qa,ao.minutes=Ja,ao.hours=Xa,ao.days=$a,ao.weeks=es,ao.months=Ka,ao.years=to,ao.humanize=os,ao.toISOString=ns,ao.toString=ns,ao.toJSON=ns,ao.locale=Ie,ao.localeData=Pe,ao.toIsoString=_("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ns),ao.lang=ma,j("X",0,0,"unix"),j("x",0,0,"valueOf"),J("x",Os),J("X",Ws),tt("X",function(t,e,i){i._d=new Date(1e3*parseFloat(t,10))}),tt("x",function(t,e,i){i._d=new Date(x(t))}),i.version="2.13.0",s(Bt),i.fn=Ta,i.min=Yt,i.max=Ht,i.now=la,i.utc=l,i.unix=Di,i.months=Li,i.isDate=o,i.locale=F,i.invalid=f,i.duration=oe,i.isMoment=b,i.weekdays=zi,i.parseZone=Mi,i.localeData=R,i.isDuration=Et,i.monthsShort=Bi,i.weekdaysMin=Hi,i.defineLocale=O,i.updateLocale=V,i.locales=W,i.weekdaysShort=Yi,i.normalizeUnits=B,i.relativeTimeThreshold=as,i.prototype=Ta;var oo=i;return oo})},{}],7:[function(t,e,i){var s=t("./core/core.js")();t("./core/core.helpers")(s),t("./core/core.element")(s),t("./core/core.animation")(s),t("./core/core.controller")(s),t("./core/core.datasetController")(s),t("./core/core.layoutService")(s),t("./core/core.legend")(s),t("./core/core.plugin.js")(s),t("./core/core.scale")(s),t("./core/core.scaleService")(s),t("./core/core.title")(s),t("./core/core.tooltip")(s),t("./controllers/controller.bar")(s),t("./controllers/controller.bubble")(s),t("./controllers/controller.doughnut")(s),t("./controllers/controller.line")(s),t("./controllers/controller.polarArea")(s),t("./controllers/controller.radar")(s),t("./scales/scale.category")(s),t("./scales/scale.linear")(s),t("./scales/scale.logarithmic")(s),t("./scales/scale.radialLinear")(s),t("./scales/scale.time")(s),t("./elements/element.arc")(s),t("./elements/element.line")(s),t("./elements/element.point")(s),t("./elements/element.rectangle")(s),t("./charts/Chart.Bar")(s),t("./charts/Chart.Bubble")(s),t("./charts/Chart.Doughnut")(s),t("./charts/Chart.Line")(s),t("./charts/Chart.PolarArea")(s),t("./charts/Chart.Radar")(s),t("./charts/Chart.Scatter")(s),window.Chart=e.exports=s},{"./charts/Chart.Bar":8,"./charts/Chart.Bubble":9,"./charts/Chart.Doughnut":10,"./charts/Chart.Line":11,"./charts/Chart.PolarArea":12,"./charts/Chart.Radar":13,"./charts/Chart.Scatter":14,"./controllers/controller.bar":15,"./controllers/controller.bubble":16,"./controllers/controller.doughnut":17,"./controllers/controller.line":18,"./controllers/controller.polarArea":19,"./controllers/controller.radar":20,"./core/core.animation":21,"./core/core.controller":22,"./core/core.datasetController":23,"./core/core.element":24,"./core/core.helpers":25,"./core/core.js":26,"./core/core.layoutService":27,"./core/core.legend":28,"./core/core.plugin.js":29,"./core/core.scale":30,"./core/core.scaleService":31,"./core/core.title":32,"./core/core.tooltip":33,"./elements/element.arc":34,"./elements/element.line":35,"./elements/element.point":36,"./elements/element.rectangle":37,"./scales/scale.category":38,"./scales/scale.linear":39,"./scales/scale.logarithmic":40,"./scales/scale.radialLinear":41,"./scales/scale.time":42}],8:[function(t,e,i){"use strict";e.exports=function(t){t.Bar=function(e,i){return i.type="bar",new t(e,i)}}},{}],9:[function(t,e,i){"use strict";e.exports=function(t){t.Bubble=function(e,i){return i.type="bubble",new t(e,i)}}},{}],10:[function(t,e,i){"use strict";e.exports=function(t){t.Doughnut=function(e,i){return i.type="doughnut",new t(e,i)}}},{}],11:[function(t,e,i){"use strict";e.exports=function(t){t.Line=function(e,i){return i.type="line",new t(e,i)}}},{}],12:[function(t,e,i){"use strict";e.exports=function(t){t.PolarArea=function(e,i){return i.type="polarArea",new t(e,i)}}},{}],13:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={aspectRatio:1};t.Radar=function(s,a){return a.options=e.configMerge(i,a.options),a.type="radar",new t(s,a)}}},{}],14:[function(t,e,i){"use strict";e.exports=function(t){var e={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-1"}],yAxes:[{type:"linear",position:"left",id:"y-axis-1"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){return"("+t.xLabel+", "+t.yLabel+")"}}}};t.defaults.scatter=e,t.controllers.scatter=t.controllers.line,t.Scatter=function(e,i){return i.type="scatter",new t(e,i)}}},{}],15:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bar={hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}},t.controllers.bar=t.DatasetController.extend({initialize:function(e,i){t.DatasetController.prototype.initialize.call(this,e,i),this.getMeta().bar=!0},getBarCount:function(){var t=0;return e.each(this.chart.data.datasets,function(e,i){var s=this.chart.getDatasetMeta(i);s.bar&&this.chart.isDatasetVisible(i)&&++t},this),t},addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:e}),s=this.getBarCount();this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0,s)},update:function(t){var i=this.getBarCount();e.each(this.getMeta().data,function(e,s){this.updateElement(e,s,t,i)},this)},updateElement:function(t,i,s,a){var o,n=this.getMeta(),r=this.getScaleForId(n.xAxisID),h=this.getScaleForId(n.yAxisID);o=h.min<0&&h.max<0?h.getPixelForValue(h.max):h.min>0&&h.max>0?h.getPixelForValue(h.min):h.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:r,_yScale:h,_datasetIndex:this.index,_index:i,_model:{x:this.calculateBarX(i,this.index),y:s?o:this.calculateBarY(i,this.index),label:this.chart.data.labels[i],datasetLabel:this.getDataset().label,base:s?o:this.calculateBarBase(this.index,i),width:this.calculateBarWidth(a),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),borderSkipped:t.custom&&t.custom.borderSkipped?t.custom.borderSkipped:this.chart.options.elements.rectangle.borderSkipped,borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}}),t.pivot()},calculateBarBase:function(t,e){var i=this.getMeta(),s=(this.getScaleForId(i.xAxisID),this.getScaleForId(i.yAxisID)),a=0;if(s.options.stacked){var o=this.chart.data.datasets[t].data[e];if(0>o)for(var n=0;t>n;n++){var r=this.chart.data.datasets[n],h=this.chart.getDatasetMeta(n);h.bar&&h.yAxisID===s.id&&this.chart.isDatasetVisible(n)&&(a+=r.data[e]<0?r.data[e]:0)}else for(var l=0;t>l;l++){var c=this.chart.data.datasets[l],d=this.chart.getDatasetMeta(l);d.bar&&d.yAxisID===s.id&&this.chart.isDatasetVisible(l)&&(a+=c.data[e]>0?c.data[e]:0)}return s.getPixelForValue(a)}return a=s.getPixelForValue(s.min),s.beginAtZero||s.min<=0&&s.max>=0||s.min>=0&&s.max<=0?a=s.getPixelForValue(0,0):s.min<0&&s.max<0&&(a=s.getPixelForValue(s.max)),a},getRuler:function(){var t=this.getMeta(),e=this.getScaleForId(t.xAxisID),i=(this.getScaleForId(t.yAxisID),this.getBarCount()),s=function(){for(var t=e.getPixelForTick(1)-e.getPixelForTick(0),i=2;ii;++i)e=this.chart.getDatasetMeta(i),e.bar&&this.chart.isDatasetVisible(i)&&++s;return s},calculateBarX:function(t,e){var i=this.getMeta(),s=(this.getScaleForId(i.yAxisID),this.getScaleForId(i.xAxisID)),a=this.getBarIndex(e),o=this.getRuler(),n=s.getPixelForValue(null,t,e,this.chart.isCombo);return n-=this.chart.isCombo?o.tickWidth/2:0,s.options.stacked?n+o.categoryWidth/2+o.categorySpacing:n+o.barWidth/2+o.categorySpacing+o.barWidth*a+o.barSpacing/2+o.barSpacing*a},calculateBarY:function(t,e){var i=this.getMeta(),s=(this.getScaleForId(i.xAxisID),this.getScaleForId(i.yAxisID)),a=this.getDataset().data[t];if(s.options.stacked){for(var o=0,n=0,r=0;e>r;r++){var h=this.chart.data.datasets[r],l=this.chart.getDatasetMeta(r);l.bar&&l.yAxisID===s.id&&this.chart.isDatasetVisible(r)&&(h.data[t]<0?n+=h.data[t]||0:o+=h.data[t]||0)}return 0>a?s.getPixelForValue(n+a):s.getPixelForValue(o+a)}return s.getPixelForValue(a)},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){var s=this.getDataset().data[e];null===s||void 0===s||isNaN(s)||t.transition(i).draw()},this)},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}}),t.defaults.horizontalBar={hover:{mode:"label"},scales:{xAxes:[{type:"linear",position:"bottom"}],yAxes:[{position:"left",type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}]}},t.controllers.horizontalBar=t.controllers.bar.extend({updateElement:function(t,i,s,a){var o,n=this.getMeta(),r=this.getScaleForId(n.xAxisID),h=this.getScaleForId(n.yAxisID);o=r.min<0&&r.max<0?r.getPixelForValue(r.max):r.min>0&&r.max>0?r.getPixelForValue(r.min):r.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:r,_yScale:h,_datasetIndex:this.index,_index:i,_model:{x:s?o:this.calculateBarX(i,this.index),y:this.calculateBarY(i,this.index),label:this.chart.data.labels[i],datasetLabel:this.getDataset().label,base:s?o:this.calculateBarBase(this.index,i),height:this.calculateBarHeight(a),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),borderSkipped:t.custom&&t.custom.borderSkipped?t.custom.borderSkipped:this.chart.options.elements.rectangle.borderSkipped,borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)},draw:function(){function t(t){return h[(c+t)%4]}var e=this._chart.ctx,i=this._view,s=i.height/2,a=i.y-s,o=i.y+s,n=i.base-(i.base-i.x),r=i.borderWidth/2;i.borderWidth&&(a+=r,o-=r,n+=r),e.beginPath(),e.fillStyle=i.backgroundColor,e.strokeStyle=i.borderColor,e.lineWidth=i.borderWidth;var h=[[i.base,o],[i.base,a],[n,a],[n,o]],l=["bottom","left","top","right"],c=l.indexOf(i.borderSkipped,0);-1===c&&(c=0),e.moveTo.apply(e,t(0));for(var d=1;4>d;d++)e.lineTo.apply(e,t(d));e.fill(),i.borderWidth&&e.stroke()},inRange:function(t,e){var i=this._view,s=!1;return i&&(s=i.x=i.y-i.height/2&&e<=i.y+i.height/2&&t>=i.x&&t<=i.base:e>=i.y-i.height/2&&e<=i.y+i.height/2&&t>=i.base&&t<=i.x),s}}),t.pivot()},calculateBarBase:function(t,e){var i=this.getMeta(),s=this.getScaleForId(i.xAxisID),a=(this.getScaleForId(i.yAxisID),0);if(s.options.stacked){var o=this.chart.data.datasets[t].data[e];if(0>o)for(var n=0;t>n;n++){var r=this.chart.data.datasets[n],h=this.chart.getDatasetMeta(n);h.bar&&h.xAxisID===s.id&&this.chart.isDatasetVisible(n)&&(a+=r.data[e]<0?r.data[e]:0)}else for(var l=0;t>l;l++){var c=this.chart.data.datasets[l],d=this.chart.getDatasetMeta(l);d.bar&&d.xAxisID===s.id&&this.chart.isDatasetVisible(l)&&(a+=c.data[e]>0?c.data[e]:0)}return s.getPixelForValue(a)}return a=s.getPixelForValue(s.min),s.beginAtZero||s.min<=0&&s.max>=0||s.min>=0&&s.max<=0?a=s.getPixelForValue(0,0):s.min<0&&s.max<0&&(a=s.getPixelForValue(s.max)),a},getRuler:function(){var t=this.getMeta(),e=(this.getScaleForId(t.xAxisID),this.getScaleForId(t.yAxisID)),i=this.getBarCount(),s=function(){for(var t=e.getPixelForTick(1)-e.getPixelForTick(0),i=2;ir;r++){var h=this.chart.data.datasets[r],l=this.chart.getDatasetMeta(r);l.bar&&l.xAxisID===s.id&&this.chart.isDatasetVisible(r)&&(h.data[t]<0?n+=h.data[t]||0:o+=h.data[t]||0)}return 0>a?s.getPixelForValue(n+a):s.getPixelForValue(o+a)}return s.getPixelForValue(a)},calculateBarY:function(t,e){var i=this.getMeta(),s=this.getScaleForId(i.yAxisID),a=(this.getScaleForId(i.xAxisID),this.getBarIndex(e)),o=this.getRuler(),n=s.getPixelForValue(null,t,e,this.chart.isCombo);return n-=this.chart.isCombo?o.tickHeight/2:0,s.options.stacked?n+o.categoryHeight/2+o.categorySpacing:n+o.barHeight/2+o.categorySpacing+o.barHeight*a+o.barSpacing/2+o.barSpacing*a}})}},{}],16:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bubble={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){var i=e.datasets[t.datasetIndex].label||"",s=e.datasets[t.datasetIndex].data[t.index];return i+": ("+s.x+", "+s.y+", "+s.r+")"}}}},t.controllers.bubble=t.DatasetController.extend({addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0)},update:function(t){var i,s=this.getMeta(),a=s.data,o=this.getScaleForId(s.yAxisID);this.getScaleForId(s.xAxisID);i=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),e.each(a,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,s){var a,o=this.getMeta(),n=this.getScaleForId(o.yAxisID),r=this.getScaleForId(o.xAxisID);a=n.min<0&&n.max<0?n.getPixelForValue(n.max):n.min>0&&n.max>0?n.getPixelForValue(n.min):n.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:r,_yScale:n,_datasetIndex:this.index,_index:i,_model:{x:s?r.getPixelForDecimal(.5):r.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:s?a:n.getPixelForValue(this.getDataset().data[i],i,this.index),radius:s?0:t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[i]),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y),t.pivot()},getRadius:function(t){return t.r||this.chart.options.elements.point.radius},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){t.transition(i),t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.hoverRadius,s,this.chart.options.elements.point.hoverRadius)+this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],17:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.doughnut={animation:{animateRotate:!0,animateScale:!1},aspectRatio:1,hover:{mode:"single"},legendCallback:function(t){var e=[];if(e.push(''),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){var i=t.data;return i.labels.length&&i.datasets.length?i.labels.map(function(s,a){var o=t.getDatasetMeta(0),n=i.datasets[0],r=o.data[a],h=r.custom&&r.custom.backgroundColor?r.custom.backgroundColor:e.getValueAtIndexOrDefault(n.backgroundColor,a,this.chart.options.elements.arc.backgroundColor),l=r.custom&&r.custom.borderColor?r.custom.borderColor:e.getValueAtIndexOrDefault(n.borderColor,a,this.chart.options.elements.arc.borderColor),c=r.custom&&r.custom.borderWidth?r.custom.borderWidth:e.getValueAtIndexOrDefault(n.borderWidth,a,this.chart.options.elements.arc.borderWidth);return{text:s,fillStyle:h,strokeStyle:l,lineWidth:c,hidden:isNaN(n.data[a])||o.data[a].hidden,index:a}},this):[]}},onClick:function(t,e){var i,s,a,o=e.index,n=this.chart;for(i=0,s=(n.data.datasets||[]).length;s>i;++i)a=n.getDatasetMeta(i),a.data[o].hidden=!a.data[o].hidden;n.update()}},cutoutPercentage:50,rotation:Math.PI*-.5,circumference:2*Math.PI,tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+e.datasets[t.datasetIndex].data[t.index]}}}},t.defaults.pie=e.clone(t.defaults.doughnut),e.extend(t.defaults.pie,{cutoutPercentage:0}),t.controllers.doughnut=t.controllers.pie=t.DatasetController.extend({linkScales:function(){},addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(i,s){var a=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i});s&&e.isArray(this.getDataset().backgroundColor)&&this.getDataset().backgroundColor.splice(i,0,s),this.getMeta().data.splice(i,0,a),this.updateElement(a,i,!0)},getRingIndex:function(t){for(var e=0,i=0;t>i;++i)this.chart.isDatasetVisible(i)&&++e;return e},update:function(t){var i=this.chart.chartArea.right-this.chart.chartArea.left-this.chart.options.elements.arc.borderWidth,s=this.chart.chartArea.bottom-this.chart.chartArea.top-this.chart.options.elements.arc.borderWidth,a=Math.min(i,s),o={x:0,y:0};if(this.chart.options.circumference<2*Math.PI){var n=this.chart.options.rotation%(2*Math.PI);n+=2*Math.PI*(n>=Math.PI?-1:n<-Math.PI?1:0);var r=n+this.chart.options.circumference,h={x:Math.cos(n),y:Math.sin(n)},l={x:Math.cos(r),y:Math.sin(r)},c=0>=n&&r>=0||n<=2*Math.PI&&2*Math.PI<=r,d=n<=.5*Math.PI&&.5*Math.PI<=r||n<=2.5*Math.PI&&2.5*Math.PI<=r,u=n<=-Math.PI&&-Math.PI<=r||n<=Math.PI&&Math.PI<=r,f=n<=.5*-Math.PI&&.5*-Math.PI<=r||n<=1.5*Math.PI&&1.5*Math.PI<=r,g=this.chart.options.cutoutPercentage/100,m={x:u?-1:Math.min(h.x*(h.x<0?1:g),l.x*(l.x<0?1:g)),y:f?-1:Math.min(h.y*(h.y<0?1:g),l.y*(l.y<0?1:g))},p={x:c?1:Math.max(h.x*(h.x>0?1:g),l.x*(l.x>0?1:g)),y:d?1:Math.max(h.y*(h.y>0?1:g),l.y*(l.y>0?1:g))},b={width:.5*(p.x-m.x),height:.5*(p.y-m.y)};a=Math.min(i/b.width,s/b.height),o={x:(p.x+m.x)*-.5,y:(p.y+m.y)*-.5}}this.chart.outerRadius=Math.max(a/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.chart.getVisibleDatasetCount(),this.chart.offsetX=o.x*this.chart.outerRadius,this.chart.offsetY=o.y*this.chart.outerRadius,this.getMeta().total=this.calculateTotal(),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.getRingIndex(this.index),this.innerRadius=this.outerRadius-this.chart.radiusLength,e.each(this.getMeta().data,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,s){var a=(this.chart.chartArea.left+this.chart.chartArea.right)/2,o=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,n=this.chart.options.rotation,r=this.chart.options.rotation,h=s&&this.chart.options.animation.animateRotate?0:t.hidden?0:this.calculateCircumference(this.getDataset().data[i])*(this.chart.options.circumference/(2*Math.PI)),l=s&&this.chart.options.animation.animateScale?0:this.innerRadius,c=s&&this.chart.options.animation.animateScale?0:this.outerRadius;e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_model:{x:a+this.chart.offsetX,y:o+this.chart.offsetY,startAngle:n,endAngle:r,circumference:h,outerRadius:c,innerRadius:l,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor,i,this.chart.options.elements.arc.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.getDataset().label,i,this.chart.data.labels[i])}}),s&&this.chart.options.animation.animateRotate||(0===i?t._model.startAngle=this.chart.options.rotation:t._model.startAngle=this.getMeta().data[i-1]._model.endAngle,t._model.endAngle=t._model.startAngle+t._model.circumference),t.pivot()},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},calculateTotal:function(){var t,i=this.getDataset(),s=this.getMeta(),a=0;return e.each(s.data,function(e,s){t=i.data[s],isNaN(t)||e.hidden||(a+=Math.abs(t))}),a},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0}})}},{}],18:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.line={showLines:!0,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({
+addElements:function(){var i=this.getMeta();i.dataset=i.dataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:i.data}),e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},update:function(t){var i,s=this.getMeta(),a=s.dataset,o=s.data,n=this.getScaleForId(s.yAxisID);this.getScaleForId(s.xAxisID);i=n.min<0&&n.max<0?n.getPixelForValue(n.max):n.min>0&&n.max>0?n.getPixelForValue(n.min):n.getPixelForValue(0),this.chart.options.showLines&&(a._scale=n,a._datasetIndex=this.index,a._children=o,void 0!==this.getDataset().tension&&void 0===this.getDataset().lineTension&&(this.getDataset().lineTension=this.getDataset().tension),a._model={tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().lineTension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,scaleTop:n.top,scaleBottom:n.bottom,scaleZero:i},a.pivot()),e.each(o,function(e,i){this.updateElement(e,i,t)},this),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},getPointBackgroundColor:function(t,i){var s=this.chart.options.elements.point.backgroundColor,a=this.getDataset();return t.custom&&t.custom.backgroundColor?s=t.custom.backgroundColor:a.pointBackgroundColor?s=e.getValueAtIndexOrDefault(a.pointBackgroundColor,i,s):a.backgroundColor&&(s=a.backgroundColor),s},getPointBorderColor:function(t,i){var s=this.chart.options.elements.point.borderColor,a=this.getDataset();return t.custom&&t.custom.borderColor?s=t.custom.borderColor:a.pointBorderColor?s=e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,s):a.borderColor&&(s=a.borderColor),s},getPointBorderWidth:function(t,i){var s=this.chart.options.elements.point.borderWidth,a=this.getDataset();return t.custom&&void 0!==t.custom.borderWidth?s=t.custom.borderWidth:void 0!==a.pointBorderWidth?s=e.getValueAtIndexOrDefault(a.pointBorderWidth,i,s):void 0!==a.borderWidth&&(s=a.borderWidth),s},updateElement:function(t,i,s){var a,o=this.getMeta(),n=this.getScaleForId(o.yAxisID),r=this.getScaleForId(o.xAxisID);a=n.min<0&&n.max<0?n.getPixelForValue(n.max):n.min>0&&n.max>0?n.getPixelForValue(n.min):n.getPixelForValue(0),t._chart=this.chart.chart,t._xScale=r,t._yScale=n,t._datasetIndex=this.index,t._index=i,void 0!==this.getDataset().radius&&void 0===this.getDataset().pointRadius&&(this.getDataset().pointRadius=this.getDataset().radius),void 0!==this.getDataset().hitRadius&&void 0===this.getDataset().pointHitRadius&&(this.getDataset().pointHitRadius=this.getDataset().hitRadius),t._model={x:r.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:s?a:this.calculatePointY(this.getDataset().data[i],i,this.index,this.chart.isCombo),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),backgroundColor:this.getPointBackgroundColor(t,i),borderColor:this.getPointBorderColor(t,i),borderWidth:this.getPointBorderWidth(t,i),tension:o.dataset._model?o.dataset._model.tension:0,hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().pointHitRadius,i,this.chart.options.elements.point.hitRadius)},t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},calculatePointY:function(t,e,i,s){var a=this.getMeta(),o=(this.getScaleForId(a.xAxisID),this.getScaleForId(a.yAxisID));if(o.options.stacked){for(var n=0,r=0,h=0;i>h;h++){var l=this.chart.data.datasets[h],c=this.chart.getDatasetMeta(h);"line"===c.type&&this.chart.isDatasetVisible(h)&&(l.data[e]<0?r+=l.data[e]||0:n+=l.data[e]||0)}return 0>t?o.getPixelForValue(r+t):o.getPixelForValue(n+t)}return o.getPixelForValue(t)},updateBezierControlPoints:function(){var t=this.getMeta();e.each(t.data,function(i,s){var a=e.splineCurve(e.previousItem(t.data,s)._model,i._model,e.nextItem(t.data,s)._model,t.dataset._model.tension);i._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i.pivot()},this)},draw:function(t){var i=this.getMeta(),s=t||1;e.each(i.data,function(t){t.transition(s)}),this.chart.options.showLines&&i.dataset.transition(s).draw(),e.each(i.data,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,s,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);void 0!==this.getDataset().radius&&void 0===this.getDataset().pointRadius&&(this.getDataset().pointRadius=this.getDataset().radius),t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=this.getPointBackgroundColor(t,i),t._model.borderColor=this.getPointBorderColor(t,i),t._model.borderWidth=this.getPointBorderWidth(t,i)}})}},{}],19:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.polarArea={scale:{type:"radialLinear",lineArc:!0},animation:{animateRotate:!0,animateScale:!0},aspectRatio:1,legendCallback:function(t){var e=[];if(e.push(''),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){var i=t.data;return i.labels.length&&i.datasets.length?i.labels.map(function(s,a){var o=t.getDatasetMeta(0),n=i.datasets[0],r=o.data[a],h=r.custom&&r.custom.backgroundColor?r.custom.backgroundColor:e.getValueAtIndexOrDefault(n.backgroundColor,a,this.chart.options.elements.arc.backgroundColor),l=r.custom&&r.custom.borderColor?r.custom.borderColor:e.getValueAtIndexOrDefault(n.borderColor,a,this.chart.options.elements.arc.borderColor),c=r.custom&&r.custom.borderWidth?r.custom.borderWidth:e.getValueAtIndexOrDefault(n.borderWidth,a,this.chart.options.elements.arc.borderWidth);return{text:s,fillStyle:h,strokeStyle:l,lineWidth:c,hidden:isNaN(n.data[a])||o.data[a].hidden,index:a}},this):[]}},onClick:function(t,e){var i,s,a,o=e.index,n=this.chart;for(i=0,s=(n.data.datasets||[]).length;s>i;++i)a=n.getDatasetMeta(i),a.data[o].hidden=!a.data[o].hidden;n.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}},t.controllers.polarArea=t.DatasetController.extend({linkScales:function(){},addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0)},update:function(t){var i=this.getMeta(),s=Math.min(this.chart.chartArea.right-this.chart.chartArea.left,this.chart.chartArea.bottom-this.chart.chartArea.top);this.chart.outerRadius=Math.max((s-this.chart.options.elements.arc.borderWidth/2)/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.chart.getVisibleDatasetCount(),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.index,this.innerRadius=this.outerRadius-this.chart.radiusLength,i.count=this.countVisibleElements(),e.each(i.data,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,s){for(var a=this.calculateCircumference(this.getDataset().data[i]),o=(this.chart.chartArea.left+this.chart.chartArea.right)/2,n=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,r=0,h=this.getMeta(),l=0;i>l;++l)isNaN(this.getDataset().data[l])||h.data[l].hidden||++r;var c=t.hidden?0:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),d=-.5*Math.PI+a*r,u=d+(t.hidden?0:a),f={x:o,y:n,innerRadius:0,outerRadius:this.chart.options.animation.animateScale?0:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),startAngle:this.chart.options.animation.animateRotate?Math.PI*-.5:d,endAngle:this.chart.options.animation.animateRotate?Math.PI*-.5:u,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])};e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:s?f:{x:o,y:n,innerRadius:0,outerRadius:c,startAngle:d,endAngle:u,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])}}),t.pivot()},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},countVisibleElements:function(){var t=this.getDataset(),i=this.getMeta(),s=0;return e.each(i.data,function(e,i){isNaN(t.data[i])||e.hidden||s++}),s},calculateCircumference:function(t){var e=this.getMeta().count;return e>0&&!isNaN(t)?2*Math.PI/e:0}})}},{}],20:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({linkScales:function(){},addElements:function(){var i=this.getMeta();i.dataset=i.dataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:i.data,_loop:!0}),e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:s,_model:{x:0,y:0}})},this)},addElementAndReset:function(e){var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0),this.updateBezierControlPoints()},update:function(t){var i,s=this.getMeta(),a=s.dataset,o=s.data,n=this.chart.scale;i=n.min<0&&n.max<0?n.getPointPositionForValue(0,n.max):n.min>0&&n.max>0?n.getPointPositionForValue(0,n.min):n.getPointPositionForValue(0,0),void 0!==this.getDataset().tension&&void 0===this.getDataset().lineTension&&(this.getDataset().lineTension=this.getDataset().tension),e.extend(s.dataset,{_datasetIndex:this.index,_children:o,_model:{tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().lineTension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,scaleTop:n.top,scaleBottom:n.bottom,scaleZero:i}}),s.dataset.pivot(),e.each(o,function(e,i){this.updateElement(e,i,t)},this),this.updateBezierControlPoints()},updateElement:function(t,i,s){var a=this.chart.scale.getPointPositionForValue(i,this.getDataset().data[i]);e.extend(t,{_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:{x:s?this.chart.scale.xCenter:a.x,y:s?this.chart.scale.yCenter:a.y,tension:t.custom&&t.custom.tension?t.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.getMeta();e.each(t.data,function(i,s){var a=e.splineCurve(e.previousItem(t.data,s,!0)._model,i._model,e.nextItem(t.data,s,!0)._model,i._model.tension);i._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i.pivot()},this)},draw:function(t){var i=this.getMeta(),s=t||1;e.each(i.data,function(t,e){t.transition(s)}),i.dataset.transition(s).draw(),e.each(i.data,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,s,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],21:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,i,s){s||(t.animating=!0);for(var a=0;a1&&(e=Math.floor(this.dropFrames),this.dropFrames=this.dropFrames%1);for(var i=0;ithis.animations[i].animationObject.numSteps&&(this.animations[i].animationObject.currentStep=this.animations[i].animationObject.numSteps),this.animations[i].animationObject.render(this.animations[i].chartInstance,this.animations[i].animationObject),this.animations[i].animationObject.onAnimationProgress&&this.animations[i].animationObject.onAnimationProgress.call&&this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].animationObject.currentStep===this.animations[i].animationObject.numSteps?(this.animations[i].animationObject.onAnimationComplete&&this.animations[i].animationObject.onAnimationComplete.call&&this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].chartInstance.animating=!1,this.animations.splice(i,1)):++i;var s=Date.now(),a=(s-t)/this.frameDuration;this.dropFrames+=a,this.animations.length>0&&this.requestAnimationFrame()}}}},{}],22:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.types={},t.instances={},t.controllers={},t.Controller=function(i){return this.chart=i,this.config=i.config,this.options=this.config.options=e.configMerge(t.defaults.global,t.defaults[this.config.type],this.config.options||{}),this.id=e.uid(),Object.defineProperty(this,"data",{get:function(){return this.config.data}}),t.instances[this.id]=this,this.options.responsive&&this.resize(!0),this.initialize(),this},e.extend(t.Controller.prototype,{initialize:function(){return t.pluginService.notifyPlugins("beforeInit",[this]),this.bindEvents(),this.ensureScalesHaveIDs(),this.buildOrUpdateControllers(),this.buildScales(),this.buildSurroundingItems(),this.updateLayout(),this.resetElements(),this.initToolTip(),this.update(),t.pluginService.notifyPlugins("afterInit",[this]),this},clear:function(){return e.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var i=this.chart.canvas,s=e.getMaximumWidth(this.chart.canvas),a=this.options.maintainAspectRatio&&isNaN(this.chart.aspectRatio)===!1&&isFinite(this.chart.aspectRatio)&&0!==this.chart.aspectRatio?s/this.chart.aspectRatio:e.getMaximumHeight(this.chart.canvas),o=this.chart.width!==s||this.chart.height!==a;return o?(i.width=this.chart.width=s,i.height=this.chart.height=a,e.retinaScale(this.chart),t||(this.stop(),this.update(this.options.responsiveAnimationDuration)),this):this},ensureScalesHaveIDs:function(){var t="x-axis-",i="y-axis-";this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(e,i){e.id=e.id||t+i}),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(t,e){t.id=t.id||i+e}))},buildScales:function(){if(this.scales={},this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(i,s){var a=e.getValueOrDefault(i.type,"category"),o=t.scaleService.getScaleConstructor(a);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(i,s){var a=e.getValueOrDefault(i.type,"linear"),o=t.scaleService.getScaleConstructor(a);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this)),this.options.scale){var i=t.scaleService.getScaleConstructor(this.options.scale.type);if(i){var s=new i({ctx:this.chart.ctx,options:this.options.scale,chart:this});this.scale=s,this.scales.radialScale=s}}t.scaleService.addScalesToLayout(this)},buildSurroundingItems:function(){this.options.title&&(this.titleBlock=new t.Title({ctx:this.chart.ctx,options:this.options.title,chart:this}),t.layoutService.addBox(this,this.titleBlock)),this.options.legend&&(this.legend=new t.Legend({ctx:this.chart.ctx,options:this.options.legend,chart:this}),t.layoutService.addBox(this,this.legend))},updateLayout:function(){t.layoutService.update(this,this.chart.width,this.chart.height)},buildOrUpdateControllers:function(){var i=[],s=[];if(e.each(this.data.datasets,function(e,a){var o=this.getDatasetMeta(a);o.type||(o.type=e.type||this.config.type),i.push(o.type),o.controller?o.controller.updateIndex(a):(o.controller=new t.controllers[o.type](this,a),s.push(o.controller))},this),i.length>1)for(var a=1;a0&&(e=this.getDatasetMeta(e[0]._datasetIndex).data),e},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var i=e._meta[this.id];return i||(i=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,i=this.data.datasets.length;i>e;++e)this.isDatasetVisible(e)&&t++;return t},isDatasetVisible:function(t){var e=this.getDatasetMeta(t);return"boolean"==typeof e.hidden?!e.hidden:!this.data.datasets[t].hidden},generateLegend:function(){return this.options.legendCallback(this)},destroy:function(){this.clear(),e.unbindEvents(this,this.events),e.removeResizeListener(this.chart.canvas.parentNode);var i=this.chart.canvas;i.width=this.chart.width,i.height=this.chart.height,void 0!==this.chart.originalDevicePixelRatio&&this.chart.ctx.scale(1/this.chart.originalDevicePixelRatio,1/this.chart.originalDevicePixelRatio),i.style.width=this.chart.originalCanvasStyleWidth,i.style.height=this.chart.originalCanvasStyleHeight,t.pluginService.notifyPlugins("destroy",[this]),delete t.instances[this.id]},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)},initToolTip:function(){this.tooltip=new t.Tooltip({_chart:this.chart,_chartInstance:this,_data:this.data,_options:this.options},this)},bindEvents:function(){e.bindEvents(this,this.options.events,function(t){this.eventHandler(t)})},eventHandler:function(t){if(this.lastActive=this.lastActive||[],this.lastTooltipActive=this.lastTooltipActive||[],"mouseout"===t.type)this.active=[],this.tooltipActive=[];else{var i=this,s=function(e){switch(e){case"single":return i.getElementAtEvent(t);case"label":return i.getElementsAtEvent(t);case"dataset":return i.getDatasetAtEvent(t);default:return t}};this.active=s(this.options.hover.mode),this.tooltipActive=s(this.options.tooltips.mode)}if(this.options.hover.onHover&&this.options.hover.onHover.call(this,this.active),("mouseup"===t.type||"click"===t.type)&&(this.options.onClick&&this.options.onClick.call(this,t,this.active),this.legend&&this.legend.handleEvent&&this.legend.handleEvent(t)),this.lastActive.length)switch(this.options.hover.mode){case"single":this.getDatasetMeta(this.lastActive[0]._datasetIndex).controller.removeHoverStyle(this.lastActive[0],this.lastActive[0]._datasetIndex,this.lastActive[0]._index);break;case"label":case"dataset":for(var a=0;ai)e.splice(i,s-i);else if(i>s)for(var a=s;i>a;++a)this.addElementAndReset(a)},addElements:i,addElementAndReset:i,draw:i,removeHoverStyle:i,setHoverStyle:i,update:i}),t.DatasetController.extend=e.inherits}},{}],24:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.elements={},t.Element=function(t){e.extend(this,t),this.initialize.apply(this,arguments)},e.extend(t.Element.prototype,{initialize:function(){this.hidden=!1},pivot:function(){return this._view||(this._view=e.clone(this._model)),this._start=e.clone(this._view),this},transition:function(t){return this._view||(this._view=e.clone(this._model)),1===t?(this._view=this._model,this._start=null,this):(this._start||this.pivot(),e.each(this._model,function(i,s){if("_"!==s[0]&&this._model.hasOwnProperty(s))if(this._view.hasOwnProperty(s))if(i===this._view[s]);else if("string"==typeof i)try{var a=e.color(this._start[s]).mix(e.color(this._model[s]),t);this._view[s]=a.rgbString()}catch(o){this._view[s]=i}else if("number"==typeof i){var n=void 0!==this._start[s]&&isNaN(this._start[s])===!1?this._start[s]:0;this._view[s]=(this._model[s]-n)*t+n}else this._view[s]=i;else"number"!=typeof i||isNaN(this._view[s])?this._view[s]=i:this._view[s]=i*t;else;},this),this)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return e.isNumber(this._model.x)&&e.isNumber(this._model.y)}}),t.Element.extend=e.inherits}},{}],25:[function(t,e,i){"use strict";var s=t("chartjs-color");e.exports=function(t){function e(t,e,i){var s;return"string"==typeof t?(s=parseInt(t,10),-1!=t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}function i(t,i,s){var a,o=document.defaultView.getComputedStyle(t)[i],n=document.defaultView.getComputedStyle(t.parentNode)[i],r=null!==o&&"none"!==o,h=null!==n&&"none"!==n;return(r||h)&&(a=Math.min(r?e(o,t,s):Number.POSITIVE_INFINITY,h?e(n,t.parentNode,s):Number.POSITIVE_INFINITY)),a}var a=t.helpers={};a.each=function(t,e,i,s){var o,n;if(a.isArray(t))if(n=t.length,s)for(o=n-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;n>o;o++)e.call(i,t[o],o);else if("object"==typeof t){var r=Object.keys(t);for(n=r.length,o=0;n>o;o++)e.call(i,t[r[o]],r[o])}},a.clone=function(t){var e={};return a.each(t,function(i,s){t.hasOwnProperty(s)&&(a.isArray(i)?e[s]=i.slice(0):"object"==typeof i&&null!==i?e[s]=a.clone(i):e[s]=i)}),e},a.extend=function(t){for(var e=arguments.length,i=[],s=1;e>s;s++)i.push(arguments[s]);return a.each(i,function(e){a.each(e,function(i,s){e.hasOwnProperty(s)&&(t[s]=i)})}),t},a.configMerge=function(e){var i=a.clone(e);return a.each(Array.prototype.slice.call(arguments,1),function(e){a.each(e,function(s,o){if(e.hasOwnProperty(o))if("scales"===o)i[o]=a.scaleMerge(i.hasOwnProperty(o)?i[o]:{},s);else if("scale"===o)i[o]=a.configMerge(i.hasOwnProperty(o)?i[o]:{},t.scaleService.getScaleDefaults(s.type),s);else if(i.hasOwnProperty(o)&&a.isArray(i[o])&&a.isArray(s)){var n=i[o];a.each(s,function(t,e){e=s[o].length||!s[o][i].type?s[o].push(a.configMerge(r,e)):e.type&&e.type!==s[o][i].type?s[o][i]=a.configMerge(s[o][i],r,e):s[o][i]=a.configMerge(s[o][i],e)}):(s[o]=[],a.each(e,function(e){var i=a.getValueOrDefault(e.type,"xAxes"===o?"category":"linear");s[o].push(a.configMerge(t.scaleService.getScaleDefaults(i),e))})):s.hasOwnProperty(o)&&"object"==typeof s[o]&&null!==s[o]&&"object"==typeof e?s[o]=a.configMerge(s[o],e):s[o]=e)}),s},a.getValueAtIndexOrDefault=function(t,e,i){return void 0===t||null===t?i:a.isArray(t)?e=0;s--){var a=t[s];if(e(a))return a}},a.inherits=function(t){var e=this,i=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},s=function(){this.constructor=i};return s.prototype=e.prototype,i.prototype=new s,i.extend=a.inherits,t&&a.extend(i.prototype,t),i.__super__=e.prototype,i},a.noop=function(){},a.uid=function(){var t=0;return function(){return t++}}(),a.warn=function(t){console&&"function"==typeof console.warn&&console.warn(t)},a.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},a.almostEquals=function(t,e,i){return Math.abs(t-e)0?1:-1)},a.log10=function(t){return Math.log10?Math.log10(t):Math.log(t)/Math.LN10},a.toRadians=function(t){return t*(Math.PI/180)},a.toDegrees=function(t){return t*(180/Math.PI)},a.getAngleFromPoint=function(t,e){var i=e.x-t.x,s=e.y-t.y,a=Math.sqrt(i*i+s*s),o=Math.atan2(s,i);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:a}},a.aliasPixel=function(t){return t%2===0?0:.5},a.splineCurve=function(t,e,i,s){var a=t.skip?e:t,o=e,n=i.skip?e:i,r=Math.sqrt(Math.pow(o.x-a.x,2)+Math.pow(o.y-a.y,2)),h=Math.sqrt(Math.pow(n.x-o.x,2)+Math.pow(n.y-o.y,2)),l=r/(r+h),c=h/(r+h);l=isNaN(l)?0:l,c=isNaN(c)?0:c;var d=s*l,u=s*c;return{previous:{x:o.x-d*(n.x-a.x),y:o.y-d*(n.y-a.y)},next:{x:o.x+u*(n.x-a.x),y:o.y+u*(n.y-a.y)}}},a.nextItem=function(t,e,i){return i?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},a.previousItem=function(t,e,i){return i?0>=e?t[t.length-1]:t[e-1]:0>=e?t[0]:t[e-1]},a.niceNum=function(t,e){var i,s=Math.floor(a.log10(t)),o=t/Math.pow(10,s);return i=e?1.5>o?1:3>o?2:7>o?5:10:1>=o?1:2>=o?2:5>=o?5:10,i*Math.pow(10,s)};var o=a.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,s=1;return 0===t?0:1===(t/=1)?1:(i||(i=.3),st?-.5*(s*Math.pow(2,10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)):s*Math.pow(2,-10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-o.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?1*(7.5625*t*t):2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*o.easeInBounce(2*t):.5*o.easeOutBounce(2*t-1)+.5}};a.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),a.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),a.getRelativePosition=function(t,e){var i,s,o=t.originalEvent||t,n=t.currentTarget||t.srcElement,r=n.getBoundingClientRect();o.touches&&o.touches.length>0?(i=o.touches[0].clientX,s=o.touches[0].clientY):(i=o.clientX,s=o.clientY);var h=parseFloat(a.getStyle(n,"padding-left")),l=parseFloat(a.getStyle(n,"padding-top")),c=parseFloat(a.getStyle(n,"padding-right")),d=parseFloat(a.getStyle(n,"padding-bottom")),u=r.right-r.left-h-c,f=r.bottom-r.top-l-d;return i=Math.round((i-r.left-h)/u*n.width/e.currentDevicePixelRatio),s=Math.round((s-r.top-l)/f*n.height/e.currentDevicePixelRatio),{x:i,y:s}},a.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i},a.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=a.noop},a.bindEvents=function(t,e,i){t.events||(t.events={}),a.each(e,function(e){t.events[e]=function(){i.apply(t,arguments)},a.addEvent(t.chart.canvas,e,t.events[e])})},a.unbindEvents=function(t,e){a.each(e,function(e,i){a.removeEvent(t.chart.canvas,i,e)})},a.getConstraintWidth=function(t){return i(t,"max-width","clientWidth")},a.getConstraintHeight=function(t){return i(t,"max-height","clientHeight")},a.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(a.getStyle(e,"padding-left"))+parseInt(a.getStyle(e,"padding-right")),s=e.clientWidth-i,o=a.getConstraintWidth(t);return void 0!==o&&(s=Math.min(s,o)),s},a.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(a.getStyle(e,"padding-top"))+parseInt(a.getStyle(e,"padding-bottom")),s=e.clientHeight-i,o=a.getConstraintHeight(t);return void 0!==o&&(s=Math.min(s,o)),s},a.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},a.retinaScale=function(t){var e=t.ctx,i=t.canvas.width,s=t.canvas.height,a=t.currentDevicePixelRatio=window.devicePixelRatio||1;1!==a&&(e.canvas.height=s*a,e.canvas.width=i*a,e.scale(a,a),t.originalDevicePixelRatio=t.originalDevicePixelRatio||a),e.canvas.style.width=i+"px",e.canvas.style.height=s+"px"},a.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},a.fontString=function(t,e,i){return e+" "+t+"px "+i},a.longestText=function(t,e,i,s){s=s||{},s.data=s.data||{},s.garbageCollect=s.garbageCollect||[],s.font!==e&&(s.data={},s.garbageCollect=[],s.font=e),t.font=e;var o=0;a.each(i,function(e){if(void 0!==e&&null!==e){var i=s.data[e];i||(i=s.data[e]=t.measureText(e).width,s.garbageCollect.push(e)),i>o&&(o=i)}});var n=s.garbageCollect.length/2;if(n>i.length){for(var r=0;n>r;r++)delete s.data[s.garbageCollect[r]];s.garbageCollect.splice(0,n)}return o},a.drawRoundedRectangle=function(t,e,i,s,a,o){t.beginPath(),t.moveTo(e+o,i),t.lineTo(e+s-o,i),t.quadraticCurveTo(e+s,i,e+s,i+o),t.lineTo(e+s,i+a-o),t.quadraticCurveTo(e+s,i+a,e+s-o,i+a),t.lineTo(e+o,i+a),t.quadraticCurveTo(e,i+a,e,i+a-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath()},a.color=function(e){return s?s(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.log("Color.js not found!"),e)},a.addResizeListener=function(t,e){var i=document.createElement("iframe"),s="chartjs-hidden-iframe";i.classlist?i.classlist.add(s):i.setAttribute("class",s),i.style.width="100%",i.style.display="block",i.style.border=0,i.style.height=0,i.style.margin=0,i.style.position="absolute",i.style.left=0,i.style.right=0,i.style.top=0,i.style.bottom=0,t.insertBefore(i,t.firstChild),(i.contentWindow||i).onresize=function(){e&&e()}},a.removeResizeListener=function(t){var e=t.querySelector(".chartjs-hidden-iframe");e&&e.parentNode.removeChild(e)},a.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(t)},a.pushAllIfDefined=function(t,e){"undefined"!=typeof t&&(a.isArray(t)?e.push.apply(e,t):e.push(t))},a.callCallback=function(t,e,i){t&&"function"==typeof t.call&&t.apply(i,e)},a.getHoverColor=function(t){return t instanceof CanvasPattern?t:a.color(t).saturate(.5).darken(.1).rgbString()}}},{"chartjs-color":2}],26:[function(t,e,i){"use strict";e.exports=function(){var t=function(e,i){this.config=i,e.length&&e[0].getContext&&(e=e[0]),e.getContext&&(e=e.getContext("2d")),this.ctx=e,this.canvas=e.canvas,this.width=e.canvas.width||parseInt(t.helpers.getStyle(e.canvas,"width"))||t.helpers.getMaximumWidth(e.canvas),this.height=e.canvas.height||parseInt(t.helpers.getStyle(e.canvas,"height"))||t.helpers.getMaximumHeight(e.canvas),this.aspectRatio=this.width/this.height,(isNaN(this.aspectRatio)||isFinite(this.aspectRatio)===!1)&&(this.aspectRatio=void 0!==i.aspectRatio?i.aspectRatio:2),this.originalCanvasStyleWidth=e.canvas.style.width,this.originalCanvasStyleHeight=e.canvas.style.height,t.helpers.retinaScale(this),i&&(this.controller=new t.Controller(this));var s=this;return t.helpers.addResizeListener(e.canvas.parentNode,function(){s.controller&&s.controller.config.options.responsive&&s.controller.resize()}),this.controller?this.controller:this};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('');for(var i=0;i'),t.data.datasets[i].label&&e.push(t.data.datasets[i].label),e.push("");return e.push("
"),e.join("")}}},t}},{}],27:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,i,s){function a(t){var e,i=t.isHorizontal();i?(e=t.update(t.options.fullWidth?m:k,y),_-=e.height):(e=t.update(x,v),k-=e.width),S.push({horizontal:i,minSize:e,box:t})}function o(t){var i=e.findNextWhere(S,function(e){return e.box===t});if(i)if(t.isHorizontal()){var s={left:w,right:D,top:0,bottom:0};t.update(t.options.fullWidth?m:k,p/2,s)}else t.update(i.minSize.width,_)}function n(t){var i=e.findNextWhere(S,function(e){return e.box===t}),s={left:0,right:0,top:M,bottom:C};i&&t.update(i.minSize.width,_,s)}function r(t){t.isHorizontal()?(t.left=t.options.fullWidth?h:w,t.right=t.options.fullWidth?i-h:w+k,t.top=T,t.bottom=T+t.height,T=t.bottom):(t.left=P,t.right=P+t.width,t.top=M,t.bottom=M+_,P=t.right)}if(t){var h=0,l=0,c=e.where(t.boxes,function(t){return"left"===t.options.position}),d=e.where(t.boxes,function(t){return"right"===t.options.position}),u=e.where(t.boxes,function(t){return"top"===t.options.position}),f=e.where(t.boxes,function(t){return"bottom"===t.options.position}),g=e.where(t.boxes,function(t){return"chartArea"===t.options.position});u.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),f.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var m=i-2*h,p=s-2*l,b=m/2,v=p/2,x=(i-b)/(c.length+d.length),y=(s-v)/(u.length+f.length),k=m,_=p,S=[];e.each(c.concat(d,u,f),a);var w=h,D=h,M=l,C=l;e.each(c.concat(d),o),e.each(c,function(t){w+=t.width}),e.each(d,function(t){D+=t.width}),e.each(u.concat(f),o),e.each(u,function(t){M+=t.height}),e.each(f,function(t){C+=t.height}),e.each(c.concat(d),n),w=h,D=h,M=l,C=l,e.each(c,function(t){w+=t.width}),e.each(d,function(t){D+=t.width}),e.each(u,function(t){M+=t.height}),e.each(f,function(t){C+=t.height});var A=s-M-C,I=i-w-D;(I!==k||A!==_)&&(e.each(c,function(t){t.height=A}),e.each(d,function(t){t.height=A}),e.each(u,function(t){t.options.fullWidth||(t.width=I)}),e.each(f,function(t){t.options.fullWidth||(t.width=I)}),_=A,k=I);var P=h,T=l;e.each(c.concat(u),r),P+=k,T+=_,e.each(d,r),e.each(f,r),t.chartArea={left:w,top:M,right:w+k,bottom:M+_},e.each(g,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(k,_)})}}}}},{}],28:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i=e.noop;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var i=e.datasetIndex,s=this.chart,a=s.getDatasetMeta(i);a.hidden=null===a.hidden?!s.data.datasets[i].hidden:null,s.update()},labels:{boxWidth:40,padding:10,generateLabels:function(t){var i=t.data;return e.isArray(i.datasets)?i.datasets.map(function(e,i){return{text:e.label,fillStyle:e.backgroundColor,hidden:!t.isDatasetVisible(i),lineCap:e.borderCapStyle,lineDash:e.borderDash,lineDashOffset:e.borderDashOffset,lineJoin:e.borderJoinStyle,lineWidth:e.borderWidth,strokeStyle:e.borderColor,datasetIndex:i}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){e.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:i,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:i,beforeSetDimensions:i,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:i,beforeBuildLabels:i,buildLabels:function(){this.legendItems=this.options.labels.generateLabels.call(this,this.chart),this.options.reverse&&this.legendItems.reverse()},afterBuildLabels:i,beforeFit:i,fit:function(){var i=this.options,s=i.labels,a=i.display,o=this.ctx,n=t.defaults.global,r=e.getValueOrDefault,h=r(s.fontSize,n.defaultFontSize),l=r(s.fontStyle,n.defaultFontStyle),c=r(s.fontFamily,n.defaultFontFamily),d=e.fontString(h,l,c),u=this.legendHitBoxes=[],f=this.minSize,g=this.isHorizontal();if(g?(f.width=this.maxWidth,f.height=a?10:0):(f.width=a?10:0,f.height=this.maxHeight),a&&g){var m=this.lineWidths=[0],p=this.legendItems.length?h+s.padding:0;o.textAlign="left",o.textBaseline="top",o.font=d,e.each(this.legendItems,function(t,e){var i=s.boxWidth+h/2+o.measureText(t.text).width;m[m.length-1]+i+s.padding>=this.width&&(p+=h+s.padding,m[m.length]=this.left),u[e]={left:0,top:0,width:i,height:h},m[m.length-1]+=i+s.padding},this),f.height+=p}this.width=f.width,this.height=f.height},afterFit:i,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var i=this.options,s=i.labels,a=t.defaults.global,o=a.elements.line,n=this.width,r=this.lineWidths;if(i.display){var h=this.ctx,l={x:this.left+(n-r[0])/2,y:this.top+s.padding,line:0},c=e.getValueOrDefault,d=c(s.fontColor,a.defaultFontColor),u=c(s.fontSize,a.defaultFontSize),f=c(s.fontStyle,a.defaultFontStyle),g=c(s.fontFamily,a.defaultFontFamily),m=e.fontString(u,f,g);if(this.isHorizontal()){h.textAlign="left",h.textBaseline="top",h.lineWidth=.5,h.strokeStyle=d,h.fillStyle=d,h.font=m;var p=s.boxWidth,b=this.legendHitBoxes;e.each(this.legendItems,function(t,e){var i=h.measureText(t.text).width,d=p+u/2+i,f=l.x,g=l.y;f+d>=n&&(g=l.y+=u+s.padding,l.line++,f=l.x=this.left+(n-r[l.line])/2),h.save(),h.fillStyle=c(t.fillStyle,a.defaultColor),h.lineCap=c(t.lineCap,o.borderCapStyle),h.lineDashOffset=c(t.lineDashOffset,o.borderDashOffset),h.lineJoin=c(t.lineJoin,o.borderJoinStyle),h.lineWidth=c(t.lineWidth,o.borderWidth),h.strokeStyle=c(t.strokeStyle,a.defaultColor),h.setLineDash&&h.setLineDash(c(t.lineDash,o.borderDash)),h.strokeRect(f,g,p,u),h.fillRect(f,g,p,u),h.restore(),b[e].left=f,b[e].top=g,h.fillText(t.text,p+u/2+f,g),t.hidden&&(h.beginPath(),h.lineWidth=2,h.moveTo(p+u/2+f,g+u/2),h.lineTo(p+u/2+f+i,g+u/2),h.stroke()),l.x+=d+s.padding},this)}}},handleEvent:function(t){var i=e.getRelativePosition(t,this.chart.chart),s=i.x,a=i.y,o=this.options;if(s>=this.left&&s<=this.right&&a>=this.top&&a<=this.bottom)for(var n=this.legendHitBoxes,r=0;r=h.left&&s<=h.left+h.width&&a>=h.top&&a<=h.top+h.height){o.onClick&&o.onClick.call(this,t,this.legendItems[r]);break}}}})}},{}],29:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.plugins=[],t.pluginService={register:function(e){var i=t.plugins;-1===i.indexOf(e)&&i.push(e)},remove:function(e){var i=t.plugins,s=i.indexOf(e);-1!==s&&i.splice(s,1)},notifyPlugins:function(i,s,a){e.each(t.plugins,function(t){t[i]&&"function"==typeof t[i]&&t[i].apply(a,s)},a)}};var i=e.noop;t.PluginBase=t.Element.extend({beforeInit:i,afterInit:i,beforeUpdate:i,afterUpdate:i,beforeDraw:i,afterDraw:i,destroy:i})}},{}],30:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.scale={display:!0,position:"left",gridLines:{display:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickMarkLength:10,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",offsetGridLines:!1},scaleLabel:{labelString:"",display:!1},ticks:{beginAtZero:!1,minRotation:0,maxRotation:50,mirror:!1,padding:10,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,labelOffset:0,callback:function(t){return""+t}}},t.Scale=t.Element.extend({beforeUpdate:function(){e.callCallback(this.options.beforeUpdate,[this])},update:function(t,i,s){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=i,this.margins=e.extend({left:0,right:0,top:0,bottom:0},s),this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this.beforeBuildTicks(),this.buildTicks(),this.afterBuildTicks(),this.beforeTickToLabelConversion(),this.convertTicksToLabels(),this.afterTickToLabelConversion(),this.beforeCalculateTickRotation(),this.calculateTickRotation(),this.afterCalculateTickRotation(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:function(){e.callCallback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){e.callCallback(this.options.beforeSetDimensions,[this])},setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0},afterSetDimensions:function(){e.callCallback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){e.callCallback(this.options.beforeDataLimits,[this])},determineDataLimits:e.noop,afterDataLimits:function(){e.callCallback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){e.callCallback(this.options.beforeBuildTicks,[this])},buildTicks:e.noop,afterBuildTicks:function(){e.callCallback(this.options.afterBuildTicks,[this])},beforeTickToLabelConversion:function(){e.callCallback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){this.ticks=this.ticks.map(function(t,e,i){return this.options.ticks.userCallback?this.options.ticks.userCallback(t,e,i):this.options.ticks.callback(t,e,i)},this)},afterTickToLabelConversion:function(){e.callCallback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){e.callCallback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),s=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),a=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),o=e.fontString(i,s,a);this.ctx.font=o;var n,r=this.ctx.measureText(this.ticks[0]).width,h=this.ctx.measureText(this.ticks[this.ticks.length-1]).width;if(this.labelRotation=this.options.ticks.minRotation||0,this.paddingRight=0,this.paddingLeft=0,this.options.display&&this.isHorizontal()){this.paddingRight=h/2+3,this.paddingLeft=r/2+3,this.longestTextCache||(this.longestTextCache={});for(var l,c,d=e.longestText(this.ctx,o,this.ticks,this.longestTextCache),u=d,f=this.getPixelForTick(1)-this.getPixelForTick(0)-6;u>f&&this.labelRotationthis.yLabelWidth&&(this.paddingLeft=n+i/2),this.paddingRight=i/2,c*d>this.maxHeight){this.labelRotation--;break}this.labelRotation++,u=l*d}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0))},afterCalculateTickRotation:function(){e.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){e.callCallback(this.options.beforeFit,[this])},fit:function(){var i=this.minSize={width:0,height:0},s=this.options,a=s.ticks,o=s.scaleLabel,n=t.defaults.global,r=s.display,h=this.isHorizontal(),l=e.getValueOrDefault(a.fontSize,n.defaultFontSize),c=e.getValueOrDefault(a.fontStyle,n.defaultFontStyle),d=e.getValueOrDefault(a.fontFamily,n.defaultFontFamily),u=e.fontString(l,c,d),f=e.getValueOrDefault(o.fontSize,n.defaultFontSize),g=e.getValueOrDefault(o.fontStyle,n.defaultFontStyle),m=e.getValueOrDefault(o.fontFamily,n.defaultFontFamily),p=(e.fontString(f,g,m),s.gridLines.tickMarkLength);if(h?i.width=this.isFullWidth()?this.maxWidth-this.margins.left-this.margins.right:this.maxWidth:i.width=r?p:0,h?i.height=r?p:0:i.height=this.maxHeight,o.display&&r&&(h?i.height+=1.5*f:i.width+=1.5*f),a.display&&r){this.longestTextCache||(this.longestTextCache={});var b=e.longestText(this.ctx,u,this.ticks,this.longestTextCache);if(h){this.longestLabelWidth=b;var v=Math.sin(e.toRadians(this.labelRotation))*this.longestLabelWidth+1.5*l;i.height=Math.min(this.maxHeight,i.height+v),this.ctx.font=u;var x=this.ctx.measureText(this.ticks[0]).width,y=this.ctx.measureText(this.ticks[this.ticks.length-1]).width,k=Math.cos(e.toRadians(this.labelRotation)),_=Math.sin(e.toRadians(this.labelRotation));this.paddingLeft=0!==this.labelRotation?k*x+3:x/2+3,this.paddingRight=0!==this.labelRotation?_*(l/2)+3:y/2+3}else{var S=this.maxWidth-i.width,w=a.mirror;w?b=0:b+=this.options.ticks.padding,S>b?i.width+=b:i.width=this.maxWidth,this.paddingTop=l/2,this.paddingBottom=l/2}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingTop=Math.max(this.paddingTop-this.margins.top,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0),this.paddingBottom=Math.max(this.paddingBottom-this.margins.bottom,0)),this.width=i.width,this.height=i.height},afterFit:function(){e.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function i(t){return null===t||"undefined"==typeof t?NaN:"number"==typeof t&&isNaN(t)?NaN:"object"==typeof t?t instanceof Date?t:i(this.isHorizontal()?t.x:t.y):t},getLabelForIndex:e.noop,getPixelForValue:e.noop,getValueForPixel:e.noop,getPixelForTick:function(t,e){if(this.isHorizontal()){var i=this.width-(this.paddingLeft+this.paddingRight),s=i/Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1),a=s*t+this.paddingLeft;e&&(a+=s/2);var o=this.left+Math.round(a);return o+=this.isFullWidth()?this.margins.left:0}var n=this.height-(this.paddingTop+this.paddingBottom);return this.top+t*(n/(this.ticks.length-1))},getPixelForDecimal:function(t){if(this.isHorizontal()){var e=this.width-(this.paddingLeft+this.paddingRight),i=e*t+this.paddingLeft,s=this.left+Math.round(i);return s+=this.isFullWidth()?this.margins.left:0}return this.top+t*this.height},draw:function(i){if(this.options.display){var s,a,o,n,r,h=0!==this.labelRotation,l=this.options.ticks.autoSkip;this.options.ticks.maxTicksLimit&&(r=this.options.ticks.maxTicksLimit);var c=e.getValueOrDefault(this.options.ticks.fontColor,t.defaults.global.defaultFontColor),d=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),u=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),f=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),g=e.fontString(d,u,f),m=this.options.gridLines.tickMarkLength,p=e.getValueOrDefault(this.options.scaleLabel.fontColor,t.defaults.global.defaultFontColor),b=e.getValueOrDefault(this.options.scaleLabel.fontSize,t.defaults.global.defaultFontSize),v=e.getValueOrDefault(this.options.scaleLabel.fontStyle,t.defaults.global.defaultFontStyle),x=e.getValueOrDefault(this.options.scaleLabel.fontFamily,t.defaults.global.defaultFontFamily),y=e.fontString(b,v,x),k=Math.cos(e.toRadians(this.labelRotation)),_=(Math.sin(e.toRadians(this.labelRotation)),
+this.longestLabelWidth*k);if(this.ctx.fillStyle=c,this.isHorizontal()){s=!0;var S="bottom"===this.options.position?this.top:this.bottom-m,w="bottom"===this.options.position?this.top+m:this.bottom;if(a=!1,(_/2+this.options.ticks.autoSkipPadding)*this.ticks.length>this.width-(this.paddingLeft+this.paddingRight)&&(a=1+Math.floor((_/2+this.options.ticks.autoSkipPadding)*this.ticks.length/(this.width-(this.paddingLeft+this.paddingRight)))),r&&this.ticks.length>r)for(;!a||this.ticks.length/(a||1)>r;)a||(a=1),a+=1;l||(a=!1),e.each(this.ticks,function(t,o){var n=this.ticks.length===o+1,r=a>1&&o%a>0||o%a===0&&o+a>this.ticks.length;if((!r||n)&&void 0!==t&&null!==t){var l=this.getPixelForTick(o),c=this.getPixelForTick(o,this.options.gridLines.offsetGridLines);this.options.gridLines.display&&(o===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,s=!0):s&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,s=!1),l+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(l,S),this.ctx.lineTo(l,w)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(l,i.top),this.ctx.lineTo(l,i.bottom)),this.ctx.stroke()),this.options.ticks.display&&(this.ctx.save(),this.ctx.translate(c+this.options.ticks.labelOffset,h?this.top+12:"top"===this.options.position?this.bottom-m:this.top+m),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textAlign=h?"right":"center",this.ctx.textBaseline=h?"middle":"top"===this.options.position?"bottom":"top",this.ctx.fillText(t,0,0),this.ctx.restore())}},this),this.options.scaleLabel.display&&(this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillStyle=p,this.ctx.font=y,o=this.left+(this.right-this.left)/2,n="bottom"===this.options.position?this.bottom-b/2:this.top+b/2,this.ctx.fillText(this.options.scaleLabel.labelString,o,n))}else{s=!0;var D="right"===this.options.position?this.left:this.right-5,M="right"===this.options.position?this.left+5:this.right;if(e.each(this.ticks,function(t,a){if(void 0!==t&&null!==t){var o=this.getPixelForTick(a);if(this.options.gridLines.display&&(a===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,s=!0):s&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,s=!1),o+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(D,o),this.ctx.lineTo(M,o)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(i.left,o),this.ctx.lineTo(i.right,o)),this.ctx.stroke()),this.options.ticks.display){var n,r=this.getPixelForTick(a,this.options.gridLines.offsetGridLines);this.ctx.save(),"left"===this.options.position?this.options.ticks.mirror?(n=this.right+this.options.ticks.padding,this.ctx.textAlign="left"):(n=this.right-this.options.ticks.padding,this.ctx.textAlign="right"):this.options.ticks.mirror?(n=this.left-this.options.ticks.padding,this.ctx.textAlign="right"):(n=this.left+this.options.ticks.padding,this.ctx.textAlign="left"),this.ctx.translate(n,r+this.options.ticks.labelOffset),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textBaseline="middle",this.ctx.fillText(t,0,0),this.ctx.restore()}}},this),this.options.scaleLabel.display){o="left"===this.options.position?this.left+b/2:this.right-b/2,n=this.top+(this.bottom-this.top)/2;var C="left"===this.options.position?-.5*Math.PI:.5*Math.PI;this.ctx.save(),this.ctx.translate(o,n),this.ctx.rotate(C),this.ctx.textAlign="center",this.ctx.fillStyle=p,this.ctx.font=y,this.ctx.textBaseline="middle",this.ctx.fillText(this.options.scaleLabel.labelString,0,0),this.ctx.restore()}}this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color;var A=this.left,I=this.right,P=this.top,T=this.bottom;this.isHorizontal()?(P=T="top"===this.options.position?this.bottom:this.top,P+=e.aliasPixel(this.ctx.lineWidth),T+=e.aliasPixel(this.ctx.lineWidth)):(A=I="left"===this.options.position?this.right:this.left,A+=e.aliasPixel(this.ctx.lineWidth),I+=e.aliasPixel(this.ctx.lineWidth)),this.ctx.beginPath(),this.ctx.moveTo(A,P),this.ctx.lineTo(I,T),this.ctx.stroke()}}})}},{}],31:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.scaleService={constructors:{},defaults:{},registerScaleType:function(t,i,s){this.constructors[t]=i,this.defaults[t]=e.clone(s)},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0},getScaleDefaults:function(i){return this.defaults.hasOwnProperty(i)?e.scaleMerge(t.defaults.scale,this.defaults[i]):{}},updateScaleDefaults:function(t,i){var s=this.defaults;s.hasOwnProperty(t)&&(s[t]=e.extend(s[t],i))},addScalesToLayout:function(i){e.each(i.scales,function(e){t.layoutService.addBox(i,e)})}}}},{}],32:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.title={display:!1,position:"top",fullWidth:!0,fontStyle:"bold",padding:10,text:""};var i=e.noop;t.Title=t.Element.extend({initialize:function(i){e.extend(this,i),this.options=e.configMerge(t.defaults.global.title,i.options),this.legendHitBoxes=[]},beforeUpdate:i,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:i,beforeSetDimensions:i,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:i,beforeBuildLabels:i,buildLabels:i,afterBuildLabels:i,beforeFit:i,fit:function(){var i=(this.ctx,e.getValueOrDefault),s=this.options,a=t.defaults.global,o=s.display,n=i(s.fontSize,a.defaultFontSize),r=this.minSize;this.isHorizontal()?(r.width=this.maxWidth,r.height=o?n+2*s.padding:0):(r.width=o?n+2*s.padding:0,r.height=this.maxHeight),this.width=r.width,this.height=r.height},afterFit:i,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var i=this.ctx,s=e.getValueOrDefault,a=this.options,o=t.defaults.global;if(a.display){var n,r,h=s(a.fontSize,o.defaultFontSize),l=s(a.fontStyle,o.defaultFontStyle),c=s(a.fontFamily,o.defaultFontFamily),d=e.fontString(h,l,c),u=0;i.fillStyle=s(a.fontColor,o.defaultFontColor),i.font=d,this.isHorizontal()?(n=this.left+(this.right-this.left)/2,r=this.top+(this.bottom-this.top)/2):(n="left"===a.position?this.left+h/2:this.right-h/2,r=this.top+(this.bottom-this.top)/2,u=Math.PI*("left"===a.position?-.5:.5)),i.save(),i.translate(n,r),i.rotate(u),i.textAlign="center",i.textBaseline="middle",i.fillText(a.text,0,0),i.restore()}}})}},{}],33:[function(t,e,i){"use strict";e.exports=function(t){function e(t,e){return e&&(i.isArray(e)?t=t.concat(e):t.push(e)),t}var i=t.helpers;t.defaults.global.tooltips={enabled:!0,custom:null,mode:"single",backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleColor:"#fff",titleAlign:"left",bodySpacing:2,bodyColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,yAlign:"center",xAlign:"center",caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",callbacks:{beforeTitle:i.noop,title:function(t,e){var i="";return t.length>0&&(t[0].xLabel?i=t[0].xLabel:e.labels.length>0&&t[0].indexthis._chart.height-t.height&&(this._model.yAlign="bottom");var e,i,s,a,o,n=this,r=(this._chartInstance.chartArea.left+this._chartInstance.chartArea.right)/2,h=(this._chartInstance.chartArea.top+this._chartInstance.chartArea.bottom)/2;"center"===this._model.yAlign?(e=function(t){return r>=t},i=function(t){return t>r}):(e=function(e){return e<=t.width/2},i=function(e){return e>=n._chart.width-t.width/2}),s=function(e){return e+t.width>n._chart.width},a=function(e){return e-t.width<0},o=function(t){return h>=t?"top":"bottom"},e(this._model.x)?(this._model.xAlign="left",s(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y))):i(this._model.x)&&(this._model.xAlign="right",a(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y)))},getBackgroundPoint:function(t,e){var i={x:t.x,y:t.y};return"right"===t.xAlign?i.x-=e.width:"center"===t.xAlign&&(i.x-=e.width/2),"top"===t.yAlign?i.y+=t.caretPadding+t.caretSize:"bottom"===t.yAlign?i.y-=e.height+t.caretPadding+t.caretSize:i.y-=e.height/2,"center"===t.yAlign?"left"===t.xAlign?i.x+=t.caretPadding+t.caretSize:"right"===t.xAlign&&(i.x-=t.caretPadding+t.caretSize):"left"===t.xAlign?i.x-=t.cornerRadius+t.caretPadding:"right"===t.xAlign&&(i.x+=t.cornerRadius+t.caretPadding),i},drawCaret:function(t,e,s,a){var o,n,r,h,l,c,d=this._view,u=this._chart.ctx;"center"===d.yAlign?("left"===d.xAlign?(o=t.x,n=o-d.caretSize,r=o):(o=t.x+e.width,n=o+d.caretSize,r=o),l=t.y+e.height/2,h=l-d.caretSize,c=l+d.caretSize):("left"===d.xAlign?(o=t.x+d.cornerRadius,n=o+d.caretSize,r=n+d.caretSize):"right"===d.xAlign?(o=t.x+e.width-d.cornerRadius,n=o-d.caretSize,r=n-d.caretSize):(n=t.x+e.width/2,o=n-d.caretSize,r=n+d.caretSize),"top"===d.yAlign?(h=t.y,l=h-d.caretSize,c=h):(h=t.y+e.height,l=h+d.caretSize,c=h));var f=i.color(d.backgroundColor);u.fillStyle=f.alpha(s*f.alpha()).rgbString(),u.beginPath(),u.moveTo(o,h),u.lineTo(n,l),u.lineTo(r,c),u.closePath(),u.fill()},drawTitle:function(t,e,s,a){if(e.title.length){s.textAlign=e._titleAlign,s.textBaseline="top";var o=i.color(e.titleColor);s.fillStyle=o.alpha(a*o.alpha()).rgbString(),s.font=i.fontString(e.titleFontSize,e._titleFontStyle,e._titleFontFamily),i.each(e.title,function(i,a){s.fillText(i,t.x,t.y),t.y+=e.titleFontSize+e.titleSpacing,a+1===e.title.length&&(t.y+=e.titleMarginBottom-e.titleSpacing)})}},drawBody:function(t,e,s,a){s.textAlign=e._bodyAlign,s.textBaseline="top";var o=i.color(e.bodyColor);s.fillStyle=o.alpha(a*o.alpha()).rgbString(),s.font=i.fontString(e.bodyFontSize,e._bodyFontStyle,e._bodyFontFamily),i.each(e.beforeBody,function(i){s.fillText(i,t.x,t.y),t.y+=e.bodyFontSize+e.bodySpacing}),i.each(e.body,function(o,n){"single"!==this._options.tooltips.mode&&(s.fillStyle=i.color(e.legendColorBackground).alpha(a).rgbaString(),s.fillRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),s.strokeStyle=i.color(e.labelColors[n].borderColor).alpha(a).rgbaString(),s.strokeRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),s.fillStyle=i.color(e.labelColors[n].backgroundColor).alpha(a).rgbaString(),s.fillRect(t.x+1,t.y+1,e.bodyFontSize-2,e.bodyFontSize-2),s.fillStyle=i.color(e.bodyColor).alpha(a).rgbaString()),s.fillText(o,t.x+("single"!==this._options.tooltips.mode?e.bodyFontSize+2:0),t.y),t.y+=e.bodyFontSize+e.bodySpacing},this),i.each(e.afterBody,function(i){s.fillText(i,t.x,t.y),t.y+=e.bodyFontSize}),t.y-=e.bodySpacing},drawFooter:function(t,e,s,a){if(e.footer.length){t.y+=e.footerMarginTop,s.textAlign=e._footerAlign,s.textBaseline="top";var o=i.color(e.footerColor);s.fillStyle=o.alpha(a*o.alpha()).rgbString(),s.font=i.fontString(e.footerFontSize,e._footerFontStyle,e._footerFontFamily),i.each(e.footer,function(i){s.fillText(i,t.x,t.y),t.y+=e.footerFontSize+e.footerSpacing})}},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var s=e.caretPadding,a=this.getTooltipSize(e),o={x:e.x,y:e.y},n=Math.abs(e.opacity<.001)?0:e.opacity;if(this._options.tooltips.enabled){var r=i.color(e.backgroundColor);t.fillStyle=r.alpha(n*r.alpha()).rgbString(),i.drawRoundedRectangle(t,o.x,o.y,a.width,a.height,e.cornerRadius),t.fill(),this.drawCaret(o,a,n,s),o.x+=e.xPadding,o.y+=e.yPadding,this.drawTitle(o,e,t,n),this.drawBody(o,e,t,n),this.drawFooter(o,e,t,n)}}}})}},{}],34:[function(t,e,i){"use strict";e.exports=function(t,e){var i=t.helpers,s=t.defaults.global;s.elements.arc={backgroundColor:s.defaultColor,borderColor:"#fff",borderWidth:2},t.elements.Arc=t.Element.extend({inLabelRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)h;)h+=2*Math.PI;for(;o>h;)o-=2*Math.PI;for(;r>o;)o+=2*Math.PI;var l=o>=r&&h>=o,c=n>=s.innerRadius&&n<=s.outerRadius;return l&&c}return!1},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t=this._chart.ctx,e=this._view,i=e.startAngle,s=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,i,s),t.arc(e.x,e.y,e.innerRadius,s,i,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],35:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.elements.line={tension:.4,backgroundColor:t.defaults.global.defaultColor,borderWidth:3,borderColor:t.defaults.global.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",fill:!0},t.elements.Line=t.Element.extend({lineToNextPoint:function(t,e,i,s,a){var o=this._chart.ctx;e._view.skip?s.call(this,t,e,i):t._view.skip?a.call(this,t,e,i):0===e._view.tension?o.lineTo(e._view.x,e._view.y):o.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y)},draw:function(){function i(t){n._view.skip||r._view.skip?t&&o.lineTo(s._view.scaleZero.x,s._view.scaleZero.y):o.bezierCurveTo(r._view.controlPointNextX,r._view.controlPointNextY,n._view.controlPointPreviousX,n._view.controlPointPreviousY,n._view.x,n._view.y)}var s=this,a=this._view,o=this._chart.ctx,n=this._children[0],r=this._children[this._children.length-1];o.save(),this._children.length>0&&a.fill&&(o.beginPath(),e.each(this._children,function(t,i){var s=e.previousItem(this._children,i),n=e.nextItem(this._children,i);0===i?(this._loop?o.moveTo(a.scaleZero.x,a.scaleZero.y):o.moveTo(t._view.x,a.scaleZero),t._view.skip?this._loop||o.moveTo(n._view.x,this._view.scaleZero):o.lineTo(t._view.x,t._view.y)):this.lineToNextPoint(s,t,n,function(t,e,i){this._loop?o.lineTo(this._view.scaleZero.x,this._view.scaleZero.y):(o.lineTo(t._view.x,this._view.scaleZero),o.moveTo(i._view.x,this._view.scaleZero))},function(t,e){o.lineTo(e._view.x,e._view.y)})},this),this._loop?i(!0):(o.lineTo(this._children[this._children.length-1]._view.x,a.scaleZero),o.lineTo(this._children[0]._view.x,a.scaleZero)),o.fillStyle=a.backgroundColor||t.defaults.global.defaultColor,o.closePath(),o.fill()),o.lineCap=a.borderCapStyle||t.defaults.global.elements.line.borderCapStyle,o.setLineDash&&o.setLineDash(a.borderDash||t.defaults.global.elements.line.borderDash),o.lineDashOffset=a.borderDashOffset||t.defaults.global.elements.line.borderDashOffset,o.lineJoin=a.borderJoinStyle||t.defaults.global.elements.line.borderJoinStyle,o.lineWidth=a.borderWidth||t.defaults.global.elements.line.borderWidth,o.strokeStyle=a.borderColor||t.defaults.global.defaultColor,o.beginPath(),e.each(this._children,function(t,i){var s=e.previousItem(this._children,i),a=e.nextItem(this._children,i);0===i?o.moveTo(t._view.x,t._view.y):this.lineToNextPoint(s,t,a,function(t,e,i){o.moveTo(i._view.x,i._view.y)},function(t,e){o.moveTo(e._view.x,e._view.y)})},this),this._loop&&this._children.length>0&&i(),o.stroke(),o.restore()}})}},{}],36:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i=t.defaults.global;i.elements.point={radius:3,pointStyle:"circle",backgroundColor:i.defaultColor,borderWidth:1,borderColor:i.defaultColor,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},t.elements.Point=t.Element.extend({inRange:function(t,e){var i=this._view;return i?Math.pow(t-i.x,2)+Math.pow(e-i.y,2)0){o.strokeStyle=i.borderColor||t.defaults.global.defaultColor,o.lineWidth=e.getValueOrDefault(i.borderWidth,t.defaults.global.elements.point.borderWidth),o.fillStyle=i.backgroundColor||t.defaults.global.defaultColor;var r,h,l=i.radius;switch(n){default:o.beginPath(),o.arc(s,a,l,0,2*Math.PI),o.closePath(),o.fill();break;case"triangle":o.beginPath();var c=3*l/Math.sqrt(3),d=c*Math.sqrt(3)/2;o.moveTo(s-c/2,a+d/3),o.lineTo(s+c/2,a+d/3),o.lineTo(s,a-2*d/3),o.closePath(),o.fill();break;case"rect":o.fillRect(s-1/Math.SQRT2*l,a-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l),o.strokeRect(s-1/Math.SQRT2*l,a-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l);break;case"rectRot":o.translate(s,a),o.rotate(Math.PI/4),o.fillRect(-1/Math.SQRT2*l,-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l),o.strokeRect(-1/Math.SQRT2*l,-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l),o.setTransform(1,0,0,1,0,0);break;case"cross":o.beginPath(),o.moveTo(s,a+l),o.lineTo(s,a-l),o.moveTo(s-l,a),o.lineTo(s+l,a),o.closePath();break;case"crossRot":o.beginPath(),r=Math.cos(Math.PI/4)*l,h=Math.sin(Math.PI/4)*l,o.moveTo(s-r,a-h),o.lineTo(s+r,a+h),o.moveTo(s-r,a+h),o.lineTo(s+r,a-h),o.closePath();break;case"star":o.beginPath(),o.moveTo(s,a+l),o.lineTo(s,a-l),o.moveTo(s-l,a),o.lineTo(s+l,a),r=Math.cos(Math.PI/4)*l,h=Math.sin(Math.PI/4)*l,o.moveTo(s-r,a-h),o.lineTo(s+r,a+h),o.moveTo(s-r,a+h),o.lineTo(s+r,a-h),o.closePath();break;case"line":o.beginPath(),o.moveTo(s-l,a),o.lineTo(s+l,a),o.closePath();break;case"dash":o.beginPath(),o.moveTo(s,a),o.lineTo(s+l,a),o.closePath()}o.stroke()}}}})}},{}],37:[function(t,e,i){"use strict";e.exports=function(t){var e=(t.helpers,t.defaults.global);e.elements.rectangle={backgroundColor:e.defaultColor,borderWidth:0,borderColor:e.defaultColor,borderSkipped:"bottom"},t.elements.Rectangle=t.Element.extend({draw:function(){function t(t){return h[(c+t)%4]}var e=this._chart.ctx,i=this._view,s=i.width/2,a=i.x-s,o=i.x+s,n=i.base-(i.base-i.y),r=i.borderWidth/2;i.borderWidth&&(a+=r,o-=r,n+=r),e.beginPath(),e.fillStyle=i.backgroundColor,e.strokeStyle=i.borderColor,e.lineWidth=i.borderWidth;var h=[[a,i.base],[a,n],[o,n],[o,i.base]],l=["bottom","left","top","right"],c=l.indexOf(i.borderSkipped,0);-1===c&&(c=0),e.moveTo.apply(e,t(0));for(var d=1;4>d;d++)e.lineTo.apply(e,t(d));e.fill(),i.borderWidth&&e.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=this._view;return i?i.y=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.y&&e<=i.base:t>=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.base&&e<=i.y:!1},inLabelRange:function(t){var e=this._view;return e?t>=e.x-e.width/2&&t<=e.x+e.width/2:!1},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],38:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"bottom"},s=t.Scale.extend({determineDataLimits:function(){this.minIndex=0,this.maxIndex=this.chart.data.labels.length-1;var t;void 0!==this.options.ticks.min&&(t=e.indexOf(this.chart.data.labels,this.options.ticks.min),this.minIndex=-1!==t?t:this.minIndex),void 0!==this.options.ticks.max&&(t=e.indexOf(this.chart.data.labels,this.options.ticks.max),this.maxIndex=-1!==t?t:this.maxIndex),this.min=this.chart.data.labels[this.minIndex],this.max=this.chart.data.labels[this.maxIndex]},buildTicks:function(t){this.ticks=0===this.minIndex&&this.maxIndex===this.chart.data.labels.length-1?this.chart.data.labels:this.chart.data.labels.slice(this.minIndex,this.maxIndex+1)},getLabelForIndex:function(t,e){return this.ticks[t]},getPixelForValue:function(t,e,i,s){var a=Math.max(this.maxIndex+1-this.minIndex-(this.options.gridLines.offsetGridLines?0:1),1);if(this.isHorizontal()){var o=this.width-(this.paddingLeft+this.paddingRight),n=o/a,r=n*(e-this.minIndex)+this.paddingLeft;return this.options.gridLines.offsetGridLines&&s&&(r+=n/2),this.left+Math.round(r)}var h=this.height-(this.paddingTop+this.paddingBottom),l=h/a,c=l*(e-this.minIndex)+this.paddingTop;return this.options.gridLines.offsetGridLines&&s&&(c+=l/2),this.top+Math.round(c)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.minIndex,null,e)},getValueForPixel:function(t){var e,i=Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1),s=this.isHorizontal(),a=s?this.width-(this.paddingLeft+this.paddingRight):this.height-(this.paddingTop+this.paddingBottom),o=a/i;return this.options.gridLines.offsetGridLines&&(t-=o/2),t-=s?this.paddingLeft:this.paddingTop,e=0>=t?0:Math.round(t/o)}});t.scaleService.registerScaleType("category",s,i)}},{}],39:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(t,i,s){var a=s.length>3?s[2]-s[1]:s[1]-s[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var o=e.log10(Math.abs(a)),n="";if(0!==t){var r=-1*Math.floor(o);r=Math.max(Math.min(r,20),0),n=t.toFixed(r)}else n="0";return n}}},s=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={},i=!1,s=!1;e.each(this.chart.data.datasets,function(a,o){var n=this.chart.getDatasetMeta(o);void 0===t[n.type]&&(t[n.type]={positiveValues:[],negativeValues:[]});var r=t[n.type].positiveValues,h=t[n.type].negativeValues;this.chart.isDatasetVisible(o)&&(this.isHorizontal()?n.xAxisID===this.id:n.yAxisID===this.id)&&e.each(a.data,function(t,e){var a=+this.getRightValue(t);isNaN(a)||n.data[e].hidden||(r[e]=r[e]||0,h[e]=h[e]||0,this.options.relativePoints?r[e]=100:0>a?(s=!0,h[e]+=a):(i=!0,r[e]+=a))},this)},this),e.each(t,function(t){var i=t.positiveValues.concat(t.negativeValues),s=e.min(i),a=e.max(i);this.min=null===this.min?s:Math.min(this.min,s),this.max=null===this.max?a:Math.max(this.max,a)},this)}else e.each(this.chart.data.datasets,function(t,i){var s=this.chart.getDatasetMeta(i);this.chart.isDatasetVisible(i)&&(this.isHorizontal()?s.xAxisID===this.id:s.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||s.data[e].hidden||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);if(this.options.ticks.beginAtZero){var a=e.sign(this.min),o=e.sign(this.max);0>a&&0>o?this.max=0:a>0&&o>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.max++,this.options.ticks.beginAtZero||this.min--)},buildTicks:function(){this.ticks=[];var i;if(this.isHorizontal())i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.width/50));else{var s=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize);i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.height/(2*s)))}i=Math.max(2,i);var a,o=this.options.ticks.fixedStepSize&&this.options.ticks.fixedStepSize>0||this.options.ticks.stepSize&&this.options.ticks.stepSize>0;if(o)a=e.getValueOrDefault(this.options.ticks.fixedStepSize,this.options.ticks.stepSize);else{var n=e.niceNum(this.max-this.min,!1);a=e.niceNum(n/(i-1),!0)}var r=Math.floor(this.min/a)*a,h=Math.ceil(this.max/a)*a,l=(h-r)/a;l=e.almostEquals(l,Math.round(l),a/1e3)?Math.round(l):Math.ceil(l),this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:r);for(var c=1;l>c;++c)this.ticks.push(r+c*a);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:h),("left"===this.options.position||"right"===this.options.position)&&this.ticks.reverse(),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},convertTicksToLabels:function(){this.ticksAsNumbers=this.ticks.slice(),this.zeroLineIndex=this.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(this)},getPixelForValue:function(t,e,i,s){var a,o=+this.getRightValue(t),n=this.end-this.start;if(this.isHorizontal()){var r=this.width-(this.paddingLeft+this.paddingRight);return a=this.left+r/n*(o-this.start),Math.round(a+this.paddingLeft)}var h=this.height-(this.paddingTop+this.paddingBottom);return a=this.bottom-this.paddingBottom-h/n*(o-this.start),Math.round(a)},getValueForPixel:function(t){var e;if(this.isHorizontal()){var i=this.width-(this.paddingLeft+this.paddingRight);e=(t-this.left-this.paddingLeft)/i}else{var s=this.height-(this.paddingTop+this.paddingBottom);e=(this.bottom-this.paddingBottom-t)/s}return this.start+(this.end-this.start)*e},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticksAsNumbers[t],null,null,e)}});t.scaleService.registerScaleType("linear",s,i)}},{}],40:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(e,i,s){var a=e/Math.pow(10,Math.floor(t.helpers.log10(e)));
+return 1===a||2===a||5===a||0===i||i===s.length-1?e.toExponential():""}}},s=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={};e.each(this.chart.data.datasets,function(i,s){var a=this.chart.getDatasetMeta(s);this.chart.isDatasetVisible(s)&&(this.isHorizontal()?a.xAxisID===this.id:a.yAxisID===this.id)&&(void 0===t[a.type]&&(t[a.type]=[]),e.each(i.data,function(e,i){var s=t[a.type],o=+this.getRightValue(e);isNaN(o)||a.data[i].hidden||(s[i]=s[i]||0,this.options.relativePoints?s[i]=100:s[i]+=o)},this))},this),e.each(t,function(t){var i=e.min(t),s=e.max(t);this.min=null===this.min?i:Math.min(this.min,i),this.max=null===this.max?s:Math.max(this.max,s)},this)}else e.each(this.chart.data.datasets,function(t,i){var s=this.chart.getDatasetMeta(i);this.chart.isDatasetVisible(i)&&(this.isHorizontal()?s.xAxisID===this.id:s.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||s.data[e].hidden||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);this.min=void 0!==this.options.ticks.min?this.options.ticks.min:this.min,this.max=void 0!==this.options.ticks.max?this.options.ticks.max:this.max,this.min===this.max&&(0!==this.min&&null!==this.min?(this.min=Math.pow(10,Math.floor(e.log10(this.min))-1),this.max=Math.pow(10,Math.floor(e.log10(this.max))+1)):(this.min=1,this.max=10))},buildTicks:function(){this.ticks=[];for(var t=void 0!==this.options.ticks.min?this.options.ticks.min:Math.pow(10,Math.floor(e.log10(this.min)));tthis.max&&(this.max=i))},this)}},this),this.options.ticks.beginAtZero){var t=e.sign(this.min),i=e.sign(this.max);0>t&&0>i?this.max=0:t>0&&i>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.min--,this.max++)},buildTicks:function(){this.ticks=[];var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),s=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.drawingArea/(1.5*i)));s=Math.max(2,s);var a=e.niceNum(this.max-this.min,!1),o=e.niceNum(a/(s-1),!0),n=Math.floor(this.min/o)*o,r=Math.ceil(this.max/o)*o,h=Math.ceil((r-n)/o);this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:n);for(var l=1;h>l;++l)this.ticks.push(n+l*o);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:r),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),this.zeroLineIndex=this.ticks.indexOf(0)},convertTicksToLabels:function(){t.Scale.prototype.convertTicksToLabels.call(this),this.pointLabels=this.chart.data.labels.map(this.options.pointLabels.callback,this)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var i,s,a,o,n,r,h,l,c,d,u,f,g=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),m=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),p=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),b=e.fontString(g,m,p),v=e.min([this.height/2-g-5,this.width/2]),x=this.width,y=0;for(this.ctx.font=b,s=0;sx&&(x=i.x+o,n=s),i.x-ox&&(x=i.x+a,n=s):s>this.getValueCount()/2&&i.x-a0||this.options.reverse){var o=this.getDistanceFromCenterForValue(this.ticks[a]),n=this.yCenter-o;if(this.options.gridLines.display)if(i.strokeStyle=this.options.gridLines.color,i.lineWidth=this.options.gridLines.lineWidth,this.options.lineArc)i.beginPath(),i.arc(this.xCenter,this.yCenter,o,0,2*Math.PI),i.closePath(),i.stroke();else{i.beginPath();for(var r=0;r=0;s--){if(this.options.angleLines.display){var a=this.getPointPosition(s,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max));i.beginPath(),i.moveTo(this.xCenter,this.yCenter),i.lineTo(a.x,a.y),i.stroke(),i.closePath()}var o=this.getPointPosition(s,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max)+5),n=e.getValueOrDefault(this.options.pointLabels.fontColor,t.defaults.global.defaultFontColor),r=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),h=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),l=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),c=e.fontString(r,h,l);i.font=c,i.fillStyle=n;var d=this.pointLabels.length,u=this.pointLabels.length/2,f=u/2,g=f>s||s>d-f,m=s===f||s===d-f;0===s?i.textAlign="center":s===u?i.textAlign="center":u>s?i.textAlign="left":i.textAlign="right",m?i.textBaseline="middle":g?i.textBaseline="bottom":i.textBaseline="top",i.fillText(this.pointLabels[s]?this.pointLabels[s]:"",o.x,o.y)}}}}});t.scaleService.registerScaleType("radialLinear",s,i)}},{}],42:[function(t,e,i){"use strict";var s=t("moment");s="function"==typeof s?s:window.moment,e.exports=function(t){var e=t.helpers,i={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12]},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},a={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},o=t.Scale.extend({initialize:function(){if(!s)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return this.labelMoments[t][e]},getMomentStartOf:function(t){return"week"===this.options.time.unit&&this.options.time.isoWeekday!==!1?t.clone().startOf("isoWeek").isoWeekday(this.options.time.isoWeekday):t.clone().startOf(this.tickUnit)},determineDataLimits:function(){this.labelMoments=[];var t=[];this.chart.data.labels&&this.chart.data.labels.length>0?(e.each(this.chart.data.labels,function(e,i){var s=this.parseTime(e);s.isValid()&&(this.options.time.round&&s.startOf(this.options.time.round),t.push(s))},this),this.firstTick=s.min.call(this,t),this.lastTick=s.max.call(this,t)):(this.firstTick=null,this.lastTick=null),e.each(this.chart.data.datasets,function(i,a){var o=[],n=this.chart.isDatasetVisible(a);"object"==typeof i.data[0]?e.each(i.data,function(t,e){var i=this.parseTime(this.getRightValue(t));i.isValid()&&(this.options.time.round&&i.startOf(this.options.time.round),o.push(i),n&&(this.firstTick=null!==this.firstTick?s.min(this.firstTick,i):i,this.lastTick=null!==this.lastTick?s.max(this.lastTick,i):i))},this):o=t,this.labelMoments.push(o)},this),this.options.time.min&&(this.firstTick=this.parseTime(this.options.time.min)),this.options.time.max&&(this.lastTick=this.parseTime(this.options.time.max)),this.firstTick=(this.firstTick||s()).clone(),this.lastTick=(this.lastTick||s()).clone()},buildTicks:function(s){this.ctx.save();var a=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),o=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),n=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),r=e.fontString(a,o,n);if(this.ctx.font=r,this.ticks=[],this.unitScale=1,this.scaleSizeInUnits=0,this.options.time.unit)this.tickUnit=this.options.time.unit||"day",this.displayFormat=this.options.time.displayFormats[this.tickUnit],this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,1);else{var h=this.isHorizontal()?this.width-(this.paddingLeft+this.paddingRight):this.height-(this.paddingTop+this.paddingBottom),l=this.tickFormatFunction(this.firstTick,0,[]),c=this.ctx.measureText(l).width,d=Math.cos(e.toRadians(this.options.ticks.maxRotation)),u=Math.sin(e.toRadians(this.options.ticks.maxRotation));c=c*d+a*u;var f=h/c;this.tickUnit="millisecond",this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.displayFormat=this.options.time.displayFormats[this.tickUnit];for(var g=0,m=i.units[g];g=Math.ceil(this.scaleSizeInUnits/f)){this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,m.steps[p]);break}break}if(m.maxStep===!1||Math.ceil(this.scaleSizeInUnits/f)=0)break;k%this.unitScale===0&&this.ticks.push(_)}var S=this.ticks[this.ticks.length-1].diff(this.lastTick,this.tickUnit);(0!==S||0===this.scaleSizeInUnits)&&(this.options.time.max?(this.ticks.push(this.lastTick.clone()),this.scaleSizeInUnits=this.lastTick.diff(this.ticks[0],this.tickUnit,!0)):(this.ticks.push(this.lastTick.clone()),this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0))),this.ctx.restore()},getLabelForIndex:function(t,e){var i=this.chart.data.labels&&tli{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.1');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.1') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.1') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.1') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.1') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.1#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
diff --git a/public/lib/font-awesome/fonts/FontAwesome.otf b/public/lib/font-awesome/fonts/FontAwesome.otf
index 3ed7f8b48a..59853bcda7 100644
Binary files a/public/lib/font-awesome/fonts/FontAwesome.otf and b/public/lib/font-awesome/fonts/FontAwesome.otf differ
diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.eot b/public/lib/font-awesome/fonts/fontawesome-webfont.eot
index 9b6afaedc0..96f92f9b83 100644
Binary files a/public/lib/font-awesome/fonts/fontawesome-webfont.eot and b/public/lib/font-awesome/fonts/fontawesome-webfont.eot differ
diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.svg b/public/lib/font-awesome/fonts/fontawesome-webfont.svg
index d05688e9e2..5a5f0ecd46 100644
--- a/public/lib/font-awesome/fonts/fontawesome-webfont.svg
+++ b/public/lib/font-awesome/fonts/fontawesome-webfont.svg
@@ -169,7 +169,7 @@
-
+
@@ -178,7 +178,7 @@
-
+
@@ -484,7 +484,7 @@
-
+
@@ -641,15 +641,45 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.ttf b/public/lib/font-awesome/fonts/fontawesome-webfont.ttf
index 26dea7951a..86784df961 100644
Binary files a/public/lib/font-awesome/fonts/fontawesome-webfont.ttf and b/public/lib/font-awesome/fonts/fontawesome-webfont.ttf differ
diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.woff b/public/lib/font-awesome/fonts/fontawesome-webfont.woff
index dc35ce3c2c..c7faa19c46 100644
Binary files a/public/lib/font-awesome/fonts/fontawesome-webfont.woff and b/public/lib/font-awesome/fonts/fontawesome-webfont.woff differ
diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.woff2 b/public/lib/font-awesome/fonts/fontawesome-webfont.woff2
index 500e517253..cab8571d58 100644
Binary files a/public/lib/font-awesome/fonts/fontawesome-webfont.woff2 and b/public/lib/font-awesome/fonts/fontawesome-webfont.woff2 differ
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index 133b37a34d..6d3f31f8b0 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -70,6 +70,8 @@ return [
'registered' => 'You have registered successfully!',
'search' => 'Search',
'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.',
+ 'source_accounts' => 'Source account(s)',
+ 'destination_accounts' => 'Destination account(s)',
// repeat frequencies:
'repeat_freq_monthly' => 'monthly',
@@ -257,6 +259,7 @@ return [
'pref_1M' => 'One month',
'pref_3M' => 'Three months (quarter)',
'pref_6M' => 'Six months',
+ 'pref_1Y' => 'One year',
'pref_languages' => 'Languages',
'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?',
'pref_custom_fiscal_year' => 'Fiscal year settings',
@@ -279,6 +282,7 @@ return [
'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions',
'transaction_page_size_label' => 'Page size',
'budget_maximum' => 'Budget maximum',
+ 'between_dates' => '(:start and :end)',
// profile:
'change_your_password' => 'Change your password',
@@ -459,8 +463,11 @@ return [
'store_new_budget' => 'Store new budget',
'stored_new_budget' => 'Stored new budget ":name"',
'availableIn' => 'Available in :date',
+ 'available_between' => 'Available between :start and :end',
'transactionsWithoutBudget' => 'Expenses without budget',
'transactionsWithoutBudgetDate' => 'Expenses without budget in :date',
+ 'transactions_no_budget' => 'Expenses without budget between :start and :end',
+ 'spent_between' => 'Spent between :start and :end',
'createBudget' => 'New budget',
'inactiveBudgets' => 'Inactive budgets',
'without_budget_between' => 'Transactions without a budget between :start and :end',
@@ -471,6 +478,7 @@ return [
'updated_budget' => 'Updated budget ":name"',
'update_amount' => 'Update amount',
'update_budget' => 'Update budget',
+ 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end',
// bills:
'matching_on' => 'Matching on',
@@ -636,18 +644,16 @@ return [
'saveOnAccount' => 'Save on account',
'unknown' => 'Unknown',
'daily' => 'Daily',
- 'weekly' => 'Weekly',
'monthly' => 'Monthly',
- 'quarterly' => 'Quarterly',
- 'half-year' => 'Every six months',
- 'yearly' => 'Yearly',
'profile' => 'Profile',
+ 'errors' => 'Errors',
// reports:
'report_default' => 'Default financial report for :start until :end',
'report_audit' => 'Transaction history overview for :start until :end',
'quick_link_reports' => 'Quick links',
'quick_link_default_report' => 'Default financial report',
+ 'quick_link_audit_report' => 'Transaction history overview',
'report_this_month_quick' => 'Current month, all accounts',
'report_this_year_quick' => 'Current year, all accounts',
'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts',
@@ -794,4 +800,41 @@ return [
'user_administration' => 'User administration',
'list_all_users' => 'All users',
'all_users' => 'All users',
+
+ // split a transaction:
+ 'transaction_meta_data' => 'Transaction meta-data',
+ 'transaction_dates' => 'Transaction dates',
+ 'splits' => 'Splits',
+ 'split_title_withdrawal' => 'Split your new withdrawal',
+ 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
+ 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
+ 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
+ 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_withdrawal' => 'Store splitted withdrawal',
+ 'update_splitted_withdrawal' => 'Update splitted withdrawal',
+
+ 'split_title_deposit' => 'Split your new deposit',
+ 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.',
+ 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.',
+ 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.',
+ 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_deposit' => 'Store splitted deposit',
+
+ 'split_title_transfer' => 'Split your new transfer',
+ 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
+ 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
+ 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
+ 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_transfer' => 'Store splitted transfer',
+
+ 'add_another_split' => 'Add another split',
+ 'split-transactions' => 'Split transactions',
+ 'split-new-transaction' => 'Split a new transaction',
+
+ 'do_split' => 'Do a split',
+ 'split_this_withdrawal' => 'Split this withdrawal',
+ 'split_this_deposit' => 'Split this deposit',
+ 'split_this_transfer' => 'Split this transfer',
+
+
];
diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php
index 4b18bfdec4..b60433f727 100644
--- a/resources/lang/en_US/form.php
+++ b/resources/lang/en_US/form.php
@@ -10,119 +10,131 @@
return [
// new user:
- 'bank_name' => 'Bank name',
- 'bank_balance' => 'Balance',
- 'savings_balance' => 'Savings balance',
- 'credit_card_limit' => 'Credit card limit',
- 'automatch' => 'Match automatically',
- 'skip' => 'Skip',
- 'name' => 'Name',
- 'active' => 'Active',
- 'amount_min' => 'Minimum amount',
- 'amount_max' => 'Maximum amount',
- 'match' => 'Matches on',
- 'repeat_freq' => 'Repeats',
- 'account_from_id' => 'From account',
- 'account_to_id' => 'To account',
- 'account_id' => 'Asset account',
- 'budget_id' => 'Budget',
- 'openingBalance' => 'Opening balance',
- 'tagMode' => 'Tag mode',
- 'tagPosition' => 'Tag location',
- 'virtualBalance' => 'Virtual balance',
- 'longitude_latitude' => 'Location',
- 'targetamount' => 'Target amount',
- 'accountRole' => 'Account role',
- 'openingBalanceDate' => 'Opening balance date',
- 'ccType' => 'Credit card payment plan',
- 'ccMonthlyPaymentDate' => 'Credit card monthly payment date',
- 'piggy_bank_id' => 'Piggy bank',
- 'returnHere' => 'Return here',
- 'returnHereExplanation' => 'After storing, return here to create another one.',
- 'returnHereUpdateExplanation' => 'After updating, return here.',
- 'description' => 'Description',
- 'expense_account' => 'Expense account',
- 'revenue_account' => 'Revenue account',
- 'amount' => 'Amount',
- 'date' => 'Date',
- 'interest_date' => 'Interest date',
- 'book_date' => 'Book date',
- 'process_date' => 'Processing date',
- 'category' => 'Category',
- 'tags' => 'Tags',
- 'deletePermanently' => 'Delete permanently',
- 'cancel' => 'Cancel',
- 'targetdate' => 'Target date',
- 'tag' => 'Tag',
- 'under' => 'Under',
- 'symbol' => 'Symbol',
- 'code' => 'Code',
- 'iban' => 'IBAN',
- 'accountNumber' => 'Account number',
- 'csv' => 'CSV file',
- 'has_headers' => 'Headers',
- 'date_format' => 'Date format',
- 'csv_config' => 'CSV import configuration',
- 'specifix' => 'Bank- or file specific fixes',
- 'csv_import_account' => 'Default import account',
- 'csv_delimiter' => 'CSV field delimiter',
- 'attachments[]' => 'Attachments',
- 'store_new_withdrawal' => 'Store new withdrawal',
- 'store_new_deposit' => 'Store new deposit',
- 'store_new_transfer' => 'Store new transfer',
- 'add_new_withdrawal' => 'Add a new withdrawal',
- 'add_new_deposit' => 'Add a new deposit',
- 'add_new_transfer' => 'Add a new transfer',
- 'noPiggybank' => '(no piggy bank)',
- 'title' => 'Title',
- 'notes' => 'Notes',
- 'filename' => 'File name',
- 'mime' => 'Mime type',
- 'size' => 'Size',
- 'trigger' => 'Trigger',
- 'stop_processing' => 'Stop processing',
- 'start_date' => 'Start of range',
- 'end_date' => 'End of range',
- 'export_start_range' => 'Start of export range',
- 'export_end_range' => 'End of export range',
- 'export_format' => 'File format',
- 'include_attachments' => 'Include uploaded attachments',
- 'include_config' => 'Include configuration file',
- 'include_old_uploads' => 'Include imported data',
- 'accounts' => 'Export transactions from these accounts',
- 'csv_comma' => 'A comma (,)',
- 'csv_semicolon' => 'A semicolon (;)',
- 'csv_tab' => 'A tab (invisible)',
- 'delete_account' => 'Delete account ":name"',
- 'delete_bill' => 'Delete bill ":name"',
- 'delete_budget' => 'Delete budget ":name"',
- 'delete_category' => 'Delete category ":name"',
- 'delete_currency' => 'Delete currency ":name"',
- 'delete_journal' => 'Delete transaction with description ":description"',
- 'delete_attachment' => 'Delete attachment ":name"',
- 'delete_rule' => 'Delete rule ":title"',
- 'delete_rule_group' => 'Delete rule group ":title"',
- 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?',
- 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?',
- 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?',
- 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?',
- 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?',
- 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?',
- 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?',
- 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?',
- 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?',
- 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?',
- 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?',
- 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?',
- 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.',
- 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.',
- 'delete_all_permanently' => 'Delete selected permanently',
- 'update_all_journals' => 'Update these transactions',
- 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
- 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
- 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
- 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
- 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
- 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
- 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+ 'bank_name' => 'Bank name',
+ 'bank_balance' => 'Balance',
+ 'savings_balance' => 'Savings balance',
+ 'credit_card_limit' => 'Credit card limit',
+ 'automatch' => 'Match automatically',
+ 'skip' => 'Skip',
+ 'name' => 'Name',
+ 'active' => 'Active',
+ 'amount_min' => 'Minimum amount',
+ 'amount_max' => 'Maximum amount',
+ 'match' => 'Matches on',
+ 'repeat_freq' => 'Repeats',
+ 'journal_currency_id' => 'Currency',
+ 'journal_amount' => 'Amount',
+ 'journal_asset_source_account' => 'Asset account (source)',
+ 'journal_source_account_name' => 'Revenue account (source)',
+ 'journal_source_account_id' => 'Asset account (source)',
+ 'account_from_id' => 'From account',
+ 'account_to_id' => 'To account',
+ 'journal_destination_account_id' => 'Asset account (destination)',
+ 'asset_destination_account' => 'Asset account (destination)',
+ 'asset_source_account' => 'Asset account (source)',
+ 'journal_description' => 'Description',
+ 'split_journal' => 'Split this transaction',
+ 'split_journal_explanation' => 'Split this transaction in multiple parts',
+ 'currency' => 'Currency',
+ 'account_id' => 'Asset account',
+ 'budget_id' => 'Budget',
+ 'openingBalance' => 'Opening balance',
+ 'tagMode' => 'Tag mode',
+ 'tagPosition' => 'Tag location',
+ 'virtualBalance' => 'Virtual balance',
+ 'longitude_latitude' => 'Location',
+ 'targetamount' => 'Target amount',
+ 'accountRole' => 'Account role',
+ 'openingBalanceDate' => 'Opening balance date',
+ 'ccType' => 'Credit card payment plan',
+ 'ccMonthlyPaymentDate' => 'Credit card monthly payment date',
+ 'piggy_bank_id' => 'Piggy bank',
+ 'returnHere' => 'Return here',
+ 'returnHereExplanation' => 'After storing, return here to create another one.',
+ 'returnHereUpdateExplanation' => 'After updating, return here.',
+ 'description' => 'Description',
+ 'expense_account' => 'Expense account',
+ 'revenue_account' => 'Revenue account',
+ 'amount' => 'Amount',
+ 'date' => 'Date',
+ 'interest_date' => 'Interest date',
+ 'book_date' => 'Book date',
+ 'process_date' => 'Processing date',
+ 'category' => 'Category',
+ 'tags' => 'Tags',
+ 'deletePermanently' => 'Delete permanently',
+ 'cancel' => 'Cancel',
+ 'targetdate' => 'Target date',
+ 'tag' => 'Tag',
+ 'under' => 'Under',
+ 'symbol' => 'Symbol',
+ 'code' => 'Code',
+ 'iban' => 'IBAN',
+ 'accountNumber' => 'Account number',
+ 'csv' => 'CSV file',
+ 'has_headers' => 'Headers',
+ 'date_format' => 'Date format',
+ 'csv_config' => 'CSV import configuration',
+ 'specifix' => 'Bank- or file specific fixes',
+ 'csv_import_account' => 'Default import account',
+ 'csv_delimiter' => 'CSV field delimiter',
+ 'attachments[]' => 'Attachments',
+ 'store_new_withdrawal' => 'Store new withdrawal',
+ 'store_new_deposit' => 'Store new deposit',
+ 'store_new_transfer' => 'Store new transfer',
+ 'add_new_withdrawal' => 'Add a new withdrawal',
+ 'add_new_deposit' => 'Add a new deposit',
+ 'add_new_transfer' => 'Add a new transfer',
+ 'noPiggybank' => '(no piggy bank)',
+ 'title' => 'Title',
+ 'notes' => 'Notes',
+ 'filename' => 'File name',
+ 'mime' => 'Mime type',
+ 'size' => 'Size',
+ 'trigger' => 'Trigger',
+ 'stop_processing' => 'Stop processing',
+ 'start_date' => 'Start of range',
+ 'end_date' => 'End of range',
+ 'export_start_range' => 'Start of export range',
+ 'export_end_range' => 'End of export range',
+ 'export_format' => 'File format',
+ 'include_attachments' => 'Include uploaded attachments',
+ 'include_config' => 'Include configuration file',
+ 'include_old_uploads' => 'Include imported data',
+ 'accounts' => 'Export transactions from these accounts',
+ 'csv_comma' => 'A comma (,)',
+ 'csv_semicolon' => 'A semicolon (;)',
+ 'csv_tab' => 'A tab (invisible)',
+ 'delete_account' => 'Delete account ":name"',
+ 'delete_bill' => 'Delete bill ":name"',
+ 'delete_budget' => 'Delete budget ":name"',
+ 'delete_category' => 'Delete category ":name"',
+ 'delete_currency' => 'Delete currency ":name"',
+ 'delete_journal' => 'Delete transaction with description ":description"',
+ 'delete_attachment' => 'Delete attachment ":name"',
+ 'delete_rule' => 'Delete rule ":title"',
+ 'delete_rule_group' => 'Delete rule group ":title"',
+ 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?',
+ 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?',
+ 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?',
+ 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?',
+ 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?',
+ 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?',
+ 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?',
+ 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?',
+ 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?',
+ 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?',
+ 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?',
+ 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?',
+ 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.',
+ 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.',
+ 'delete_all_permanently' => 'Delete selected permanently',
+ 'update_all_journals' => 'Update these transactions',
+ 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
+ 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
+ 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
+ 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
+ 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
+ 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
+ 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
];
diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php
index ca4ab77a2d..8a69fced9e 100644
--- a/resources/lang/en_US/list.php
+++ b/resources/lang/en_US/list.php
@@ -22,8 +22,13 @@ return [
'balanceDiff' => 'Balance difference between :start and :end',
'matchedOn' => 'Matched on',
'matchesOn' => 'Matched on',
+ 'account_type' => 'Account type',
+ 'new_balance' => 'New balance',
+ 'account' => 'Account',
'matchingAmount' => 'Amount',
'lastMatch' => 'Last match',
+ 'split_number' => 'Split #',
+ 'destination' => 'Destination',
'expectedMatch' => 'Expected match',
'automatch' => 'Auto match?',
'repeat_freq' => 'Repeats',
@@ -34,6 +39,7 @@ return [
'book_date' => 'Book date',
'process_date' => 'Processing date',
'from' => 'From',
+ 'piggy_bank' => 'Piggy bank',
'to' => 'To',
'budget' => 'Budget',
'category' => 'Category',
diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php
index 2638f9e75f..fa086c9fbb 100644
--- a/resources/lang/en_US/validation.php
+++ b/resources/lang/en_US/validation.php
@@ -17,6 +17,7 @@ return [
'file_attached' => 'Succesfully uploaded file ":name".',
'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.',
'file_too_large' => 'File ":name" is too large.',
+ 'belongs_to_user' => 'The value of :attribute is unknown',
'accepted' => 'The :attribute must be accepted.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',
diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php
index c14f07ff81..3a24a0601a 100644
--- a/resources/lang/fr_FR/firefly.php
+++ b/resources/lang/fr_FR/firefly.php
@@ -70,6 +70,8 @@ return [
'registered' => 'You have registered successfully!',
'search' => 'Search',
'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.',
+ 'source_accounts' => 'Source account(s)',
+ 'destination_accounts' => 'Destination account(s)',
// repeat frequencies:
'repeat_freq_monthly' => 'monthly',
@@ -257,6 +259,7 @@ return [
'pref_1M' => 'One month',
'pref_3M' => 'Three months (quarter)',
'pref_6M' => 'Six months',
+ 'pref_1Y' => 'One year',
'pref_languages' => 'Languages',
'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?',
'pref_custom_fiscal_year' => 'Fiscal year settings',
@@ -279,6 +282,7 @@ return [
'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions',
'transaction_page_size_label' => 'Page size',
'budget_maximum' => 'Budget maximum',
+ 'between_dates' => '(:start and :end)',
// profile:
'change_your_password' => 'Change your password',
@@ -459,8 +463,11 @@ return [
'store_new_budget' => 'Store new budget',
'stored_new_budget' => 'Stored new budget ":name"',
'availableIn' => 'Available in :date',
+ 'available_between' => 'Available between :start and :end',
'transactionsWithoutBudget' => 'Expenses without budget',
'transactionsWithoutBudgetDate' => 'Expenses without budget in :date',
+ 'transactions_no_budget' => 'Expenses without budget between :start and :end',
+ 'spent_between' => 'Spent between :start and :end',
'createBudget' => 'New budget',
'inactiveBudgets' => 'Inactive budgets',
'without_budget_between' => 'Transactions without a budget between :start and :end',
@@ -471,6 +478,7 @@ return [
'updated_budget' => 'Updated budget ":name"',
'update_amount' => 'Update amount',
'update_budget' => 'Update budget',
+ 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end',
// bills:
'matching_on' => 'Matching on',
@@ -648,6 +656,7 @@ return [
'report_audit' => 'Transaction history overview for :start until :end',
'quick_link_reports' => 'Quick links',
'quick_link_default_report' => 'Default financial report',
+ 'quick_link_audit_report' => 'Transaction history overview',
'report_this_month_quick' => 'Current month, all accounts',
'report_this_year_quick' => 'Current year, all accounts',
'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts',
@@ -794,4 +803,36 @@ return [
'user_administration' => 'User administration',
'list_all_users' => 'All users',
'all_users' => 'All users',
+
+ // split a transaction:
+ 'transaction_meta_data' => 'Transaction meta-data',
+ 'transaction_dates' => 'Transaction dates',
+ 'splits' => 'Splits',
+ 'split_title_withdrawal' => 'Split your new withdrawal',
+ 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
+ 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
+ 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
+ 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_withdrawal' => 'Store splitted withdrawal',
+ 'update_splitted_withdrawal' => 'Update splitted withdrawal',
+
+ 'split_title_deposit' => 'Split your new deposit',
+ 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.',
+ 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.',
+ 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.',
+ 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_deposit' => 'Store splitted deposit',
+
+ 'split_title_transfer' => 'Split your new transfer',
+ 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
+ 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
+ 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
+ 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_transfer' => 'Store splitted transfer',
+
+ 'add_another_split' => 'Add another split',
+ 'split-transactions' => 'Split transactions',
+ 'split-new-transaction' => 'Split a new transaction',
+
+
];
diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php
index eb5f3511c9..6babd72883 100644
--- a/resources/lang/fr_FR/form.php
+++ b/resources/lang/fr_FR/form.php
@@ -10,119 +10,131 @@
return [
// new user:
- 'bank_name' => 'Nom de la banque',
- 'bank_balance' => 'Solde',
- 'savings_balance' => 'Solde de l\'épargne',
- 'credit_card_limit' => 'Limite de carte de crédit',
- 'automatch' => 'Match automatically',
- 'skip' => 'Skip',
- 'name' => 'Nom',
- 'active' => 'Actif',
- 'amount_min' => 'Montant minimum',
- 'amount_max' => 'Montant maximum',
- 'match' => 'Matches on',
- 'repeat_freq' => 'Repeats',
- 'account_from_id' => 'Compte d\'origine',
- 'account_to_id' => 'Compte de destination',
- 'account_id' => 'Asset account',
- 'budget_id' => 'Budget',
- 'openingBalance' => 'Solde initial',
- 'tagMode' => 'Tag mode',
- 'tagPosition' => 'Tag location',
- 'virtualBalance' => 'Solde virtuel',
- 'longitude_latitude' => 'Location',
- 'targetamount' => 'Target amount',
- 'accountRole' => 'Account role',
- 'openingBalanceDate' => 'Opening balance date',
- 'ccType' => 'Credit card payment plan',
- 'ccMonthlyPaymentDate' => 'Credit card monthly payment date',
- 'piggy_bank_id' => 'Tirelire',
- 'returnHere' => 'Return here',
- 'returnHereExplanation' => 'After storing, return here to create another one.',
- 'returnHereUpdateExplanation' => 'After updating, return here.',
- 'description' => 'Description',
- 'expense_account' => 'Expense account',
- 'revenue_account' => 'Revenue account',
- 'amount' => 'Amount',
- 'date' => 'Date',
- 'interest_date' => 'Interest date',
- 'book_date' => 'Book date',
- 'process_date' => 'Processing date',
- 'category' => 'Category',
- 'tags' => 'Tags',
- 'deletePermanently' => 'Delete permanently',
- 'cancel' => 'Cancel',
- 'targetdate' => 'Target date',
- 'tag' => 'Tag',
- 'under' => 'Under',
- 'symbol' => 'Symbol',
- 'code' => 'Code',
- 'iban' => 'IBAN',
- 'accountNumber' => 'Account number',
- 'csv' => 'CSV file',
- 'has_headers' => 'Headers',
- 'date_format' => 'Date format',
- 'csv_config' => 'CSV import configuration',
- 'specifix' => 'Bank- or file specific fixes',
- 'csv_import_account' => 'Default import account',
- 'csv_delimiter' => 'CSV field delimiter',
- 'attachments[]' => 'Attachments',
- 'store_new_withdrawal' => 'Store new withdrawal',
- 'store_new_deposit' => 'Store new deposit',
- 'store_new_transfer' => 'Store new transfer',
- 'add_new_withdrawal' => 'Add a new withdrawal',
- 'add_new_deposit' => 'Add a new deposit',
- 'add_new_transfer' => 'Add a new transfer',
- 'noPiggybank' => '(no piggy bank)',
- 'title' => 'Title',
- 'notes' => 'Notes',
- 'filename' => 'File name',
- 'mime' => 'Mime type',
- 'size' => 'Size',
- 'trigger' => 'Trigger',
- 'stop_processing' => 'Stop processing',
- 'start_date' => 'Start of range',
- 'end_date' => 'End of range',
- 'export_start_range' => 'Start of export range',
- 'export_end_range' => 'End of export range',
- 'export_format' => 'File format',
- 'include_attachments' => 'Include uploaded attachments',
- 'include_config' => 'Include configuration file',
- 'include_old_uploads' => 'Include imported data',
- 'accounts' => 'Export transactions from these accounts',
- 'csv_comma' => 'A comma (,)',
- 'csv_semicolon' => 'A semicolon (;)',
- 'csv_tab' => 'A tab (invisible)',
- 'delete_account' => 'Delete account ":name"',
- 'delete_bill' => 'Supprimer la facture ":name"',
- 'delete_budget' => 'Delete budget ":name"',
- 'delete_category' => 'Delete category ":name"',
- 'delete_currency' => 'Delete currency ":name"',
- 'delete_journal' => 'Delete transaction with description ":description"',
- 'delete_attachment' => 'Delete attachment ":name"',
- 'delete_rule' => 'Delete rule ":title"',
- 'delete_rule_group' => 'Delete rule group ":title"',
- 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?',
- 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?',
- 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?',
- 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?',
- 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?',
- 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?',
- 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?',
- 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?',
- 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?',
- 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?',
- 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?',
- 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?',
- 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.',
- 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.',
- 'delete_all_permanently' => 'Delete selected permanently',
- 'update_all_journals' => 'Update these transactions',
- 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
- 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
- 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
- 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
- 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
- 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
- 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+ 'bank_name' => 'Nom de la banque',
+ 'bank_balance' => 'Solde',
+ 'savings_balance' => 'Solde de l\'épargne',
+ 'credit_card_limit' => 'Limite de carte de crédit',
+ 'automatch' => 'Match automatically',
+ 'skip' => 'Skip',
+ 'name' => 'Nom',
+ 'active' => 'Actif',
+ 'amount_min' => 'Montant minimum',
+ 'amount_max' => 'Montant maximum',
+ 'match' => 'Matches on',
+ 'repeat_freq' => 'Repeats',
+ 'journal_currency_id' => 'Currency',
+ 'journal_amount' => 'Amount',
+ 'journal_asset_source_account' => 'Asset account (source)',
+ 'journal_source_account_name' => 'Revenue account (source)',
+ 'journal_source_account_id' => 'Asset account (source)',
+ 'account_from_id' => 'Compte d\'origine',
+ 'account_to_id' => 'Compte de destination',
+ 'journal_destination_account_id' => 'Asset account (destination)',
+ 'asset_destination_account' => 'Asset account (destination)',
+ 'asset_source_account' => 'Asset account (source)',
+ 'journal_description' => 'Description',
+ 'split_journal' => 'Split this transaction',
+ 'split_journal_explanation' => 'Split this transaction in multiple parts',
+ 'currency' => 'Currency',
+ 'account_id' => 'Asset account',
+ 'budget_id' => 'Budget',
+ 'openingBalance' => 'Solde initial',
+ 'tagMode' => 'Tag mode',
+ 'tagPosition' => 'Tag location',
+ 'virtualBalance' => 'Solde virtuel',
+ 'longitude_latitude' => 'Location',
+ 'targetamount' => 'Target amount',
+ 'accountRole' => 'Account role',
+ 'openingBalanceDate' => 'Opening balance date',
+ 'ccType' => 'Credit card payment plan',
+ 'ccMonthlyPaymentDate' => 'Credit card monthly payment date',
+ 'piggy_bank_id' => 'Tirelire',
+ 'returnHere' => 'Return here',
+ 'returnHereExplanation' => 'After storing, return here to create another one.',
+ 'returnHereUpdateExplanation' => 'After updating, return here.',
+ 'description' => 'Description',
+ 'expense_account' => 'Expense account',
+ 'revenue_account' => 'Revenue account',
+ 'amount' => 'Amount',
+ 'date' => 'Date',
+ 'interest_date' => 'Interest date',
+ 'book_date' => 'Book date',
+ 'process_date' => 'Processing date',
+ 'category' => 'Category',
+ 'tags' => 'Tags',
+ 'deletePermanently' => 'Delete permanently',
+ 'cancel' => 'Cancel',
+ 'targetdate' => 'Target date',
+ 'tag' => 'Tag',
+ 'under' => 'Under',
+ 'symbol' => 'Symbol',
+ 'code' => 'Code',
+ 'iban' => 'IBAN',
+ 'accountNumber' => 'Account number',
+ 'csv' => 'CSV file',
+ 'has_headers' => 'Headers',
+ 'date_format' => 'Date format',
+ 'csv_config' => 'CSV import configuration',
+ 'specifix' => 'Bank- or file specific fixes',
+ 'csv_import_account' => 'Default import account',
+ 'csv_delimiter' => 'CSV field delimiter',
+ 'attachments[]' => 'Attachments',
+ 'store_new_withdrawal' => 'Store new withdrawal',
+ 'store_new_deposit' => 'Store new deposit',
+ 'store_new_transfer' => 'Store new transfer',
+ 'add_new_withdrawal' => 'Add a new withdrawal',
+ 'add_new_deposit' => 'Add a new deposit',
+ 'add_new_transfer' => 'Add a new transfer',
+ 'noPiggybank' => '(no piggy bank)',
+ 'title' => 'Title',
+ 'notes' => 'Notes',
+ 'filename' => 'File name',
+ 'mime' => 'Mime type',
+ 'size' => 'Size',
+ 'trigger' => 'Trigger',
+ 'stop_processing' => 'Stop processing',
+ 'start_date' => 'Start of range',
+ 'end_date' => 'End of range',
+ 'export_start_range' => 'Start of export range',
+ 'export_end_range' => 'End of export range',
+ 'export_format' => 'File format',
+ 'include_attachments' => 'Include uploaded attachments',
+ 'include_config' => 'Include configuration file',
+ 'include_old_uploads' => 'Include imported data',
+ 'accounts' => 'Export transactions from these accounts',
+ 'csv_comma' => 'A comma (,)',
+ 'csv_semicolon' => 'A semicolon (;)',
+ 'csv_tab' => 'A tab (invisible)',
+ 'delete_account' => 'Delete account ":name"',
+ 'delete_bill' => 'Supprimer la facture ":name"',
+ 'delete_budget' => 'Delete budget ":name"',
+ 'delete_category' => 'Delete category ":name"',
+ 'delete_currency' => 'Delete currency ":name"',
+ 'delete_journal' => 'Delete transaction with description ":description"',
+ 'delete_attachment' => 'Delete attachment ":name"',
+ 'delete_rule' => 'Delete rule ":title"',
+ 'delete_rule_group' => 'Delete rule group ":title"',
+ 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?',
+ 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?',
+ 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?',
+ 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?',
+ 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?',
+ 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?',
+ 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?',
+ 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?',
+ 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?',
+ 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?',
+ 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?',
+ 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?',
+ 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.',
+ 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.',
+ 'delete_all_permanently' => 'Delete selected permanently',
+ 'update_all_journals' => 'Update these transactions',
+ 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
+ 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
+ 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
+ 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
+ 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
+ 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
+ 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
];
diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php
index 4929e32564..97815e936d 100644
--- a/resources/lang/fr_FR/list.php
+++ b/resources/lang/fr_FR/list.php
@@ -22,8 +22,13 @@ return [
'balanceDiff' => 'Difference solde entre :start et :end',
'matchedOn' => 'Matched on',
'matchesOn' => 'Matched on',
+ 'account_type' => 'Account type',
+ 'new_balance' => 'New balance',
+ 'account' => 'Account',
'matchingAmount' => 'Montant',
'lastMatch' => 'Last match',
+ 'split_number' => 'Split #',
+ 'destination' => 'Destination',
'expectedMatch' => 'Expected match',
'automatch' => 'Auto match?',
'repeat_freq' => 'Repeats',
@@ -34,6 +39,7 @@ return [
'book_date' => 'Book date',
'process_date' => 'Processing date',
'from' => 'From',
+ 'piggy_bank' => 'Piggy bank',
'to' => 'To',
'budget' => 'Budget',
'category' => 'Category',
diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php
index c6bcbb04c7..49fc9c9e96 100644
--- a/resources/lang/fr_FR/validation.php
+++ b/resources/lang/fr_FR/validation.php
@@ -17,6 +17,7 @@ return [
'file_attached' => 'Succesfully uploaded file ":name".',
'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.',
'file_too_large' => 'File ":name" is too large.',
+ 'belongs_to_user' => 'The value of :attribute is unknown',
'accepted' => 'Le champ :attribute doit être accepté.',
'active_url' => 'Le champ :attribute n\'est pas une URL valide.',
'after' => 'Le champ :attribute doit être une date postérieure au :date.',
diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php
index 332aec8d35..159eedee49 100644
--- a/resources/lang/nl_NL/firefly.php
+++ b/resources/lang/nl_NL/firefly.php
@@ -70,6 +70,8 @@ return [
'registered' => 'Je bent geregistreerd!',
'search' => 'Zoeken',
'no_budget_pointer' => 'Je hebt nog geen budgetten. Maak er een aantal op de budgetten-pagina. Met budgetten kan je je uitgaven beter bijhouden.',
+ 'source_accounts' => 'Bronrekening(en)',
+ 'destination_accounts' => 'Doelrekening(en)',
// repeat frequencies:
'repeat_freq_monthly' => 'maandelijks',
@@ -257,6 +259,7 @@ return [
'pref_1M' => 'Eén maand',
'pref_3M' => 'Drie maanden (kwartaal)',
'pref_6M' => 'Zes maanden',
+ 'pref_1Y' => 'Eén jaar',
'pref_languages' => 'Talen',
'pref_languages_help' => 'Firefly III ondersteunt meerdere talen. Welke heeft jouw voorkeur?',
'pref_custom_fiscal_year' => 'Instellingen voor boekjaar',
@@ -279,6 +282,7 @@ return [
'transaction_page_size_help' => 'Elke lijst met transacties er op is zo lang',
'transaction_page_size_label' => 'Paginalengte',
'budget_maximum' => 'Maximale budgetgrootte',
+ 'between_dates' => '(:start en :end)',
// profile:
'change_your_password' => 'Verander je wachtwoord',
@@ -459,8 +463,11 @@ return [
'store_new_budget' => 'Sla nieuw budget op',
'stored_new_budget' => 'Nieuw budget ":name" opgeslagen',
'availableIn' => 'Beschikbaar in :date',
+ 'available_between' => 'Beschikbaar tussen :start en :end',
'transactionsWithoutBudget' => 'Uitgaven zonder budget',
'transactionsWithoutBudgetDate' => 'Uitgaven zonder budget in :date',
+ 'transactions_no_budget' => 'Uitgaven zonder budget tussen :start en :end',
+ 'spent_between' => 'Uitgegeven tussen :start en :end',
'createBudget' => 'Maak nieuw budget',
'inactiveBudgets' => 'Inactieve budgetten',
'without_budget_between' => 'Transacties zonder budget tussen :start en :end',
@@ -471,6 +478,7 @@ return [
'updated_budget' => 'Budget ":name" geüpdatet',
'update_amount' => 'Bedrag bijwerken',
'update_budget' => 'Budget bijwerken',
+ 'update_budget_amount_range' => 'Update het verwacht beschikbare bedrag tussen :start en :end',
// bills:
'matching_on' => 'Wordt herkend',
@@ -648,6 +656,7 @@ return [
'report_audit' => 'Transactiehistorie-overzicht van :start tot :end',
'quick_link_reports' => 'Snelle links',
'quick_link_default_report' => 'Standaard financieel rapport',
+ 'quick_link_audit_report' => 'Transactiehistorie-overzicht',
'report_this_month_quick' => 'Deze maand, alle rekeningen',
'report_this_year_quick' => 'Dit jaar, alle rekeningen',
'report_this_fiscal_year_quick' => 'Huidig boekjaar, alle rekeningen',
@@ -794,4 +803,36 @@ return [
'user_administration' => 'Gebruikersadministratie',
'list_all_users' => 'Alle gebruikers',
'all_users' => 'Alle gebruikers',
+
+ // split a transaction:
+ 'transaction_meta_data' => 'Transactie meta-data',
+ 'transaction_dates' => 'Transactie data',
+ 'splits' => 'Splitten',
+ 'split_title_withdrawal' => 'Splits je nieuwe uitgave',
+ 'split_intro_one_withdrawal' => 'Firefly kan een uitgave "splitsen".',
+ 'split_intro_two_withdrawal' => 'Dat betekent dat de uitgave die je maakt wordt verdeeld over verschillende doelrekeningen, budgetten of categorieën.',
+ 'split_intro_three_withdrawal' => 'Je kan bijvoorbeeld je boodschappen van :total verdelen zodat :split_one uit je boodschappenbudget komt, en :split_two uit je sigarettenbudget.',
+ 'split_table_intro_withdrawal' => 'Split je uitgave in zoveel stukken als je maar wilt. Standaard is je uitgave niet gesplitst; er is maar één "split". Voeg hieronder zoveel splits toe als je wilt. Denk er aan dat je niet afwijkt van het totaalbedrag. Als je dat wel doet zal Firefly je waarschuwen maar niet corrigeren.',
+ 'store_splitted_withdrawal' => 'Sla gesplitste uitgave op',
+ 'update_splitted_withdrawal' => 'Gesplitste uitgave updaten',
+
+ 'split_title_deposit' => 'Splits je nieuwe inkomsten',
+ 'split_intro_one_deposit' => 'Firefly kan inkomsten "splitsen".',
+ 'split_intro_two_deposit' => 'Dat betekent dat de inkomsten die je krijgt wordt verdeeld over verschillende doelrekeningen of categorieën.',
+ 'split_intro_three_deposit' => 'Je kan bijvoorbeeld je salaris van :total verdelen zodat :split_one wordt opgeslagen als je basissalaris, en :split_two als declaratieteruggave.',
+ 'split_table_intro_deposit' => 'Split je inkomsten in zoveel stukken als je maar wilt. Standaard zijn je inkomsten niet gesplitst; er is maar één "split". Voeg hieronder zoveel splits toe als je wilt. Denk er aan dat je niet afwijkt van het totaalbedrag. Als je dat wel doet zal Firefly je waarschuwen maar niet corrigeren.',
+ 'store_splitted_deposit' => 'Sla gesplitse inkomsten op',
+
+ 'split_title_transfer' => 'Splits je nieuwe overschrijving',
+ 'split_intro_one_transfer' => 'Firefly kan overschrijvingen "splitsen".',
+ 'split_intro_two_transfer' => 'Dat betekent dat de uitgave die je maakt wordt verdeeld over verschillende categorieën of spaarpotjes.',
+ 'split_intro_three_transfer' => 'Je kan bijvoorbeeld je overschrijving van :total verdelen zodat :split_one in het ene spaarpotje terecht komt, en :split_two in het andere spaarpotje.',
+ 'split_table_intro_transfer' => 'Split je overschrijving in zoveel stukken als je maar wilt. Standaard is je overschrijving niet gesplitst; er is maar één "split". Voeg hieronder zoveel splits toe als je wilt. Denk er aan dat je niet afwijkt van het totaalbedrag. Als je dat wel doet zal Firefly je waarschuwen maar niet corrigeren.',
+ 'store_splitted_transfer' => 'Sla gesplitste overschrijving op',
+
+ 'add_another_split' => 'Voeg een split toe',
+ 'split-transactions' => 'Split transacties',
+ 'split-new-transaction' => 'Split een nieuwe transactie',
+
+
];
diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php
index 4441007fc7..be8e10d516 100644
--- a/resources/lang/nl_NL/form.php
+++ b/resources/lang/nl_NL/form.php
@@ -10,119 +10,131 @@
return [
// new user:
- 'bank_name' => 'Banknaam',
- 'bank_balance' => 'Saldo',
- 'savings_balance' => 'Saldo van spaarrekening',
- 'credit_card_limit' => 'Credit card limiet',
- 'automatch' => 'Automatisch herkennen',
- 'skip' => 'Overslaan',
- 'name' => 'Naam',
- 'active' => 'Actief',
- 'amount_min' => 'Minimumbedrag',
- 'amount_max' => 'Maximumbedrag',
- 'match' => 'Reageert op',
- 'repeat_freq' => 'Herhaling',
- 'account_from_id' => 'Van account',
- 'account_to_id' => 'Naar account',
- 'account_id' => 'Betaalrekening',
- 'budget_id' => 'Budget',
- 'openingBalance' => 'Startsaldo',
- 'tagMode' => 'Tag modus',
- 'tagPosition' => 'Tag locatie',
- 'virtualBalance' => 'Virtuele saldo',
- 'longitude_latitude' => 'Locatie',
- 'targetamount' => 'Doelbedrag',
- 'accountRole' => 'Rol van rekening',
- 'openingBalanceDate' => 'Startsaldodatum',
- 'ccType' => 'Betaalplan',
- 'ccMonthlyPaymentDate' => 'Betaaldatum',
- 'piggy_bank_id' => 'Spaarpotje',
- 'returnHere' => 'Keer terug',
- 'returnHereExplanation' => 'Terug naar deze pagina na het opslaan.',
- 'returnHereUpdateExplanation' => 'Terug naar deze pagina na het wijzigen.',
- 'description' => 'Omschrijving',
- 'expense_account' => 'Crediteur',
- 'revenue_account' => 'Debiteur',
- 'amount' => 'Bedrag',
- 'date' => 'Datum',
- 'interest_date' => 'Rentedatum',
- 'book_date' => 'Boekdatum',
- 'process_date' => 'Verwerkingsdatum',
- 'category' => 'Categorie',
- 'tags' => 'Tags',
- 'deletePermanently' => 'Verwijderen',
- 'cancel' => 'Annuleren',
- 'targetdate' => 'Doeldatum',
- 'tag' => 'Tag',
- 'under' => 'Onder',
- 'symbol' => 'Symbool',
- 'code' => 'Code',
- 'iban' => 'IBAN',
- 'accountNumber' => 'Rekeningnummer',
- 'csv' => 'CSV-bestand',
- 'has_headers' => 'Kolomnamen op de eerste rij?',
- 'date_format' => 'Datumformaat',
- 'csv_config' => 'Configuratiebestand',
- 'specifix' => 'Bank- or of bestandsspecifieke opties',
- 'csv_import_account' => 'Standaard rekening voor importeren',
- 'csv_delimiter' => 'CSV scheidingsteken',
- 'attachments[]' => 'Bijlagen',
- 'store_new_withdrawal' => 'Nieuwe uitgave opslaan',
- 'store_new_deposit' => 'Nieuwe inkomsten opslaan',
- 'store_new_transfer' => 'Nieuwe overschrijving opslaan',
- 'add_new_withdrawal' => 'Maak nieuwe uitgave',
- 'add_new_deposit' => 'Maak nieuwe inkomsten',
- 'add_new_transfer' => 'Maak nieuwe overschrijving',
- 'noPiggybank' => '(geen spaarpotje)',
- 'title' => 'Titel',
- 'notes' => 'Notities',
- 'filename' => 'Bestandsnaam',
- 'mime' => 'Bestandstype',
- 'size' => 'Grootte',
- 'trigger' => 'Trigger',
- 'stop_processing' => 'Stop met verwerken',
- 'start_date' => 'Start van bereik',
- 'end_date' => 'Einde van bereik',
- 'export_start_range' => 'Start van exportbereik',
- 'export_end_range' => 'Einde van exportbereik',
- 'export_format' => 'Bestandsformaat',
- 'include_attachments' => 'Sla ook geüploade bijlagen op',
- 'include_config' => 'Sla ook een configuratiebestand ook',
- 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op',
- 'accounts' => 'Exporteer boekingen van deze rekeningen',
- 'csv_comma' => 'Een komma (,)',
- 'csv_semicolon' => 'Een puntkomma (;)',
- 'csv_tab' => 'Een tab (onzichtbaar)',
- 'delete_account' => 'Verwijder rekening ":name"',
- 'delete_bill' => 'Verwijder contract ":name"',
- 'delete_budget' => 'Verwijder budget ":name"',
- 'delete_category' => 'Verwijder categorie ":name"',
- 'delete_currency' => 'Verwijder valuta ":name"',
- 'delete_journal' => 'Verwijder transactie met omschrijving ":description"',
- 'delete_attachment' => 'Verwijder bijlage ":name"',
- 'delete_rule' => 'Verwijder regel ":title"',
- 'delete_rule_group' => 'Verwijder regelgroep ":title"',
- 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?',
- 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?',
- 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?',
- 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?',
- 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?',
- 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?',
- 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?',
- 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?',
- 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?',
- 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?',
- 'mass_journal_are_you_sure' => 'Weet je zeker dat je al deze transacties wilt verwijderen?',
- 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?',
- 'permDeleteWarning' => 'Dingen verwijderen uit Firefly is permanent en kan niet ongedaan gemaakt worden.',
- 'mass_make_selection' => 'Je kan items alsnog redden van de ondergang door het vinkje weg te halen.',
- 'delete_all_permanently' => 'Verwijder geselecteerde items permanent',
- 'update_all_journals' => 'Wijzig deze transacties',
- 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.',
- 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.',
- 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.',
- 'bill_keep_transactions' => 'De transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.',
- 'budget_keep_transactions' => 'De transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.',
- 'category_keep_transactions' => 'De transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.',
- 'tag_keep_transactions' => 'De transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.',
+ 'bank_name' => 'Banknaam',
+ 'bank_balance' => 'Saldo',
+ 'savings_balance' => 'Saldo van spaarrekening',
+ 'credit_card_limit' => 'Credit card limiet',
+ 'automatch' => 'Automatisch herkennen',
+ 'skip' => 'Overslaan',
+ 'name' => 'Naam',
+ 'active' => 'Actief',
+ 'amount_min' => 'Minimumbedrag',
+ 'amount_max' => 'Maximumbedrag',
+ 'match' => 'Reageert op',
+ 'repeat_freq' => 'Herhaling',
+ 'journal_currency_id' => 'Valuta',
+ 'journal_amount' => 'Bedrag',
+ 'journal_asset_source_account' => 'Betaalrekening (bron)',
+ 'journal_source_account_name' => 'Debiteur (bron)',
+ 'journal_source_account_id' => 'Betaalrekening (bron)',
+ 'account_from_id' => 'Van account',
+ 'account_to_id' => 'Naar account',
+ 'journal_destination_account_id' => 'Betaalrekening (doel)',
+ 'asset_destination_account' => 'Betaalrekening (doel)',
+ 'asset_source_account' => 'Betaalrekening (bron)',
+ 'journal_description' => 'Omschrijving',
+ 'split_journal' => 'Splits deze transactie',
+ 'split_journal_explanation' => 'Splits deze transactie in meerdere stukken',
+ 'currency' => 'Valuta',
+ 'account_id' => 'Betaalrekening',
+ 'budget_id' => 'Budget',
+ 'openingBalance' => 'Startsaldo',
+ 'tagMode' => 'Tag modus',
+ 'tagPosition' => 'Tag locatie',
+ 'virtualBalance' => 'Virtuele saldo',
+ 'longitude_latitude' => 'Locatie',
+ 'targetamount' => 'Doelbedrag',
+ 'accountRole' => 'Rol van rekening',
+ 'openingBalanceDate' => 'Startsaldodatum',
+ 'ccType' => 'Betaalplan',
+ 'ccMonthlyPaymentDate' => 'Betaaldatum',
+ 'piggy_bank_id' => 'Spaarpotje',
+ 'returnHere' => 'Keer terug',
+ 'returnHereExplanation' => 'Terug naar deze pagina na het opslaan.',
+ 'returnHereUpdateExplanation' => 'Terug naar deze pagina na het wijzigen.',
+ 'description' => 'Omschrijving',
+ 'expense_account' => 'Crediteur',
+ 'revenue_account' => 'Debiteur',
+ 'amount' => 'Bedrag',
+ 'date' => 'Datum',
+ 'interest_date' => 'Rentedatum',
+ 'book_date' => 'Boekdatum',
+ 'process_date' => 'Verwerkingsdatum',
+ 'category' => 'Categorie',
+ 'tags' => 'Tags',
+ 'deletePermanently' => 'Verwijderen',
+ 'cancel' => 'Annuleren',
+ 'targetdate' => 'Doeldatum',
+ 'tag' => 'Tag',
+ 'under' => 'Onder',
+ 'symbol' => 'Symbool',
+ 'code' => 'Code',
+ 'iban' => 'IBAN',
+ 'accountNumber' => 'Rekeningnummer',
+ 'csv' => 'CSV-bestand',
+ 'has_headers' => 'Kolomnamen op de eerste rij?',
+ 'date_format' => 'Datumformaat',
+ 'csv_config' => 'Configuratiebestand',
+ 'specifix' => 'Bank- or of bestandsspecifieke opties',
+ 'csv_import_account' => 'Standaard rekening voor importeren',
+ 'csv_delimiter' => 'CSV scheidingsteken',
+ 'attachments[]' => 'Bijlagen',
+ 'store_new_withdrawal' => 'Nieuwe uitgave opslaan',
+ 'store_new_deposit' => 'Nieuwe inkomsten opslaan',
+ 'store_new_transfer' => 'Nieuwe overschrijving opslaan',
+ 'add_new_withdrawal' => 'Maak nieuwe uitgave',
+ 'add_new_deposit' => 'Maak nieuwe inkomsten',
+ 'add_new_transfer' => 'Maak nieuwe overschrijving',
+ 'noPiggybank' => '(geen spaarpotje)',
+ 'title' => 'Titel',
+ 'notes' => 'Notities',
+ 'filename' => 'Bestandsnaam',
+ 'mime' => 'Bestandstype',
+ 'size' => 'Grootte',
+ 'trigger' => 'Trigger',
+ 'stop_processing' => 'Stop met verwerken',
+ 'start_date' => 'Start van bereik',
+ 'end_date' => 'Einde van bereik',
+ 'export_start_range' => 'Start van exportbereik',
+ 'export_end_range' => 'Einde van exportbereik',
+ 'export_format' => 'Bestandsformaat',
+ 'include_attachments' => 'Sla ook geüploade bijlagen op',
+ 'include_config' => 'Sla ook een configuratiebestand ook',
+ 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op',
+ 'accounts' => 'Exporteer boekingen van deze rekeningen',
+ 'csv_comma' => 'Een komma (,)',
+ 'csv_semicolon' => 'Een puntkomma (;)',
+ 'csv_tab' => 'Een tab (onzichtbaar)',
+ 'delete_account' => 'Verwijder rekening ":name"',
+ 'delete_bill' => 'Verwijder contract ":name"',
+ 'delete_budget' => 'Verwijder budget ":name"',
+ 'delete_category' => 'Verwijder categorie ":name"',
+ 'delete_currency' => 'Verwijder valuta ":name"',
+ 'delete_journal' => 'Verwijder transactie met omschrijving ":description"',
+ 'delete_attachment' => 'Verwijder bijlage ":name"',
+ 'delete_rule' => 'Verwijder regel ":title"',
+ 'delete_rule_group' => 'Verwijder regelgroep ":title"',
+ 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?',
+ 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?',
+ 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?',
+ 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?',
+ 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?',
+ 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?',
+ 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?',
+ 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?',
+ 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?',
+ 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?',
+ 'mass_journal_are_you_sure' => 'Weet je zeker dat je al deze transacties wilt verwijderen?',
+ 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?',
+ 'permDeleteWarning' => 'Dingen verwijderen uit Firefly is permanent en kan niet ongedaan gemaakt worden.',
+ 'mass_make_selection' => 'Je kan items alsnog redden van de ondergang door het vinkje weg te halen.',
+ 'delete_all_permanently' => 'Verwijder geselecteerde items permanent',
+ 'update_all_journals' => 'Wijzig deze transacties',
+ 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.',
+ 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.',
+ 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.',
+ 'bill_keep_transactions' => 'De transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.',
+ 'budget_keep_transactions' => 'De transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.',
+ 'category_keep_transactions' => 'De transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.',
+ 'tag_keep_transactions' => 'De transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.',
];
diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php
index 6c22a737b3..c96785ecbf 100644
--- a/resources/lang/nl_NL/list.php
+++ b/resources/lang/nl_NL/list.php
@@ -22,8 +22,13 @@ return [
'balanceDiff' => 'Saldoverschil tussen :start en :end',
'matchedOn' => 'Wordt herkend',
'matchesOn' => 'Wordt herkend',
+ 'account_type' => 'Accounttype',
+ 'new_balance' => 'Nieuw saldo',
+ 'account' => 'Rekening',
'matchingAmount' => 'Bedrag',
'lastMatch' => 'Laatste keer gezien',
+ 'split_number' => 'Split #',
+ 'destination' => 'Doel',
'expectedMatch' => 'Wordt verwacht',
'automatch' => 'Automatisch herkennen?',
'repeat_freq' => 'Herhaling',
@@ -34,6 +39,7 @@ return [
'book_date' => 'Boekdatum',
'process_date' => 'Verwerkingsdatum',
'from' => 'Van',
+ 'piggy_bank' => 'Spaarpotje',
'to' => 'Naar',
'budget' => 'Budget',
'category' => 'Categorie',
diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php
index dd48deb096..075279714f 100644
--- a/resources/lang/nl_NL/validation.php
+++ b/resources/lang/nl_NL/validation.php
@@ -17,6 +17,7 @@ return [
'file_attached' => 'Bestand met naam ":name" is met succes geuploaded.',
'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.',
'file_too_large' => 'Bestand ":name" is te groot.',
+ 'belongs_to_user' => 'De waarde van :attribute is onbekend',
'accepted' => ':attribute moet geaccepteerd zijn.',
'active_url' => ':attribute is geen geldige URL.',
'after' => ':attribute moet een datum na :date zijn.',
diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php
index ba0b6bd44c..599afa7b20 100644
--- a/resources/lang/pt_BR/firefly.php
+++ b/resources/lang/pt_BR/firefly.php
@@ -31,8 +31,8 @@ return [
'bounced_error' => 'A mensagem enviado para :email ressaltado, não tem acesso para você.',
'deleted_error' => 'Estas credenciais não correspondem aos nossos registros.',
'general_blocked_error' => 'Sua conta foi desativada, você não pode entrar.',
- 'expired_error' => 'Your account has expired, and can no longer be used.',
- 'unbalanced_error' => 'Your transactions are unbalanced. This means a withdrawal, deposit or transfer was not stored properly. Please check your accounts and transactions for errors (unbalanced amount :amount).',
+ 'expired_error' => 'Sua conta expirou e não pode mais ser usada.',
+ 'unbalanced_error' => 'Suas operações estão desequilibrados. Isto significa uma retirada, depósito ou transferência não foi armazenado corretamente. Por favor verifique suas contas e transações por erros (quantidade desequilibrada :amount).',
'removed_amount' => ':amount removido',
'added_amount' => ':amount adicionada',
'asset_account_role_help' => 'Quaisquer opções extras resultantes da sua escolha pode ser definido mais tarde.',
@@ -69,10 +69,12 @@ return [
'warning_much_data' => ':days dias de dados podem demorar um pouco para carregar.',
'registered' => 'Você se registrou com sucesso!',
'search' => 'Pesquisa',
- 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.',
+ 'no_budget_pointer' => 'Parece que não há orçamentos ainda. Você deve criar alguns na página orçamentos. Orçamentos podem ajudá-lo a manter o controle de despesas.',
+ 'source_accounts' => 'Source account(s)',
+ 'destination_accounts' => 'Destination account(s)',
// repeat frequencies:
- 'repeat_freq_monthly' => 'monthly',
+ 'repeat_freq_monthly' => 'mensal',
'weekly' => 'Semanal',
'quarterly' => 'Trimestral',
'half-year' => 'Semestral',
@@ -123,7 +125,7 @@ return [
// rules
'rules' => 'Regras',
- 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.',
+ 'rules_explanation' => 'Aqui você pode gerenciar as regras. As regras são acionadas quando uma transação é criada ou atualizada. Então, se a transação tiver certas propriedades (chamadas de "gatilhos") Firefly executará as "ações". Combinado, você pode fazer Firefly responder de uma certa maneira para novas transações.',
'rule_name' => 'Nome da regra',
'rule_triggers' => 'Regra dispara quando',
'rule_actions' => 'Regra será',
@@ -169,36 +171,36 @@ return [
'edit_rule' => 'Editar regra ":title"',
'delete_rule' => 'Excluir a regra ":title"',
'update_rule' => 'Atualizar Regra',
- 'test_rule_triggers' => 'Veja transações equivalentes',
- 'warning_transaction_subset' => 'For performance reasons this list is limited to :max_num_transactions and may only show a subset of matching transactions',
- 'warning_no_matching_transactions' => 'No matching transactions found. Please note that for performance reasons, only the last :num_transactions transactions have been checked.',
+ 'test_rule_triggers' => 'Veja transações correspondentes',
+ 'warning_transaction_subset' => 'Por razões de desempenho esta lista está limitado a :max_num_transactions e só pode mostrar um subconjunto das transações correspondentes',
+ 'warning_no_matching_transactions' => 'Nenhuma transação correspondente encontrada. Por favor note que por motivos de desempenho, apenas as últimas :num_transactions transações tenham sido verificadas.',
'warning_no_valid_triggers' => 'Sem gatilhos válidos fornecidos.',
'execute_on_existing_transactions' => 'Executar transações existentes',
'execute_on_existing_transactions_intro' => 'Quando uma regra ou um grupo for alterado ou adicionado, você pode executá-lo para transações existentes',
'execute_on_existing_transactions_short' => 'Transações existentes',
- 'executed_group_on_existing_transactions' => 'Executed group ":title" for existing transactions',
- 'execute_group_on_existing_transactions' => 'Execute group ":title" for existing transactions',
- 'include_transactions_from_accounts' => 'Include transactions from these accounts',
+ 'executed_group_on_existing_transactions' => 'Executado o grupo ":title" para transações existentes',
+ 'execute_group_on_existing_transactions' => 'Executar o grupo ":title" para transações existentes',
+ 'include_transactions_from_accounts' => 'Incluir as transações destas contas',
'execute' => 'Executar',
// actions and triggers
'rule_trigger_user_action' => 'Ação do usuário é ":trigger_value"',
- 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"',
- 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"',
- 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"',
- 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"',
- 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"',
- 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"',
- 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"',
- 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"',
- 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"',
- 'rule_trigger_amount_less' => 'Amount is less than :trigger_value',
+ 'rule_trigger_from_account_starts' => 'Conta de origem começa com ":trigger_value"',
+ 'rule_trigger_from_account_ends' => 'Conta de origem termina com ":trigger_value"',
+ 'rule_trigger_from_account_is' => 'É da conta de origem ":trigger_value"',
+ 'rule_trigger_from_account_contains' => 'Conta de origem contém ":trigger_value"',
+ 'rule_trigger_to_account_starts' => 'Conta destino começa com ":trigger_value"',
+ 'rule_trigger_to_account_ends' => 'Conta destino termina com ":trigger_value"',
+ 'rule_trigger_to_account_is' => 'Conta de destino é ":trigger_value"',
+ 'rule_trigger_to_account_contains' => 'Conta de destino contém ":trigger_value"',
+ 'rule_trigger_transaction_type' => 'Transação é do tipo ":trigger_value"',
+ 'rule_trigger_amount_less' => 'Quantia é inferior :trigger_value',
'rule_trigger_amount_exactly' => 'Quantia é: trigger_value',
- 'rule_trigger_amount_more' => 'Amount is more than :trigger_value',
- 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"',
- 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"',
- 'rule_trigger_description_contains' => 'Description contains ":trigger_value"',
- 'rule_trigger_description_is' => 'Description is ":trigger_value"',
+ 'rule_trigger_amount_more' => 'Quantia é mais de :trigger_value',
+ 'rule_trigger_description_starts' => 'Descrição começa com ":trigger_value"',
+ 'rule_trigger_description_ends' => 'Descrição termina com ":trigger_value"',
+ 'rule_trigger_description_contains' => 'Descrição contém ":trigger_value"',
+ 'rule_trigger_description_is' => 'Descrição é ":trigger_value"',
'rule_trigger_from_account_starts_choice' => 'Conta de origem começa com..',
'rule_trigger_from_account_ends_choice' => 'Conta de origem termina com..',
'rule_trigger_from_account_is_choice' => 'Conta de origem é..',
@@ -257,6 +259,7 @@ return [
'pref_1M' => 'Um mês',
'pref_3M' => 'Trimestral',
'pref_6M' => 'Semestral',
+ 'pref_1Y' => 'Um ano',
'pref_languages' => 'Idiomas',
'pref_languages_help' => 'Firefly III suporta muitos idiomas. Qual você prefere?',
'pref_custom_fiscal_year' => 'Configurações de ano fiscal',
@@ -275,10 +278,11 @@ return [
'pref_two_factor_auth_remove_will_disable' => '(isso também irá desativar a autenticação de duas etapas)',
'pref_save_settings' => 'Salvar definições',
'saved_preferences' => 'Preferências salvas!',
- 'transaction_page_size_title' => 'Page size',
- 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions',
- 'transaction_page_size_label' => 'Page size',
- 'budget_maximum' => 'Budget maximum',
+ 'transaction_page_size_title' => 'Tamanho da página',
+ 'transaction_page_size_help' => 'Qualquer lista de transações mostra, no máximo, muitas transações',
+ 'transaction_page_size_label' => 'Tamanho da página',
+ 'budget_maximum' => 'Máximo de orçamento',
+ 'between_dates' => '(:start e :end)',
// profile:
'change_your_password' => 'Alterar sua senha',
@@ -338,7 +342,7 @@ return [
'csv_csv_config_file_help' => 'Selecione a configuração de importação CSV aqui. Se você não sabe o que é isso, ignore. Será explicado mais tarde.',
'csv_upload_button' => 'Iniciando importação do CSV',
'csv_column_roles_title' => 'Definir papeis da coluna',
- 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.',
+ 'csv_column_roles_text' => 'Firefly não sabe o que significa cada coluna. Você precisa indicar o que cada coluna é. Por favor confira os dados de exemplo se você não está certo mesmo. Clique sobre o ponto de interrogação (superior direito da página) para aprender o que significa que cada coluna. Se você deseja mapear dados importados para dados existentes em Firefly, use a caixa de seleção. O próximo passo irá mostrar-lhe o que esse botão faz.',
'csv_column_roles_table' => 'Papéis da Coluna',
'csv_column' => 'Coluna CSV',
'csv_column_name' => 'Nome da coluna do CSV',
@@ -348,20 +352,20 @@ return [
'csv_continue' => 'Continuar para o próximo passo',
'csv_go_back' => 'Voltar para o passo anterior',
'csv_map_title' => 'Valores mapeados encontrados para valores existentes',
- 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.',
+ 'csv_map_text' => 'Esta página permite mapear os valores do arquivo CSV para entradas existentes em seu banco de dados. Isso garante que as contas e outras coisas não serão criadas duas vezes.',
'csv_field_value' => 'Valor do campo do CSV',
'csv_field_mapped_to' => 'Deve ser mapeado para...',
'csv_do_not_map' => 'Não mapear este valor',
'csv_download_config_title' => 'Download do CSV de configuração ',
- 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.',
- 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.',
+ 'csv_download_config_text' => 'Tudo o que você configurou pode ser baixado como um arquivo de configuração. Clique no botão para fazê-lo.',
+ 'csv_more_information_text' => 'Se a importação falhar, você pode usar este arquivo de configuração, então você não tem que começar tudo de novo. Mas, se a importação for bem-sucedido, será mais fácil carregar arquivos CSV semelhantes.',
'csv_do_download_config' => 'Download do arquivo de configuração.',
'csv_empty_description' => '(descrição vazia)',
'csv_upload_form' => 'Formulário de Upload do CSV',
'csv_index_unsupported_warning' => 'O importador de CSV está incapaz de fazer o seguinte:',
- 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.',
- 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".',
- 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.',
+ 'csv_unsupported_map' => 'O importador não pode mapear a coluna ":columnRole" para os valores existentes no banco de dados.',
+ 'csv_unsupported_value' => 'O importador não sabe como lidar com valores em colunas marcadas como ":columnRole".',
+ 'csv_cannot_store_value' => 'O importador não reservou espaço para colunas marcadas ":columnRole" e será incapaz de processá-los.',
'csv_process_title' => 'Importação do CSV terminou!',
'csv_process_text' => 'O importador do CSV terminou e processou :rows linhas',
'csv_row' => 'Linha',
@@ -393,24 +397,24 @@ return [
'csv_column_opposing-iban' => 'Opondo-se conta (IBAN)',
'csv_column_opposing-id' => 'ID da conta opostas (Firefly equivalente)',
'csv_column_opposing-name' => 'Opondo-se conta (nome)',
- 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator',
- 'csv_column_ing-debet-credit' => 'ING specific debet/credit indicator',
- 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID',
- 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account',
- 'csv_column_sepa-db' => 'SEPA Direct Debet',
+ 'csv_column_rabo-debet-credit' => 'Indicador de débito/crédito do Rabobank',
+ 'csv_column_ing-debet-credit' => 'Indicador de débito/crédito do ING',
+ 'csv_column_sepa-ct-id' => 'Transferência de crédito SEPA fim-a-fim ID',
+ 'csv_column_sepa-ct-op' => 'Transferência de crédito SEPA conta contrária',
+ 'csv_column_sepa-db' => 'SEPA Débito Direto',
'csv_column_tags-comma' => 'Tags (separadas por vÃrgula)',
'csv_column_tags-space' => 'Tags (separadas por espaço)',
'csv_column_account-number' => 'Conta de ativo (número da conta)',
- 'csv_column_opposing-number' => 'Opposing account (account number)',
- 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.',
- 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.',
- 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.',
- 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.',
- 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.',
- 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?',
- 'could_not_recover' => 'Could not continue from the previous step. Your progress has been lost :(. The log files will tell you what happened.',
- 'must_select_roles' => 'You must select some roles for your file content, or the process cannot continue.',
- 'invalid_mapping' => 'You have submitted an invalid mapping. The process cannot continue.',
+ 'csv_column_opposing-number' => 'Conta Contrária (número da conta)',
+ 'csv_specifix_RabobankDescription' => 'Selecione esta opção quando você estiver importando arquivos de exportação de CSV do Rabobank.',
+ 'csv_specifix_AbnAmroDescription' => 'Selecione esta opção quando você estiver importando arquivos de exportação de CSV do ABN AMRO.',
+ 'csv_specifix_Dummy' => 'Marcar esta não tem qualquer efeito.',
+ 'csv_import_account_help' => 'Se seu arquivo CSV não contém informações sobre sua(s) conta(s) ativa(s), use este combobox para selecionar para qual conta pertencem as transações em CSV.',
+ 'csv_delimiter_help' => 'Escolha o delimitador de campo que é usado em seu arquivo de entrada. Se não estiver claro, a vÃrgula é a opção mais segura.',
+ 'csv_date_parse_error' => 'Não foi possÃvel analisar uma data válida de ":value", usando o formato ":formato". Tem certeza que seu CSV está correto?',
+ 'could_not_recover' => 'Não poderia continuar da etapa anterior. Seu progresso foi perdido :(. Os arquivos de log você dirão o que aconteceu.',
+ 'must_select_roles' => 'Você deve selecionar algumas funções para o conteúdo do arquivo, ou o processo não pode continuar.',
+ 'invalid_mapping' => 'Você se submeteu a um mapeamento inválido. O processo não pode continuar.',
'no_file_uploaded' => 'Parece que você não enviou um arquivo.',
@@ -442,11 +446,11 @@ return [
// new user:
'submit' => 'Enviar',
'getting_started' => 'Iniciar',
- 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:',
- 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:',
- 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.',
- 'stored_new_account_new_user' => 'Yay! Your new account has been stored.',
- 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.',
+ 'to_get_started' => 'Para começar com o Firefly, por favor digite o nome do seu banco atual e o saldo de sua conta corrente:',
+ 'savings_balance_text' => 'Se você tem uma conta poupança, por favor, digite o saldo atual de sua conta de poupança:',
+ 'cc_balance_text' => 'Se você tiver um cartão de crédito, por favor digite o limite do seu cartão de crédito.',
+ 'stored_new_account_new_user' => 'Yay! Sua nova conta foi armazenada.',
+ 'stored_new_accounts_new_user' => 'Yay! Suas novas contas foram armazenadas.',
// forms:
'mandatoryFields' => 'Campos obrigatórios',
@@ -459,8 +463,11 @@ return [
'store_new_budget' => 'Armazenar novo orçamento',
'stored_new_budget' => 'Novo orçamento armazenado ":name"',
'availableIn' => 'DisponÃvel em :date',
+ 'available_between' => 'DisponÃvel entre :start e :end',
'transactionsWithoutBudget' => 'Despesas sem orçamentos',
'transactionsWithoutBudgetDate' => 'Despesas sem orçamentos em :date',
+ 'transactions_no_budget' => 'Despesas sem orçamento entre :start e :end',
+ 'spent_between' => 'Gasto entre :start e :end',
'createBudget' => 'Novo orçamento',
'inactiveBudgets' => 'Orçamentos inativos',
'without_budget_between' => 'Transações sem um orçamento entre :start e :end',
@@ -471,20 +478,21 @@ return [
'updated_budget' => 'Orçamento atualizado ":name"',
'update_amount' => 'Atualizar quantia',
'update_budget' => 'Atualizar Orçamento',
+ 'update_budget_amount_range' => 'Atualizar quantia disponÃvel (esperada) entre :start e :end',
// bills:
- 'matching_on' => 'Matching on',
- 'between_amounts' => 'between :low and :high.',
- 'repeats' => 'Repeats',
- 'connected_journals' => 'Connected transactions',
- 'auto_match_on' => 'Automatically matched by Firefly',
- 'auto_match_off' => 'Not automatically matched by Firefly',
- 'next_expected_match' => 'Next expected match',
+ 'matching_on' => 'Corresponde em',
+ 'between_amounts' => 'entre :low e :high.',
+ 'repeats' => 'Repetições',
+ 'connected_journals' => 'Transações conectadas',
+ 'auto_match_on' => 'Coincidido automaticamente pelo Firefly',
+ 'auto_match_off' => 'Não coincidido automaticamente pelo Firefly',
+ 'next_expected_match' => 'Próximo correspondente esperado',
'delete_bill' => 'Apagar fatura ":name"',
'deleted_bill' => 'Fatura apagada ":name"',
'edit_bill' => 'Editar fatura ":name"',
- 'more' => 'More',
- 'rescan_old' => 'Rescan old transactions',
+ 'more' => 'Mais',
+ 'rescan_old' => 'Examinar novamente o transações antigas',
'update_bill' => 'Atualizar fatura',
'updated_bill' => 'Fatura atualizada ":name"',
'store_new_bill' => 'Armazenar nova fatura',
@@ -528,7 +536,7 @@ return [
'save_transactions_by_moving' => 'Salve essas transações, movendo-os para outra conta:',
'stored_new_account' => 'Nova conta ":name" armazenado!',
'updated_account' => 'Conta ":name" atualizada',
- 'credit_card_options' => 'Credit card options',
+ 'credit_card_options' => 'Opções de cartão de crédito',
// categories:
'new_category' => 'Nova categoria',
@@ -560,16 +568,16 @@ return [
'deleted_deposit' => 'Depósito ":description" excluÃdo com sucesso',
'deleted_transfer' => 'Transferência ":description" excluÃda com sucesso',
'stored_journal' => 'Transação ":description" excluÃda com sucesso',
- 'select_transactions' => 'Select transactions',
- 'stop_selection' => 'Stop selecting transactions',
- 'edit_selected' => 'Edit selected',
- 'delete_selected' => 'Delete selected',
- 'mass_delete_journals' => 'Delete a number of transactions',
- 'mass_edit_journals' => 'Edit a number of transactions',
- 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.',
- 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.',
- 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).',
- 'mass_edited_transactions_success' => 'Updated :amount transaction(s)',
+ 'select_transactions' => 'Selecione as transações',
+ 'stop_selection' => 'Parar de selecionar transações',
+ 'edit_selected' => 'Editar selecionado',
+ 'delete_selected' => 'Excluir selecionados',
+ 'mass_delete_journals' => 'Excluir um número de transacções',
+ 'mass_edit_journals' => 'Editar um número de transacções',
+ 'cannot_edit_other_fields' => 'Você não pode editar em massa outros campos que não esse aqui, porque não há espaço para mostrá-los. Por favor siga o link e editá-los por um por um, se você precisar editar esses campos.',
+ 'perm-delete-many' => 'Exclusão de muitos itens de uma só vez pode ser muito perturbador. Por favor, seja cauteloso.',
+ 'mass_deleted_transactions_success' => 'ExcluÃdo :amount de transação(ões).',
+ 'mass_edited_transactions_success' => 'Atualizado :amount de transação(ões)',
// new user:
@@ -648,6 +656,7 @@ return [
'report_audit' => 'Visão geral do histórico de transação de :start até :end',
'quick_link_reports' => 'Ligações rápidas',
'quick_link_default_report' => 'Relatório financeiro padrão',
+ 'quick_link_audit_report' => 'Transaction history overview',
'report_this_month_quick' => 'Mês atual, todas as contas',
'report_this_year_quick' => 'Ano atual, todas as contas',
'report_this_fiscal_year_quick' => 'Ano fiscal atual, todas as contas',
@@ -666,7 +675,7 @@ return [
'splitByAccount' => 'Dividir por conta',
'balancedByTransfersAndTags' => 'Balanço por transferências e tags',
'coveredWithTags' => 'Coberto com tags',
- 'leftUnbalanced' => 'Left unbalanced',
+ 'leftUnbalanced' => 'Deixar desequilibrado',
'expectedBalance' => 'Saldo Experado',
'outsideOfBudgets' => 'Fora do orçamento',
'leftInBudget' => 'Deixou no orçamento',
@@ -674,7 +683,7 @@ return [
'noCategory' => '(sem categoria)',
'notCharged' => 'Não cobrado (ainda)',
'inactive' => 'Inativo',
- 'active' => 'Active',
+ 'active' => 'Ativo',
'difference' => 'Diferente',
'in' => 'Entrada',
'out' => 'SaÃda',
@@ -690,8 +699,8 @@ return [
'report_type' => 'Tipo de relatório',
'report_type_default' => 'Relatório financeiro padrão',
'report_type_audit' => 'Visão geral do histórico de transação (auditoria)',
- 'report_type_meta-history' => 'Categories, budgets and bills overview',
- 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.',
+ 'report_type_meta-history' => 'Visão geral de categorias, orçamentos e faturas',
+ 'more_info_help' => 'Mais informações sobre esses tipos de relatórios podem ser encontradas nas páginas de ajuda. Pressione o Ãcone (?) no canto superior direito.',
'report_included_accounts' => 'Contas incluÃdas',
'report_date_range' => 'PerÃodo',
'report_include_help' => 'Em todos os casos, as transferências para contas compartilhadas contam como despesas e transferências de contas compartilhadas contam como renda.',
@@ -707,7 +716,7 @@ return [
'audit_end_balance' => 'Saldo da conta : account_name no final de :end foi :balance',
// charts:
- 'chart' => 'Chart',
+ 'chart' => 'Gráfico',
'dayOfMonth' => 'Dia do mês',
'month' => 'Mês',
'budget' => 'Orçamento',
@@ -782,10 +791,10 @@ return [
'no_year' => 'Nenhum ano definido',
'no_month' => 'Nenhum mês definido',
'tag_title_nothing' => 'Tags padrões',
- 'tag_title_balancingAct' => 'Balancing act tags',
- 'tag_title_advancePayment' => 'Advance payment tags',
- 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.',
- 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.',
+ 'tag_title_balancingAct' => 'Saldo das tags',
+ 'tag_title_advancePayment' => 'Tags de pagamento do adiantamento',
+ 'tags_introduction' => 'Geralmente tags são palavras singulares, projetadas para rapidamente agrupar itens usando coisas como caro, conta ou para a festa. Em Firefly III, tags podem ter propriedades mais como uma data, descrição e localização. Isso permite que você una as operações de uma forma mais significativa. Por exemplo, você poderia fazer uma tag chamada ceia de Natal com amigos e adicionar informações sobre o restaurante. Tais tags são "singulares", só use-as para uma ocasião única, talvez com várias transações.',
+ 'tags_group' => 'Tags agrupam transações, que torna possÃvel armazenar os reembolsos (no caso você empreste dinheiro para os outros) e outros "balancetes" onde as despesas são somadas (os pagamentos na sua TV nova) ou onde as despesas e depósitos estão anulando uns aos outros (compra algo com dinheiro guardado). Isso é tudo para você. Usando tags à moda antiga claro é sempre possÃvel.',
'tags_start' => 'Crie uma tag para começar ou insira tags ao criar novas transações.',
@@ -794,4 +803,36 @@ return [
'user_administration' => 'Administração de usuários',
'list_all_users' => 'Todos os usuários',
'all_users' => 'Todos os usuários',
+
+ // split a transaction:
+ 'transaction_meta_data' => 'Transaction meta-data',
+ 'transaction_dates' => 'Transaction dates',
+ 'splits' => 'Splits',
+ 'split_title_withdrawal' => 'Split your new withdrawal',
+ 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
+ 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
+ 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
+ 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_withdrawal' => 'Store splitted withdrawal',
+ 'update_splitted_withdrawal' => 'Update splitted withdrawal',
+
+ 'split_title_deposit' => 'Split your new deposit',
+ 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.',
+ 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.',
+ 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.',
+ 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_deposit' => 'Store splitted deposit',
+
+ 'split_title_transfer' => 'Split your new transfer',
+ 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
+ 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
+ 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
+ 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
+ 'store_splitted_transfer' => 'Store splitted transfer',
+
+ 'add_another_split' => 'Add another split',
+ 'split-transactions' => 'Split transactions',
+ 'split-new-transaction' => 'Split a new transaction',
+
+
];
diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php
index a372d46f85..1b7f25b825 100644
--- a/resources/lang/pt_BR/form.php
+++ b/resources/lang/pt_BR/form.php
@@ -10,119 +10,131 @@
return [
// new user:
- 'bank_name' => 'Nome do banco',
- 'bank_balance' => 'Saldo',
- 'savings_balance' => 'Salda da Poupança',
- 'credit_card_limit' => 'Limite do Cartão de Crédito',
- 'automatch' => 'Equivale automaticamente',
- 'skip' => 'Pular',
- 'name' => 'Nome',
- 'active' => 'Ativar',
- 'amount_min' => 'Valor MÃnimo',
- 'amount_max' => 'Valor Máximo',
- 'match' => 'Corresponde em',
- 'repeat_freq' => 'Repetições',
- 'account_from_id' => 'da conta',
- 'account_to_id' => 'para conta',
- 'account_id' => 'Conta de ativo',
- 'budget_id' => 'Orçamento',
- 'openingBalance' => 'Saldo inicial',
- 'tagMode' => 'Modo de tag',
- 'tagPosition' => 'Localização de tag',
- 'virtualBalance' => 'Saldo virtual',
- 'longitude_latitude' => 'Localização',
- 'targetamount' => 'Valor alvo',
- 'accountRole' => 'Tipo de conta',
- 'openingBalanceDate' => 'Data do Saldo inicial',
- 'ccType' => 'Plano de pagamento do Cartão de Crédito',
- 'ccMonthlyPaymentDate' => 'Data do pagamento mensal do Cartão de Crédito',
- 'piggy_bank_id' => 'Cofrinho',
- 'returnHere' => 'Retornar aqui',
- 'returnHereExplanation' => 'Depois de armazenar, retorne aqui para criar outro.',
- 'returnHereUpdateExplanation' => 'Depois da atualização, retorne aqui',
- 'description' => 'Descrição',
- 'expense_account' => 'Conta de Despesa',
- 'revenue_account' => 'Conta de Receita',
- 'amount' => 'Valor',
- 'date' => 'Data',
- 'interest_date' => 'Data de interesse',
- 'book_date' => 'Data reserva',
- 'process_date' => 'Data de processamento',
- 'category' => 'Categoria',
- 'tags' => 'Etiquetas',
- 'deletePermanently' => 'Apagar permanentemente',
- 'cancel' => 'Cancelar',
- 'targetdate' => 'Data Alvo',
- 'tag' => 'Etiqueta',
- 'under' => 'Debaixo',
- 'symbol' => 'SÃmbolo',
- 'code' => 'Código',
- 'iban' => 'IBAN',
- 'accountNumber' => 'Número de conta',
- 'csv' => 'Arquivo CSV',
- 'has_headers' => 'Cabeçalhos',
- 'date_format' => 'Formato da Data',
- 'csv_config' => 'Importar CSV de configuração',
- 'specifix' => 'Banco- ou arquivo especÃfico corrigÃdos',
- 'csv_import_account' => 'Conta de importação padrão',
- 'csv_delimiter' => 'Delimitador de campo CSV',
- 'attachments[]' => 'Anexos',
- 'store_new_withdrawal' => 'Armazenar nova retirada',
- 'store_new_deposit' => 'Armazenar novo depósito',
- 'store_new_transfer' => 'Armazenar nova transferência',
- 'add_new_withdrawal' => 'Adicionar uma nova retirada',
- 'add_new_deposit' => 'Adicionar um novo depósito',
- 'add_new_transfer' => 'Adicionar uma nova transferência',
- 'noPiggybank' => '(nenhum cofrinho)',
- 'title' => 'TÃtulo',
- 'notes' => 'Notas',
- 'filename' => 'Nome do arquivo',
- 'mime' => 'Tipo do Arquivo (MIME)',
- 'size' => 'Tamanho',
- 'trigger' => 'Disparo',
- 'stop_processing' => 'Parar processamento',
- 'start_date' => 'InÃcio do intervalo',
- 'end_date' => 'Final do intervalo',
- 'export_start_range' => 'InÃcio do intervalo de exportação',
- 'export_end_range' => 'Fim do intervalo de exportação',
- 'export_format' => 'Formato do arquivo',
- 'include_attachments' => 'Incluir anexos enviados',
- 'include_config' => 'Incluir o arquivo de configuração',
- 'include_old_uploads' => 'Incluir dados importados',
- 'accounts' => 'Exportar transações destas contas',
- 'csv_comma' => 'Uma vÃrgula (,)',
- 'csv_semicolon' => 'Um ponto e vÃrgula (;)',
- 'csv_tab' => 'Um Tab (invisÃvel)',
- 'delete_account' => 'Apagar conta ":name"',
- 'delete_bill' => 'Apagar fatura ":name"',
- 'delete_budget' => 'Excluir o orçamento ":name"',
- 'delete_category' => 'Excluir categoria ":name"',
- 'delete_currency' => 'Excluir moeda ":moeda"',
- 'delete_journal' => 'Excluir a transação com a descrição ":description"',
- 'delete_attachment' => 'Apagar anexo ":name"',
- 'delete_rule' => 'Excluir regra ":title"',
- 'delete_rule_group' => 'Exclua o grupo de regras ":title"',
- 'attachment_areYouSure' => 'Tem certeza que deseja excluir o anexo denominado ":name"?',
- 'account_areYouSure' => 'Tem certeza que deseja excluir a conta denominada ":name"?',
- 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?',
- 'rule_areYouSure' => 'Tem certeza que deseja excluir a regra intitulada ":title"?',
- 'ruleGroup_areYouSure' => 'Tem certeza que deseja excluir o grupo de regras intitulado ":title"?',
- 'budget_areYouSure' => 'Tem certeza que deseja excluir o orçamento chamado ":name"?',
- 'category_areYouSure' => 'Tem certeza que deseja excluir a categoria com o nome ":name"?',
- 'currency_areYouSure' => 'Tem certeza que deseja excluir a moeda chamada ":name"?',
- 'piggyBank_areYouSure' => 'Tem certeza que deseja excluir o cofrinho chamado ":name"?',
- 'journal_areYouSure' => 'Tem certeza que deseja excluir a transação descrita ":description"?',
- 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?',
- 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?',
- 'permDeleteWarning' => 'Exclusão de dados do Firely são permanentes e não podem ser desfeitos.',
- 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.',
- 'delete_all_permanently' => 'Delete selected permanently',
- 'update_all_journals' => 'Update these transactions',
- 'also_delete_transactions' => 'A única transação ligada a essa conta será excluÃda também.|Todas as :count transações ligadas a esta conta serão excluÃdas também.',
- 'also_delete_rules' => 'A única regra que ligado a este grupo de regras será excluÃda também.|Todos as :count regras ligadas a este grupo de regras serão excluÃdas também.',
- 'also_delete_piggyBanks' => 'O único cofrinho conectado a essa conta será excluÃdo também.|Todos os :count cofrinhos conectados a esta conta serão excluÃdos também.',
- 'bill_keep_transactions' => 'A única transação a esta conta não será excluÃda.|Todos as :count transações conectadas a esta fatura não serão excluÃdos.',
- 'budget_keep_transactions' => 'A única transação conectada a este orçamento não será excluÃda.|Todos :count transações ligadas a este orçamento não serão excluÃdos.',
- 'category_keep_transactions' => 'A única transação ligada a esta categoria não será excluÃda.|Todos :count transações ligadas a esta categoria não serão excluÃdos.',
- 'tag_keep_transactions' => 'A única transação ligada a essa marca não será excluÃda.|Todos :count transações ligadas a essa marca não serão excluÃdos.',
+ 'bank_name' => 'Nome do banco',
+ 'bank_balance' => 'Saldo',
+ 'savings_balance' => 'Salda da Poupança',
+ 'credit_card_limit' => 'Limite do Cartão de Crédito',
+ 'automatch' => 'Equivale automaticamente',
+ 'skip' => 'Pular',
+ 'name' => 'Nome',
+ 'active' => 'Ativar',
+ 'amount_min' => 'Valor MÃnimo',
+ 'amount_max' => 'Valor Máximo',
+ 'match' => 'Corresponde em',
+ 'repeat_freq' => 'Repetições',
+ 'journal_currency_id' => 'Currency',
+ 'journal_amount' => 'Amount',
+ 'journal_asset_source_account' => 'Asset account (source)',
+ 'journal_source_account_name' => 'Revenue account (source)',
+ 'journal_source_account_id' => 'Asset account (source)',
+ 'account_from_id' => 'da conta',
+ 'account_to_id' => 'para conta',
+ 'journal_destination_account_id' => 'Asset account (destination)',
+ 'asset_destination_account' => 'Asset account (destination)',
+ 'asset_source_account' => 'Asset account (source)',
+ 'journal_description' => 'Description',
+ 'split_journal' => 'Split this transaction',
+ 'split_journal_explanation' => 'Split this transaction in multiple parts',
+ 'currency' => 'Currency',
+ 'account_id' => 'Conta de ativo',
+ 'budget_id' => 'Orçamento',
+ 'openingBalance' => 'Saldo inicial',
+ 'tagMode' => 'Modo de tag',
+ 'tagPosition' => 'Localização de tag',
+ 'virtualBalance' => 'Saldo virtual',
+ 'longitude_latitude' => 'Localização',
+ 'targetamount' => 'Valor alvo',
+ 'accountRole' => 'Tipo de conta',
+ 'openingBalanceDate' => 'Data do Saldo inicial',
+ 'ccType' => 'Plano de pagamento do Cartão de Crédito',
+ 'ccMonthlyPaymentDate' => 'Data do pagamento mensal do Cartão de Crédito',
+ 'piggy_bank_id' => 'Cofrinho',
+ 'returnHere' => 'Retornar aqui',
+ 'returnHereExplanation' => 'Depois de armazenar, retorne aqui para criar outro.',
+ 'returnHereUpdateExplanation' => 'Depois da atualização, retorne aqui',
+ 'description' => 'Descrição',
+ 'expense_account' => 'Conta de Despesa',
+ 'revenue_account' => 'Conta de Receita',
+ 'amount' => 'Valor',
+ 'date' => 'Data',
+ 'interest_date' => 'Data de interesse',
+ 'book_date' => 'Data reserva',
+ 'process_date' => 'Data de processamento',
+ 'category' => 'Categoria',
+ 'tags' => 'Etiquetas',
+ 'deletePermanently' => 'Apagar permanentemente',
+ 'cancel' => 'Cancelar',
+ 'targetdate' => 'Data Alvo',
+ 'tag' => 'Etiqueta',
+ 'under' => 'Debaixo',
+ 'symbol' => 'SÃmbolo',
+ 'code' => 'Código',
+ 'iban' => 'IBAN',
+ 'accountNumber' => 'Número de conta',
+ 'csv' => 'Arquivo CSV',
+ 'has_headers' => 'Cabeçalhos',
+ 'date_format' => 'Formato da Data',
+ 'csv_config' => 'Importar CSV de configuração',
+ 'specifix' => 'Banco- ou arquivo especÃfico corrigÃdos',
+ 'csv_import_account' => 'Conta de importação padrão',
+ 'csv_delimiter' => 'Delimitador de campo CSV',
+ 'attachments[]' => 'Anexos',
+ 'store_new_withdrawal' => 'Armazenar nova retirada',
+ 'store_new_deposit' => 'Armazenar novo depósito',
+ 'store_new_transfer' => 'Armazenar nova transferência',
+ 'add_new_withdrawal' => 'Adicionar uma nova retirada',
+ 'add_new_deposit' => 'Adicionar um novo depósito',
+ 'add_new_transfer' => 'Adicionar uma nova transferência',
+ 'noPiggybank' => '(nenhum cofrinho)',
+ 'title' => 'TÃtulo',
+ 'notes' => 'Notas',
+ 'filename' => 'Nome do arquivo',
+ 'mime' => 'Tipo do Arquivo (MIME)',
+ 'size' => 'Tamanho',
+ 'trigger' => 'Disparo',
+ 'stop_processing' => 'Parar processamento',
+ 'start_date' => 'InÃcio do intervalo',
+ 'end_date' => 'Final do intervalo',
+ 'export_start_range' => 'InÃcio do intervalo de exportação',
+ 'export_end_range' => 'Fim do intervalo de exportação',
+ 'export_format' => 'Formato do arquivo',
+ 'include_attachments' => 'Incluir anexos enviados',
+ 'include_config' => 'Incluir o arquivo de configuração',
+ 'include_old_uploads' => 'Incluir dados importados',
+ 'accounts' => 'Exportar transações destas contas',
+ 'csv_comma' => 'Uma vÃrgula (,)',
+ 'csv_semicolon' => 'Um ponto e vÃrgula (;)',
+ 'csv_tab' => 'Um Tab (invisÃvel)',
+ 'delete_account' => 'Apagar conta ":name"',
+ 'delete_bill' => 'Apagar fatura ":name"',
+ 'delete_budget' => 'Excluir o orçamento ":name"',
+ 'delete_category' => 'Excluir categoria ":name"',
+ 'delete_currency' => 'Excluir moeda ":moeda"',
+ 'delete_journal' => 'Excluir a transação com a descrição ":description"',
+ 'delete_attachment' => 'Apagar anexo ":name"',
+ 'delete_rule' => 'Excluir regra ":title"',
+ 'delete_rule_group' => 'Exclua o grupo de regras ":title"',
+ 'attachment_areYouSure' => 'Tem certeza que deseja excluir o anexo denominado ":name"?',
+ 'account_areYouSure' => 'Tem certeza que deseja excluir a conta denominada ":name"?',
+ 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?',
+ 'rule_areYouSure' => 'Tem certeza que deseja excluir a regra intitulada ":title"?',
+ 'ruleGroup_areYouSure' => 'Tem certeza que deseja excluir o grupo de regras intitulado ":title"?',
+ 'budget_areYouSure' => 'Tem certeza que deseja excluir o orçamento chamado ":name"?',
+ 'category_areYouSure' => 'Tem certeza que deseja excluir a categoria com o nome ":name"?',
+ 'currency_areYouSure' => 'Tem certeza que deseja excluir a moeda chamada ":name"?',
+ 'piggyBank_areYouSure' => 'Tem certeza que deseja excluir o cofrinho chamado ":name"?',
+ 'journal_areYouSure' => 'Tem certeza que deseja excluir a transação descrita ":description"?',
+ 'mass_journal_are_you_sure' => 'Tem a certeza que pretende apagar estas transações?',
+ 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?',
+ 'permDeleteWarning' => 'Exclusão de dados do Firely são permanentes e não podem ser desfeitos.',
+ 'mass_make_selection' => 'Você ainda pode evitar que itens sejam excluÃdos, removendo a caixa de seleção.',
+ 'delete_all_permanently' => 'Exclua os selecionados permanentemente',
+ 'update_all_journals' => 'Atualizar essas transações',
+ 'also_delete_transactions' => 'A única transação ligada a essa conta será excluÃda também.|Todas as :count transações ligadas a esta conta serão excluÃdas também.',
+ 'also_delete_rules' => 'A única regra que ligado a este grupo de regras será excluÃda também.|Todos as :count regras ligadas a este grupo de regras serão excluÃdas também.',
+ 'also_delete_piggyBanks' => 'O único cofrinho conectado a essa conta será excluÃdo também.|Todos os :count cofrinhos conectados a esta conta serão excluÃdos também.',
+ 'bill_keep_transactions' => 'A única transação a esta conta não será excluÃda.|Todos as :count transações conectadas a esta fatura não serão excluÃdos.',
+ 'budget_keep_transactions' => 'A única transação conectada a este orçamento não será excluÃda.|Todos :count transações ligadas a este orçamento não serão excluÃdos.',
+ 'category_keep_transactions' => 'A única transação ligada a esta categoria não será excluÃda.|Todos :count transações ligadas a esta categoria não serão excluÃdos.',
+ 'tag_keep_transactions' => 'A única transação ligada a essa marca não será excluÃda.|Todos :count transações ligadas a essa marca não serão excluÃdos.',
];
diff --git a/resources/lang/pt_BR/help.php b/resources/lang/pt_BR/help.php
index ed5f833cb9..9f720e032e 100644
--- a/resources/lang/pt_BR/help.php
+++ b/resources/lang/pt_BR/help.php
@@ -11,80 +11,80 @@ return [
// tour!
'main-content-title' => 'Bem Vindo ao Firefly III',
- 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.',
- 'sidebar-toggle-title' => 'Sidebar to create stuff',
- 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!',
- 'account-menu-title' => 'All your accounts',
- 'account-menu-text' => 'Here you can find all the accounts you\'ve made.',
+ 'main-content-text' => 'Faça um favor a você mesmo e siga este pequeno guia para certificar-se de que sabe o caminho de volta.',
+ 'sidebar-toggle-title' => 'Barra lateral para criar coisas',
+ 'sidebar-toggle-text' => 'Escondido sob o Ãcone de adição são todos os botões para criar coisas novas. Contas, transações, tudo!',
+ 'account-menu-title' => 'Todas suas contas',
+ 'account-menu-text' => 'Aqui você pode encontrar todas as contas que você fez.',
'budget-menu-title' => 'Orçamentos',
- 'budget-menu-text' => 'Use this page to organise your finances and limit spending.',
+ 'budget-menu-text' => 'Use esta página para organizar suas finanças e limitar as despesas.',
'report-menu-title' => 'Relatórios',
- 'report-menu-text' => 'Check this out when you want a solid overview of your finances.',
+ 'report-menu-text' => 'Retire isso quando você quiser uma visão geral de suas finanças.',
'transaction-menu-title' => 'Transações',
- 'transaction-menu-text' => 'All transactions you\'ve created can be found here.',
+ 'transaction-menu-text' => 'Todas as transações que você criou podem ser encontradas aqui.',
'option-menu-title' => 'Opções',
- 'option-menu-text' => 'This is pretty self-explanatory.',
+ 'option-menu-text' => 'Isto é bastante auto-explicativo.',
'main-content-end-title' => 'Fim!',
- 'main-content-end-text' => 'Remember that every page has a small question mark at the right top. Click it to get help about the page you\'re on.',
+ 'main-content-end-text' => 'Lembre-se que cada página tem um pequeno ponto de interrogação na parte superior direita. Clique nele para obter ajuda sobre a página que você está.',
'index' => 'Ãndice',
'home' => 'casa',
- 'accounts-index' => 'accounts.index',
- 'accounts-create' => 'accounts.create',
- 'accounts-edit' => 'accounts.edit',
- 'accounts-delete' => 'accounts.delete',
- 'accounts-show' => 'accounts.show',
- 'attachments-edit' => 'attachments.edit',
- 'attachments-delete' => 'attachments.delete',
- 'attachments-show' => 'attachments.show',
- 'attachments-preview' => 'attachments.preview',
- 'bills-index' => 'bills.index',
- 'bills-create' => 'bills.create',
- 'bills-edit' => 'bills.edit',
- 'bills-delete' => 'bills.delete',
- 'bills-show' => 'bills.show',
- 'budgets-index' => 'budgets.index',
- 'budgets-create' => 'budgets.create',
- 'budgets-edit' => 'budgets.edit',
- 'budgets-delete' => 'budgets.delete',
- 'budgets-show' => 'budgets.show',
- 'budgets-noBudget' => 'budgets.noBudget',
- 'categories-index' => 'categories.index',
- 'categories-create' => 'categories.create',
- 'categories-edit' => 'categories.edit',
- 'categories-delete' => 'categories.delete',
- 'categories-show' => 'categories.show',
- 'categories-show-date' => 'categories.show.date',
- 'categories-noCategory' => 'categories.noCategory',
+ 'accounts-index' => 'Contas',
+ 'accounts-create' => 'Criando Contas',
+ 'accounts-edit' => 'Editando Contas',
+ 'accounts-delete' => 'Apagando Contas',
+ 'accounts-show' => 'Detalhes de Contas',
+ 'attachments-edit' => 'Editando Anexos',
+ 'attachments-delete' => 'Apagando Anexos',
+ 'attachments-show' => 'Detalhes de Anexos',
+ 'attachments-preview' => 'Previsualisação de Anexos',
+ 'bills-index' => 'Faturas',
+ 'bills-create' => 'Criando Faturas',
+ 'bills-edit' => 'Editando Faturas',
+ 'bills-delete' => 'Apagando Faturas',
+ 'bills-show' => 'Detalhes da Fatura',
+ 'budgets-index' => 'Orçamentos',
+ 'budgets-create' => 'Criando Orçamentos',
+ 'budgets-edit' => 'Editando Orçamentos',
+ 'budgets-delete' => 'Apagando Orçamentos',
+ 'budgets-show' => 'Detalhes de Orçamentos',
+ 'budgets-noBudget' => 'Sem Orçamentos',
+ 'categories-index' => 'Categorias',
+ 'categories-create' => 'Criando Categorias',
+ 'categories-edit' => 'Editando Categorias',
+ 'categories-delete' => 'Apagando Categorias',
+ 'categories-show' => 'Detalhes de Categorias',
+ 'categories-show-date' => 'Detalhes da Categoria por Data',
+ 'categories-noCategory' => 'Sem Categorias',
'csv-index' => 'Carregar e importar um arquivo CSV',
- 'csv-column-roles' => 'csv.column-roles',
- 'csv-map' => 'csv.map',
- 'csv-download-config-page' => 'csv.download-config-page',
- 'csv-process' => 'csv.process',
- 'currency-index' => 'currency.index',
- 'currency-create' => 'currency.create',
- 'currency-edit' => 'currency.edit',
- 'currency-delete' => 'currency.delete',
- 'new-user-index' => 'new-user.index',
- 'piggy-banks-index' => 'piggy-banks.index',
- 'piggy-banks-create' => 'piggy-banks.create',
- 'piggy-banks-edit' => 'piggy-banks.edit',
- 'piggy-banks-delete' => 'piggy-banks.delete',
- 'piggy-banks-show' => 'piggy-banks.show',
- 'preferences' => 'preferences',
- 'profile' => 'profile',
- 'profile-change-password' => 'profile.change-password',
- 'profile-delete-account' => 'profile.delete-account',
- 'reports-index' => 'reports.index',
- 'reports-report' => 'reports.report',
- 'search' => 'search',
- 'tags-index' => 'tags.index',
- 'tags-create' => 'tags.create',
- 'tags-show' => 'tags.show',
- 'tags-edit' => 'tags.edit',
- 'tags-delete' => 'tags.delete',
- 'transactions-index' => 'transactions.index',
- 'transactions-create' => 'transactions.create',
- 'transactions-edit' => 'transactions.edit',
- 'transactions-delete' => 'transactions.delete',
- 'transactions-show' => 'transactions.show',
+ 'csv-column-roles' => 'CSV Papel da coluna',
+ 'csv-map' => 'Mapeamento CSV',
+ 'csv-download-config-page' => 'Baixando CSV de Configuração',
+ 'csv-process' => 'Processando CSV',
+ 'currency-index' => 'Moedas',
+ 'currency-create' => 'Criando Moedas',
+ 'currency-edit' => 'Editando Moedas',
+ 'currency-delete' => 'Apagando Moedas',
+ 'new-user-index' => 'Novo Usuário',
+ 'piggy-banks-index' => 'Cofrinhos',
+ 'piggy-banks-create' => 'Criando Cofrinhos',
+ 'piggy-banks-edit' => 'Editando Cofrinhos',
+ 'piggy-banks-delete' => 'Apagando Cofrinhos',
+ 'piggy-banks-show' => 'Detalhes do Cofrinho',
+ 'preferences' => 'preferências',
+ 'profile' => 'perfil',
+ 'profile-change-password' => 'Alterando Senha',
+ 'profile-delete-account' => 'Apagando Perfil',
+ 'reports-index' => 'Relatórios',
+ 'reports-report' => 'Relatório',
+ 'search' => 'pesquisa',
+ 'tags-index' => 'Tags',
+ 'tags-create' => 'Criando Tags',
+ 'tags-show' => 'Detalhes da Tag',
+ 'tags-edit' => 'Editando Tags',
+ 'tags-delete' => 'Apagando Tags',
+ 'transactions-index' => 'Transações',
+ 'transactions-create' => 'Criando Transações',
+ 'transactions-edit' => 'Editando Transações',
+ 'transactions-delete' => 'Apagando Transações',
+ 'transactions-show' => 'Detalhes da Transação',
];
diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php
index 38d4450553..21e94af988 100644
--- a/resources/lang/pt_BR/list.php
+++ b/resources/lang/pt_BR/list.php
@@ -22,8 +22,13 @@ return [
'balanceDiff' => 'Saldo diferente entre :start e :end',
'matchedOn' => 'Coincide',
'matchesOn' => 'Correspondido em',
+ 'account_type' => 'Account type',
+ 'new_balance' => 'New balance',
+ 'account' => 'Account',
'matchingAmount' => 'Total',
'lastMatch' => 'Último equivalente',
+ 'split_number' => 'Split #',
+ 'destination' => 'Destination',
'expectedMatch' => 'Equivalente esperado',
'automatch' => 'Auto match?',
'repeat_freq' => 'Repetições',
@@ -34,6 +39,7 @@ return [
'book_date' => 'Data reserva',
'process_date' => 'Data de processamento',
'from' => 'De',
+ 'piggy_bank' => 'Piggy bank',
'to' => 'Até',
'budget' => 'Orçamento',
'category' => 'Categoria',
diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php
index ba12c8e297..f34f08e2d2 100644
--- a/resources/lang/pt_BR/validation.php
+++ b/resources/lang/pt_BR/validation.php
@@ -17,6 +17,7 @@ return [
'file_attached' => 'Arquivo carregado com sucesso ":name".',
'file_invalid_mime' => 'Arquivo ":name" é do tipo ":mime" que não é aceito como um novo upload.',
'file_too_large' => 'Arquivo ":name" é muito grande.',
+ 'belongs_to_user' => 'The value of :attribute is unknown',
'accepted' => 'O campo :attribute deve ser aceito.',
'active_url' => 'O campo :attribute não contém um URL válido.',
'after' => 'O campo :attribute deverá conter uma data posterior a :date.',
diff --git a/resources/views/accounts/index.twig b/resources/views/accounts/index.twig
index a1b0377c75..78c30497da 100644
--- a/resources/views/accounts/index.twig
+++ b/resources/views/accounts/index.twig
@@ -15,13 +15,12 @@
-
+
{{ subTitle }}
-