mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-18 18:44:16 +00:00
Merge branch 'release/4.4.0'
This commit is contained in:
@@ -41,6 +41,8 @@ SHOW_INCOMPLETE_TRANSLATIONS=false
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
GOOGLE_MAPS_API_KEY=
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
|
35
CHANGELOG.md
35
CHANGELOG.md
@@ -2,7 +2,15 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [4.4.0] - 2017-04-23
|
||||
### Added
|
||||
- Firefly III can now handle foreign currencies better, including some code to get the exchange rate live from the web.
|
||||
- Can now make rules for attachments, see #608, as suggested by dzaikos.
|
||||
|
||||
### Fixed
|
||||
- Fixed #629, reported by forcaeluz
|
||||
- Fixed #630, reported by welbert
|
||||
- And more various bug fixes.
|
||||
|
||||
## [4.3.8] - 2017-04-08
|
||||
|
||||
@@ -203,13 +211,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
||||
- Updated all email messages.
|
||||
- Made some fonts local
|
||||
|
||||
|
||||
### Deprecated
|
||||
- Initial release.
|
||||
|
||||
### Removed
|
||||
- Initial release.
|
||||
|
||||
### Fixed
|
||||
- Issue #408
|
||||
- Various issues with split journals
|
||||
@@ -218,11 +219,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
||||
- Issue #422, thx [xzaz](https://github.com/xzaz)
|
||||
- Various import bugs, such as #416 ([zjean](https://github.com/zjean))
|
||||
|
||||
|
||||
### Security
|
||||
- Initial release.
|
||||
|
||||
|
||||
## [4.1.7] - 2016-11-19
|
||||
### Added
|
||||
- Check for database table presence in console commands.
|
||||
@@ -345,15 +341,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
||||
- New Presidents Choice specific to fix #307
|
||||
- Added some trimming (#335)
|
||||
|
||||
### Changed
|
||||
- Initial release.
|
||||
|
||||
### Deprecated
|
||||
- Initial release.
|
||||
|
||||
### Removed
|
||||
- Initial release.
|
||||
|
||||
### Fixed
|
||||
- Fixed a bug where incoming transactions would not be properly filtered in several reports.
|
||||
- #334 by [cyberkov](https://github.com/cyberkov)
|
||||
@@ -361,12 +348,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
||||
- #336
|
||||
- #338 found by [roberthorlings](https://github.com/roberthorlings)
|
||||
|
||||
### Security
|
||||
- Initial release.
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.0.0] - 2015-09-26
|
||||
### Added
|
||||
- Upgraded to Laravel 5.3, most other libraries upgraded as well.
|
||||
|
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ConfigureLogging.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Bootstrap;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Foundation\Bootstrap\ConfigureLogging as IlluminateConfigureLogging;
|
||||
use Illuminate\Log\Writer;
|
||||
|
||||
/**
|
||||
* Class ConfigureLogging
|
||||
*
|
||||
* @package FireflyIII\Bootstrap
|
||||
*/
|
||||
class ConfigureLogging extends IlluminateConfigureLogging
|
||||
{
|
||||
|
||||
/**
|
||||
* Configure the Monolog handlers for the application.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @param \Illuminate\Log\Writer $log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureDailyHandler(Application $app, Writer $log)
|
||||
{
|
||||
$config = $app->make('config');
|
||||
|
||||
$maxFiles = $config->get('app.log_max_files');
|
||||
|
||||
$log->useDailyFiles(
|
||||
$app->storagePath() . '/logs/firefly-iii.log', is_null($maxFiles) ? 5 : $maxFiles,
|
||||
$config->get('app.log_level', 'debug')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the Monolog handlers for the application.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @param \Illuminate\Log\Writer $log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureSingleHandler(Application $app, Writer $log)
|
||||
{
|
||||
$log->useFiles(
|
||||
$app->storagePath() . '/logs/firefly-iii.log',
|
||||
$app->make('config')->get('app.log_level', 'debug')
|
||||
);
|
||||
}
|
||||
}
|
@@ -15,15 +15,22 @@ namespace FireflyIII\Console\Commands;
|
||||
|
||||
|
||||
use DB;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\LimitRepetition;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Schema;
|
||||
|
||||
/**
|
||||
@@ -63,8 +70,14 @@ class UpgradeDatabase extends Command
|
||||
$this->setTransactionIdentifier();
|
||||
$this->migrateRepetitions();
|
||||
$this->repairPiggyBanks();
|
||||
$this->updateAccountCurrencies();
|
||||
$this->updateJournalCurrencies();
|
||||
$this->info('Firefly III database is up to date.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate budget repetitions to new format.
|
||||
*/
|
||||
private function migrateRepetitions()
|
||||
{
|
||||
if (!Schema::hasTable('budget_limits')) {
|
||||
@@ -102,18 +115,20 @@ class UpgradeDatabase extends Command
|
||||
/** @var PiggyBankEvent $event */
|
||||
foreach ($set as $event) {
|
||||
|
||||
if (!is_null($event->transaction_journal_id)) {
|
||||
$type = $event->transactionJournal->transactionType->type;
|
||||
if (is_null($event->transaction_journal_id)) {
|
||||
continue;
|
||||
}
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->transactionJournal()->first();
|
||||
if (is_null($journal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $journal->transactionType->type;
|
||||
if ($type !== TransactionType::TRANSFER) {
|
||||
$event->transaction_journal_id = null;
|
||||
$event->save();
|
||||
$this->line(
|
||||
sprintf('Piggy bank #%d ("%s") was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id,
|
||||
$event->piggyBank->name
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +161,57 @@ class UpgradeDatabase extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function updateAccountCurrencies()
|
||||
{
|
||||
$accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
// get users preference, fall back to system pref.
|
||||
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
|
||||
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||
$accountCurrency = intval($account->getMeta('currency_id'));
|
||||
$openingBalance = $account->getOpeningBalance();
|
||||
$openingBalanceCurrency = intval($openingBalance->transaction_currency_id);
|
||||
|
||||
// both 0? set to default currency:
|
||||
if ($accountCurrency === 0 && $openingBalanceCurrency === 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
continue;
|
||||
}
|
||||
|
||||
// opening balance 0, account not zero? just continue:
|
||||
if ($accountCurrency > 0 && $openingBalanceCurrency === 0) {
|
||||
continue;
|
||||
}
|
||||
// account is set to 0, opening balance is not?
|
||||
if ($accountCurrency === 0 && $openingBalanceCurrency > 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $openingBalanceCurrency]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
continue;
|
||||
}
|
||||
|
||||
// both are equal, just continue:
|
||||
if ($accountCurrency === $openingBalanceCurrency) {
|
||||
continue;
|
||||
}
|
||||
// do not match:
|
||||
if ($accountCurrency !== $openingBalanceCurrency) {
|
||||
// update opening balance:
|
||||
$openingBalance->transaction_currency_id = $accountCurrency;
|
||||
$openingBalance->save();
|
||||
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
|
||||
* which has 0 as an identifier and give it the same identifier.
|
||||
@@ -189,4 +255,83 @@ class UpgradeDatabase extends Command
|
||||
$identifier++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that withdrawals, deposits and transfers have
|
||||
* a currency setting matching their respective accounts
|
||||
*/
|
||||
private function updateJournalCurrencies()
|
||||
{
|
||||
$types = [
|
||||
TransactionType::WITHDRAWAL => '<',
|
||||
TransactionType::DEPOSIT => '>',
|
||||
];
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.';
|
||||
$transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.';
|
||||
|
||||
foreach ($types as $type => $operator) {
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin(
|
||||
'transactions', function (JoinClause $join) use ($operator) {
|
||||
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0');
|
||||
}
|
||||
)
|
||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
|
||||
->where('transaction_types.type', $type)
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'))
|
||||
->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$expectedCurrency = $repository->find(intval($journal->expected_currency_id));
|
||||
$line = sprintf($notification, $type, $journal->id, $journal->transactionCurrency->code, $expectedCurrency->code);
|
||||
|
||||
$journal->setMeta('foreign_amount', $journal->transaction_amount);
|
||||
$journal->setMeta('foreign_currency_id', $journal->transaction_currency_id);
|
||||
$journal->transaction_currency_id = $expectedCurrency->id;
|
||||
$journal->save();
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* For transfers it's slightly different. Both source and destination
|
||||
* must match the respective currency preference. So we must verify ALL
|
||||
* transactions.
|
||||
*/
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->get(['transaction_journals.*']);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$updated = false;
|
||||
/** @var Transaction $sourceTransaction */
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
$sourceCurrency = $repository->find(intval($sourceTransaction->account->getMeta('currency_id')));
|
||||
|
||||
if ($sourceCurrency->id !== $journal->transaction_currency_id) {
|
||||
$updated = true;
|
||||
$journal->transaction_currency_id = $sourceCurrency->id;
|
||||
$journal->save();
|
||||
}
|
||||
|
||||
// destination
|
||||
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
$destinationCurrency = $repository->find(intval($destinationTransaction->account->getMeta('currency_id')));
|
||||
|
||||
if ($destinationCurrency->id !== $journal->transaction_currency_id) {
|
||||
$updated = true;
|
||||
$journal->deleteMeta('foreign_amount');
|
||||
$journal->deleteMeta('foreign_currency_id');
|
||||
$journal->setMeta('foreign_amount', $destinationTransaction->amount);
|
||||
$journal->setMeta('foreign_currency_id', $destinationCurrency->id);
|
||||
}
|
||||
if ($updated) {
|
||||
$line = sprintf($transfer, $journal->id);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -41,7 +41,6 @@ class Kernel extends ConsoleKernel
|
||||
= [
|
||||
'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables',
|
||||
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
|
||||
//'FireflyIII\Bootstrap\ConfigureLogging',
|
||||
'Illuminate\Foundation\Bootstrap\HandleExceptions',
|
||||
'Illuminate\Foundation\Bootstrap\RegisterFacades',
|
||||
'Illuminate\Foundation\Bootstrap\SetRequestForConsole',
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
/**
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Exceptions;
|
||||
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Exceptions;
|
||||
|
||||
use ErrorException;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Exceptions;
|
||||
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Exceptions;
|
||||
|
||||
/**
|
||||
|
@@ -81,6 +81,9 @@ class ChartJsGenerator implements GeneratorInterface
|
||||
if (isset($set['fill'])) {
|
||||
$currentSet['fill'] = $set['fill'];
|
||||
}
|
||||
if (isset($set['currency_symbol'])) {
|
||||
$currentSet['currency_symbol'] = $set['currency_symbol'];
|
||||
}
|
||||
|
||||
$chartData['datasets'][] = $currentSet;
|
||||
}
|
||||
@@ -105,6 +108,10 @@ class ChartJsGenerator implements GeneratorInterface
|
||||
],
|
||||
'labels' => [],
|
||||
];
|
||||
|
||||
// sort by value, keep keys.
|
||||
asort($data);
|
||||
|
||||
$index = 0;
|
||||
foreach ($data as $key => $value) {
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Attachments;
|
||||
|
||||
use Crypt;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collection;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collection;
|
||||
|
||||
use FireflyIII\Models\Account as AccountModel;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collection;
|
||||
|
||||
use FireflyIII\Models\Account as AccountModel;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collection;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collection;
|
||||
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collection;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Collection;
|
||||
|
||||
use FireflyIII\Models\Category as CategoryModel;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Help;
|
||||
|
||||
/**
|
||||
|
@@ -28,6 +28,37 @@ class PopupReport implements PopupReportInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @param $account
|
||||
* @param $attributes
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function balanceDifference($account, $attributes): Collection
|
||||
{
|
||||
// row that displays difference
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector
|
||||
->setAccounts(new Collection([$account]))
|
||||
->setTypes([TransactionType::WITHDRAWAL])
|
||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
||||
->withoutBudget();
|
||||
$journals = $collector->getJournals();
|
||||
|
||||
|
||||
return $journals->filter(
|
||||
function (Transaction $transaction) {
|
||||
$tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count();
|
||||
if ($tags === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param Account $account
|
||||
@@ -165,35 +196,4 @@ class PopupReport implements PopupReportInterface
|
||||
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $account
|
||||
* @param $attributes
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function balanceDifference($account, $attributes): Collection
|
||||
{
|
||||
// row that displays difference
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector
|
||||
->setAccounts(new Collection([$account]))
|
||||
->setTypes([TransactionType::WITHDRAWAL])
|
||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
||||
->withoutBudget();
|
||||
$journals = $collector->getJournals();
|
||||
|
||||
|
||||
return $journals->filter(
|
||||
function (Transaction $transaction) {
|
||||
$tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count();
|
||||
if ($tags === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -22,8 +22,8 @@ use FireflyIII\Http\Requests\AccountFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
@@ -70,7 +70,8 @@ class AccountController extends Controller
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currencies = ExpandedForm::makeSelectList($repository->get());
|
||||
$allCurrencies = $repository->get();
|
||||
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
$subTitle = trans('firefly.make_new_' . $what . '_account');
|
||||
@@ -91,7 +92,7 @@ class AccountController extends Controller
|
||||
$request->session()->flash('gaEventCategory', 'accounts');
|
||||
$request->session()->flash('gaEventAction', 'create-' . $what);
|
||||
|
||||
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencies', 'roles'));
|
||||
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencySelectList', 'allCurrencies', 'roles'));
|
||||
|
||||
}
|
||||
|
||||
@@ -147,13 +148,13 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function edit(Request $request, Account $account)
|
||||
{
|
||||
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
|
||||
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currencies = ExpandedForm::makeSelectList($repository->get());
|
||||
$allCurrencies = $repository->get();
|
||||
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
|
||||
$roles = [];
|
||||
foreach (config('firefly.accountRoles') as $role) {
|
||||
$roles[$role] = strval(trans('firefly.account_role_' . $role));
|
||||
@@ -173,6 +174,7 @@ class AccountController extends Controller
|
||||
$openingBalanceAmount = $account->getOpeningBalanceAmount() === '0' ? '' : $openingBalanceAmount;
|
||||
$openingBalanceDate = $account->getOpeningBalanceDate();
|
||||
$openingBalanceDate = $openingBalanceDate->year === 1900 ? null : $openingBalanceDate->format('Y-m-d');
|
||||
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||
|
||||
$preFilled = [
|
||||
'accountNumber' => $account->getMeta('accountNumber'),
|
||||
@@ -183,13 +185,18 @@ class AccountController extends Controller
|
||||
'openingBalanceDate' => $openingBalanceDate,
|
||||
'openingBalance' => $openingBalanceAmount,
|
||||
'virtualBalance' => $account->virtual_balance,
|
||||
'currency_id' => $account->getMeta('currency_id'),
|
||||
'currency_id' => $currency->id,
|
||||
|
||||
];
|
||||
$request->session()->flash('preFilled', $preFilled);
|
||||
$request->session()->flash('gaEventCategory', 'accounts');
|
||||
$request->session()->flash('gaEventAction', 'edit-' . $what);
|
||||
|
||||
return view('accounts.edit', compact('currencies', 'account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles'));
|
||||
return view(
|
||||
'accounts.edit', compact(
|
||||
'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,6 +249,8 @@ class AccountController extends Controller
|
||||
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
||||
return $this->redirectToOriginalAccount($account);
|
||||
}
|
||||
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
@@ -250,6 +259,7 @@ class AccountController extends Controller
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
|
||||
|
||||
// prep for "all" view.
|
||||
if ($moment === 'all') {
|
||||
@@ -283,7 +293,6 @@ class AccountController extends Controller
|
||||
$periods = $this->getPeriodOverview($account);
|
||||
}
|
||||
|
||||
$accountType = $account->accountType->type;
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
@@ -316,7 +325,8 @@ class AccountController extends Controller
|
||||
|
||||
|
||||
return view(
|
||||
'accounts.show', compact('account', 'moment', 'accountType', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')
|
||||
'accounts.show',
|
||||
compact('account', 'currency', 'moment', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -331,7 +341,6 @@ class AccountController extends Controller
|
||||
{
|
||||
$data = $request->getAccountData();
|
||||
$account = $repository->store($data);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
|
||||
Preferences::mark();
|
||||
|
||||
@@ -409,9 +418,6 @@ class AccountController extends Controller
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
/** @var AccountTaskerInterface $tasker */
|
||||
$tasker = app(AccountTaskerInterface::class);
|
||||
|
||||
$start = $repository->oldestJournalDate($account);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
@@ -429,17 +435,26 @@ class AccountController extends Controller
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// only include asset accounts when this account is an asset:
|
||||
$assets = new Collection;
|
||||
if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) {
|
||||
$assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
||||
}
|
||||
Log::debug('Going to get period expenses and incomes.');
|
||||
while ($end >= $start) {
|
||||
$end = Navigation::startOfPeriod($end, $range);
|
||||
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||
$spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
||||
$earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
||||
|
||||
// try a collector for income:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
|
||||
->setTypes([TransactionType::DEPOSIT])
|
||||
->withOpposingAccount();
|
||||
$earned = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
|
||||
// try a collector for expenses:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
|
||||
->setTypes([TransactionType::WITHDRAWAL])
|
||||
->withOpposingAccount();
|
||||
$spent = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = Navigation::periodShow($end, $range);
|
||||
$entries->push(
|
||||
|
@@ -128,6 +128,8 @@ class UserController extends Controller
|
||||
* @param UserFormRequest $request
|
||||
* @param User $user
|
||||
*
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(UserFormRequest $request, User $user, UserRepositoryInterface $repository)
|
||||
|
@@ -43,6 +43,8 @@ class ForgotPasswordController extends Controller
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
|
||||
|
@@ -112,6 +112,8 @@ class LoginController extends Controller
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showLoginForm(Request $request, CookieJar $cookieJar)
|
||||
|
@@ -192,6 +192,7 @@ class BudgetController extends Controller
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param string $moment
|
||||
*
|
||||
* @return View
|
||||
@@ -310,7 +311,7 @@ class BudgetController extends Controller
|
||||
$journals->setPath('/budgets/show/' . $budget->id);
|
||||
|
||||
|
||||
$subTitle = e($budget->name);
|
||||
$subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
|
||||
|
||||
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
|
||||
}
|
||||
|
@@ -154,6 +154,10 @@ class CategoryController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param string $moment
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
|
||||
@@ -398,7 +402,7 @@ class CategoryController extends Controller
|
||||
// count journals without budget in this period:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount();
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->disableInternalFilter();
|
||||
$count = $collector->getJournals()->count();
|
||||
|
||||
// amount transferred
|
||||
|
@@ -26,6 +26,7 @@ use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
@@ -335,19 +336,13 @@ class AccountController extends Controller
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param string $date
|
||||
* @param Carbon $start
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function period(Account $account, string $date)
|
||||
public function period(Account $account, Carbon $start)
|
||||
{
|
||||
try {
|
||||
$start = new Carbon($date);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
throw new FireflyException('"' . e($date) . '" does not seem to be a valid date. Should be in the format YYYY-MM-DD');
|
||||
}
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
$cache = new CacheProperties();
|
||||
@@ -501,10 +496,15 @@ class AccountController extends Controller
|
||||
}
|
||||
Log::debug('Regenerate chart.account.account-balance-chart from scratch.');
|
||||
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
|
||||
$chartData = [];
|
||||
foreach ($accounts as $account) {
|
||||
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'entries' => [],
|
||||
];
|
||||
$currentStart = clone $start;
|
||||
|
@@ -18,11 +18,14 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Support\Collection;
|
||||
use Navigation;
|
||||
@@ -153,6 +156,144 @@ class BudgetController extends Controller
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param BudgetLimit|null $budgetLimit
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseAsset(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
$cache->addProperty($budgetLimit->id ?? 0);
|
||||
$cache->addProperty('chart.budget.expense-asset');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget);
|
||||
if (!is_null($budgetLimit->id)) {
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
|
||||
}
|
||||
|
||||
$transactions = $collector->getJournals();
|
||||
$result = [];
|
||||
$chartData = [];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$assetId = intval($transaction->account_id);
|
||||
$result[$assetId] = $result[$assetId] ?? '0';
|
||||
$result[$assetId] = bcadd($transaction->transaction_amount, $result[$assetId]);
|
||||
}
|
||||
|
||||
$names = $this->getAccountNames(array_keys($result));
|
||||
foreach ($result as $assetId => $amount) {
|
||||
$chartData[$names[$assetId]] = $amount;
|
||||
}
|
||||
|
||||
$data = $this->generator->pieChart($chartData);
|
||||
$cache->store($data);
|
||||
|
||||
return Response::json($data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param BudgetLimit|null $budgetLimit
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseCategory(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
$cache->addProperty($budgetLimit->id ?? 0);
|
||||
$cache->addProperty('chart.budget.expense-category');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withCategoryInformation();
|
||||
if (!is_null($budgetLimit->id)) {
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
|
||||
}
|
||||
|
||||
$transactions = $collector->getJournals();
|
||||
$result = [];
|
||||
$chartData = [];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$jrnlCatId = intval($transaction->transaction_journal_category_id);
|
||||
$transCatId = intval($transaction->transaction_category_id);
|
||||
$categoryId = max($jrnlCatId, $transCatId);
|
||||
$result[$categoryId] = $result[$categoryId] ?? '0';
|
||||
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
|
||||
}
|
||||
|
||||
$names = $this->getCategoryNames(array_keys($result));
|
||||
foreach ($result as $categoryId => $amount) {
|
||||
$chartData[$names[$categoryId]] = $amount;
|
||||
}
|
||||
|
||||
$data = $this->generator->pieChart($chartData);
|
||||
$cache->store($data);
|
||||
|
||||
return Response::json($data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param BudgetLimit|null $budgetLimit
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseExpense(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
$cache->addProperty($budgetLimit->id ?? 0);
|
||||
$cache->addProperty('chart.budget.expense-expense');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withOpposingAccount();
|
||||
if (!is_null($budgetLimit->id)) {
|
||||
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
|
||||
}
|
||||
|
||||
$transactions = $collector->getJournals();
|
||||
$result = [];
|
||||
$chartData = [];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$opposingId = intval($transaction->opposing_account_id);
|
||||
$result[$opposingId] = $result[$opposingId] ?? '0';
|
||||
$result[$opposingId] = bcadd($transaction->transaction_amount, $result[$opposingId]);
|
||||
}
|
||||
|
||||
$names = $this->getAccountNames(array_keys($result));
|
||||
foreach ($result as $opposingId => $amount) {
|
||||
$name = $names[$opposingId] ?? 'no name';
|
||||
$chartData[$name] = $amount;
|
||||
}
|
||||
|
||||
$data = $this->generator->pieChart($chartData);
|
||||
$cache->store($data);
|
||||
|
||||
return Response::json($data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a budget list with spent/left/overspent.
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
||||
@@ -288,6 +429,28 @@ class BudgetController extends Controller
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $accountIds
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAccountNames(array $accountIds): array
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::EXPENSE]);
|
||||
$grouped = $accounts->groupBy('id')->toArray();
|
||||
$return = [];
|
||||
foreach ($accountIds as $accountId) {
|
||||
if (isset($grouped[$accountId])) {
|
||||
$return[$accountId] = $grouped[$accountId][0]['name'];
|
||||
}
|
||||
}
|
||||
$return[0] = '(no name)';
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param Carbon $start
|
||||
@@ -314,6 +477,30 @@ class BudgetController extends Controller
|
||||
return $budgeted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Small helper function for some of the charts.
|
||||
*
|
||||
* @param array $categoryIds
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getCategoryNames(array $categoryIds): array
|
||||
{
|
||||
/** @var CategoryRepositoryInterface $repository */
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
$categories = $repository->getCategories();
|
||||
$grouped = $categories->groupBy('id')->toArray();
|
||||
$return = [];
|
||||
foreach ($categoryIds as $categoryId) {
|
||||
if (isset($grouped[$categoryId])) {
|
||||
$return[$categoryId] = $grouped[$categoryId][0]['name'];
|
||||
}
|
||||
}
|
||||
$return[0] = trans('firefly.noCategory');
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok.
|
||||
|
@@ -100,9 +100,9 @@ class CategoryController extends Controller
|
||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
||||
$sum = bcadd($spent, $earned);
|
||||
$label = Navigation::periodShow($start, $range);
|
||||
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||
$chartData[1]['entries'][$label] = $earned;
|
||||
$chartData[2]['entries'][$label] = $sum;
|
||||
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||
$start = Navigation::addPeriod($start, $range, 0);
|
||||
}
|
||||
|
||||
@@ -113,21 +113,6 @@ class CategoryController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CategoryRepositoryInterface $repository
|
||||
* @param Category $category
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function currentPeriod(CategoryRepositoryInterface $repository, Category $category)
|
||||
{
|
||||
$start = clone session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$data = $this->makePeriodChart($repository, $category, $start, $end);
|
||||
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CategoryRepositoryInterface $repository
|
||||
* @param AccountRepositoryInterface $accountRepository
|
||||
@@ -215,9 +200,9 @@ class CategoryController extends Controller
|
||||
$spent = $expenses[$category->id]['entries'][$period] ?? '0';
|
||||
$earned = $income[$category->id]['entries'][$period] ?? '0';
|
||||
$sum = bcadd($spent, $earned);
|
||||
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||
$chartData[1]['entries'][$label] = $earned;
|
||||
$chartData[2]['entries'][$label] = $sum;
|
||||
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||
}
|
||||
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
@@ -290,12 +275,11 @@ class CategoryController extends Controller
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function specificPeriod(CategoryRepositoryInterface $repository, Category $category, $date)
|
||||
public function specificPeriod(CategoryRepositoryInterface $repository, Category $category, Carbon $date)
|
||||
{
|
||||
$carbon = new Carbon($date);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($carbon, $range);
|
||||
$end = Navigation::endOfPeriod($carbon, $range);
|
||||
$start = Navigation::startOfPeriod($date, $range);
|
||||
$end = Navigation::endOfPeriod($date, $range);
|
||||
$data = $this->makePeriodChart($repository, $category, $start, $end);
|
||||
|
||||
return Response::json($data);
|
||||
@@ -350,11 +334,11 @@ class CategoryController extends Controller
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
|
||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
|
||||
$sum = bcadd($spent, $earned);
|
||||
$label = Navigation::periodShow($start, '1D');
|
||||
$label = trim(Navigation::periodShow($start, '1D'));
|
||||
|
||||
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||
$chartData[1]['entries'][$label] = $earned;
|
||||
$chartData[2]['entries'][$label] = $sum;
|
||||
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'),12);
|
||||
$chartData[1]['entries'][$label] = round($earned,12);
|
||||
$chartData[2]['entries'][$label] = round($sum,12);
|
||||
|
||||
|
||||
$start->addDay();
|
||||
|
@@ -20,6 +20,7 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Navigation;
|
||||
use Response;
|
||||
use Steam;
|
||||
@@ -103,8 +104,9 @@ class ReportController extends Controller
|
||||
$cache->addProperty($accounts);
|
||||
$cache->addProperty($end);
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
//return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
|
||||
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||
$source = $this->getChartData($accounts, $start, $end);
|
||||
$chartData = [
|
||||
@@ -163,6 +165,8 @@ class ReportController extends Controller
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
|
||||
$source = $this->getChartData($accounts, $start, $end);
|
||||
$numbers = [
|
||||
'sum_earned' => '0',
|
||||
@@ -246,19 +250,41 @@ class ReportController extends Controller
|
||||
$cache->addProperty($accounts);
|
||||
$cache->addProperty($end);
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
// return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
|
||||
$tasker = app(AccountTaskerInterface::class);
|
||||
$currentStart = clone $start;
|
||||
$spentArray = [];
|
||||
$earnedArray = [];
|
||||
|
||||
/** @var AccountTaskerInterface $tasker */
|
||||
$tasker = app(AccountTaskerInterface::class);
|
||||
|
||||
while ($currentStart <= $end) {
|
||||
|
||||
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
|
||||
$earned = strval(
|
||||
array_sum(
|
||||
array_map(
|
||||
function ($item) {
|
||||
return $item['sum'];
|
||||
}, $tasker->getIncomeReport($currentStart, $currentEnd, $accounts)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$spent = strval(
|
||||
array_sum(
|
||||
array_map(
|
||||
function ($item) {
|
||||
return $item['sum'];
|
||||
}, $tasker->getExpenseReport($currentStart, $currentEnd, $accounts)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$label = $currentStart->format('Y-m') . '-01';
|
||||
$spent = $tasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
||||
$earned = $tasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
||||
$spentArray[$label] = bcmul($spent, '-1');
|
||||
$earnedArray[$label] = $earned;
|
||||
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
|
||||
|
@@ -233,7 +233,7 @@ class TagReportController extends Controller
|
||||
}
|
||||
}
|
||||
if (count($newSet) === 0) {
|
||||
$newSet = $chartData;
|
||||
$newSet = $chartData; // @codeCoverageIgnore
|
||||
}
|
||||
$data = $this->generator->multiSet($newSet);
|
||||
$cache->store($data);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Artisan;
|
||||
|
@@ -274,10 +274,10 @@ class ImportController extends Controller
|
||||
* Step 5. Depending on the importer, this will show the user settings to
|
||||
* fill in.
|
||||
*
|
||||
* @param ImportJobRepositoryInterface $repository
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return View
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function settings(ImportJobRepositoryInterface $repository, ImportJob $job)
|
||||
{
|
||||
|
@@ -15,6 +15,7 @@ use Amount;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -29,9 +30,11 @@ use Session;
|
||||
*/
|
||||
class JavascriptController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @param AccountRepositoryInterface $repository
|
||||
* @param CurrencyRepositoryInterface $currencyRepository
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository)
|
||||
{
|
||||
@@ -47,7 +50,7 @@ class JavascriptController extends Controller
|
||||
$accountId = $account->id;
|
||||
$currency = intval($account->getMeta('currency_id'));
|
||||
$currency = $currency === 0 ? $default->id : $currency;
|
||||
$entry = ['preferredCurrency' => $currency];
|
||||
$entry = ['preferredCurrency' => $currency, 'name' => $account->name];
|
||||
$data['accounts'][$accountId] = $entry;
|
||||
}
|
||||
|
||||
@@ -57,6 +60,27 @@ class JavascriptController extends Controller
|
||||
->header('Content-Type', 'text/javascript');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CurrencyRepositoryInterface $repository
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function currencies(CurrencyRepositoryInterface $repository)
|
||||
{
|
||||
$currencies = $repository->get();
|
||||
$data = ['currencies' => [],];
|
||||
/** @var TransactionCurrency $currency */
|
||||
foreach ($currencies as $currency) {
|
||||
$currencyId = $currency->id;
|
||||
$entry = ['name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol];
|
||||
$data['currencies'][$currencyId] = $entry;
|
||||
}
|
||||
|
||||
return response()
|
||||
->view('javascript.currencies', $data, 200)
|
||||
->header('Content-Type', 'text/javascript');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
|
66
app/Http/Controllers/Json/ExchangeController.php
Normal file
66
app/Http/Controllers/Json/ExchangeController.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* ExchangeController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Json;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Services\Currency\ExchangeRateInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* Class ExchangeController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Json
|
||||
*/
|
||||
class ExchangeController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param TransactionCurrency $fromCurrency
|
||||
* @param TransactionCurrency $toCurrency
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getRate(Request $request, TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date)
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date);
|
||||
$amount = null;
|
||||
if (is_null($rate->id)) {
|
||||
Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
|
||||
$preferred = env('EXCHANGE_RATE_SERVICE', config('firefly.preferred_exchange_service'));
|
||||
$class = config('firefly.currency_exchange_services.' . $preferred);
|
||||
/** @var ExchangeRateInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
$rate = $object->getRate($fromCurrency, $toCurrency, $date);
|
||||
}
|
||||
$return = $rate->toArray();
|
||||
$return['amount'] = null;
|
||||
if (!is_null($request->get('amount'))) {
|
||||
// assume amount is in "from" currency:
|
||||
$return['amount'] = bcmul($request->get('amount'), strval($rate->rate), 12);
|
||||
// round to toCurrency decimal places:
|
||||
$return['amount'] = round($return['amount'], $toCurrency->decimal_places);
|
||||
}
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
}
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Amount;
|
||||
@@ -17,6 +18,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
@@ -144,7 +146,7 @@ class JsonController extends Controller
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function boxIn(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository)
|
||||
public function boxIn()
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
@@ -157,9 +159,15 @@ class JsonController extends Controller
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||
$assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$amount = $accountTasker->amountInInPeriod($accounts, $assets, $start, $end);
|
||||
|
||||
// try a collector for income:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||
->setTypes([TransactionType::DEPOSIT])
|
||||
->withOpposingAccount();
|
||||
|
||||
$amount = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
$data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
|
||||
$cache->store($data);
|
||||
|
||||
@@ -172,7 +180,7 @@ class JsonController extends Controller
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function boxOut(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository)
|
||||
public function boxOut()
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
@@ -186,9 +194,13 @@ class JsonController extends Controller
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||
$assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$amount = $accountTasker->amountOutInPeriod($accounts, $assets, $start, $end);
|
||||
// try a collector for expenses:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||
->setTypes([TransactionType::WITHDRAWAL])
|
||||
->withOpposingAccount();
|
||||
$amount = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
|
||||
$data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
|
||||
$cache->store($data);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Amount;
|
||||
@@ -209,10 +210,11 @@ class PiggyBankController extends Controller
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
|
||||
$accounts = [];
|
||||
Log::debug('Looping piggues');
|
||||
/** @var PiggyBank $piggyBank */
|
||||
foreach ($piggyBanks as $piggyBank) {
|
||||
$piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount;
|
||||
$piggyBank->percentage = $piggyBank->savedSoFar != 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
|
||||
$piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0';
|
||||
$piggyBank->percentage = bccomp('0', $piggyBank->savedSoFar) !== 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
|
||||
$piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar));
|
||||
$piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage;
|
||||
|
||||
@@ -220,7 +222,9 @@ class PiggyBankController extends Controller
|
||||
* Fill account information:
|
||||
*/
|
||||
$account = $piggyBank->account;
|
||||
$new = false;
|
||||
if (!isset($accounts[$account->id])) {
|
||||
$new = true;
|
||||
$accounts[$account->id] = [
|
||||
'name' => $account->name,
|
||||
'balance' => Steam::balanceIgnoreVirtual($account, $end),
|
||||
@@ -230,7 +234,7 @@ class PiggyBankController extends Controller
|
||||
'leftToSave' => $piggyBank->leftToSave,
|
||||
];
|
||||
}
|
||||
if (isset($accounts[$account->id])) {
|
||||
if (isset($accounts[$account->id]) && $new === false) {
|
||||
$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);
|
||||
@@ -308,12 +312,7 @@ class PiggyBankController extends Controller
|
||||
return redirect(route('piggy-banks.index'));
|
||||
}
|
||||
|
||||
|
||||
$amount = strval(round($request->get('amount'), 12));
|
||||
$savedSoFar = $piggyBank->currentRelevantRep()->currentamount;
|
||||
|
||||
if (bccomp($amount, $savedSoFar) <= 0) {
|
||||
}
|
||||
|
||||
Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
|
||||
|
||||
|
@@ -19,7 +19,6 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collection\BalanceLine;
|
||||
use FireflyIII\Helpers\Report\PopupReportInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
|
@@ -134,6 +134,8 @@ class PreferencesController extends Controller
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postIndex(Request $request, UserRepositoryInterface $repository)
|
||||
|
@@ -140,18 +140,18 @@ class ProfileController extends Controller
|
||||
return redirect(route('index'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $current
|
||||
* @param string $new
|
||||
* @param string $newConfirmation
|
||||
*
|
||||
* @return bool
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function validatePassword(User $user, string $current, string $new): bool
|
||||
{
|
||||
if (!Hash::check($current, auth()->user()->password)) {
|
||||
if (!Hash::check($current, $user->password)) {
|
||||
throw new ValidationException(strval(trans('firefly.invalid_current_password')));
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,6 @@ use FireflyIII\Models\Category;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Navigation;
|
||||
|
||||
/**
|
||||
|
@@ -19,6 +19,7 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -31,13 +32,14 @@ class OperationsController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @param AccountTaskerInterface $tasker
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function expenses(Collection $accounts, Carbon $start, Carbon $end)
|
||||
public function expenses(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
|
||||
{
|
||||
// chart properties for cache:
|
||||
$cache = new CacheProperties;
|
||||
@@ -48,7 +50,7 @@ class OperationsController extends Controller
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
$entries = $this->getExpenseReport($start, $end, $accounts);
|
||||
$entries = $tasker->getExpenseReport($start, $end, $accounts);
|
||||
$type = 'expense-entry';
|
||||
$result = view('reports.partials.income-expenses', compact('entries', 'type'))->render();
|
||||
$cache->store($result);
|
||||
@@ -58,13 +60,14 @@ class OperationsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountTaskerInterface $tasker
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function income(Collection $accounts, Carbon $start, Carbon $end)
|
||||
public function income(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
|
||||
{
|
||||
// chart properties for cache:
|
||||
$cache = new CacheProperties;
|
||||
@@ -75,7 +78,7 @@ class OperationsController extends Controller
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
$entries = $this->getIncomeReport($start, $end, $accounts);
|
||||
$entries = $tasker->getIncomeReport($start, $end, $accounts);
|
||||
$type = 'income-entry';
|
||||
$result = view('reports.partials.income-expenses', compact('entries', 'type'))->render();
|
||||
|
||||
@@ -86,13 +89,14 @@ class OperationsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountTaskerInterface $tasker
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function operations(Collection $accounts, Carbon $start, Carbon $end)
|
||||
public function operations(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
|
||||
{
|
||||
// chart properties for cache:
|
||||
$cache = new CacheProperties;
|
||||
@@ -104,8 +108,8 @@ class OperationsController extends Controller
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$incomes = $this->getIncomeReport($start, $end, $accounts);
|
||||
$expenses = $this->getExpenseReport($start, $end, $accounts);
|
||||
$incomes = $tasker->getIncomeReport($start, $end, $accounts);
|
||||
$expenses = $tasker->getExpenseReport($start, $end, $accounts);
|
||||
$incomeSum = array_sum(
|
||||
array_map(
|
||||
function ($item) {
|
||||
@@ -129,125 +133,4 @@ class OperationsController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array
|
||||
{
|
||||
// get all expenses for the given accounts in the given period!
|
||||
// also transfers!
|
||||
// get all transactions:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts($accounts)->setRange($start, $end);
|
||||
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||
->withOpposingAccount()
|
||||
->enableInternalFilter();
|
||||
$transactions = $collector->getJournals();
|
||||
$transactions = $transactions->filter(
|
||||
function (Transaction $transaction) {
|
||||
// return negative amounts only.
|
||||
if (bccomp($transaction->transaction_amount, '0') === -1) {
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$expenses = $this->groupByOpposing($transactions);
|
||||
|
||||
// sort the result
|
||||
// Obtain a list of columns
|
||||
$sum = [];
|
||||
foreach ($expenses as $accountId => $row) {
|
||||
$sum[$accountId] = floatval($row['sum']);
|
||||
}
|
||||
|
||||
array_multisort($sum, SORT_ASC, $expenses);
|
||||
|
||||
return $expenses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array
|
||||
{
|
||||
// get all expenses for the given accounts in the given period!
|
||||
// also transfers!
|
||||
// get all transactions:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts($accounts)->setRange($start, $end);
|
||||
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||
->withOpposingAccount()
|
||||
->enableInternalFilter();
|
||||
$transactions = $collector->getJournals();
|
||||
$transactions = $transactions->filter(
|
||||
function (Transaction $transaction) {
|
||||
// return positive amounts only.
|
||||
if (bccomp($transaction->transaction_amount, '0') === 1) {
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$income = $this->groupByOpposing($transactions);
|
||||
|
||||
// sort the result
|
||||
// Obtain a list of columns
|
||||
$sum = [];
|
||||
foreach ($income as $accountId => $row) {
|
||||
$sum[$accountId] = floatval($row['sum']);
|
||||
}
|
||||
|
||||
array_multisort($sum, SORT_DESC, $income);
|
||||
|
||||
return $income;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $transactions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function groupByOpposing(Collection $transactions): array
|
||||
{
|
||||
$expenses = [];
|
||||
// join the result together:
|
||||
foreach ($transactions as $transaction) {
|
||||
$opposingId = $transaction->opposing_account_id;
|
||||
$name = $transaction->opposing_account_name;
|
||||
if (!isset($expenses[$opposingId])) {
|
||||
$expenses[$opposingId] = [
|
||||
'id' => $opposingId,
|
||||
'name' => $name,
|
||||
'sum' => '0',
|
||||
'average' => '0',
|
||||
'count' => 0,
|
||||
];
|
||||
}
|
||||
$expenses[$opposingId]['sum'] = bcadd($expenses[$opposingId]['sum'], $transaction->transaction_amount);
|
||||
$expenses[$opposingId]['count']++;
|
||||
}
|
||||
// do averages:
|
||||
foreach ($expenses as $key => $entry) {
|
||||
if ($expenses[$key]['count'] > 1) {
|
||||
$expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count']));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $expenses;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -262,6 +262,7 @@ class ReportController extends Controller
|
||||
$categories = join(',', $request->getCategoryList()->pluck('id')->toArray());
|
||||
$budgets = join(',', $request->getBudgetList()->pluck('id')->toArray());
|
||||
$tags = join(',', $request->getTagList()->pluck('tag')->toArray());
|
||||
$uri = route('reports.index');
|
||||
|
||||
if ($request->getAccountList()->count() === 0) {
|
||||
Log::debug('Account count is zero');
|
||||
|
@@ -142,6 +142,8 @@ class TagController extends Controller
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @param TagRepositoryInterface $repository
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Tag $tag, TagRepositoryInterface $repository)
|
||||
@@ -241,6 +243,8 @@ class TagController extends Controller
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
|
||||
$sum = '0';
|
||||
|
||||
|
||||
// prep for "all" view.
|
||||
@@ -248,6 +252,7 @@ class TagController extends Controller
|
||||
$subTitle = trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]);
|
||||
$start = $repository->firstUseDate($tag);
|
||||
$end = new Carbon;
|
||||
$sum = $repository->sumOfTag($tag);
|
||||
}
|
||||
|
||||
// prep for "specific date" view.
|
||||
@@ -260,6 +265,7 @@ class TagController extends Controller
|
||||
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$periods = $this->getPeriodOverview($tag);
|
||||
$sum = $repository->sumOfTag($tag, $start, $end);
|
||||
}
|
||||
|
||||
// prep for current period
|
||||
@@ -298,9 +304,8 @@ class TagController extends Controller
|
||||
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
$sum = '0';
|
||||
|
||||
return view('tags.show', compact('tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
|
||||
return view('tags.show', compact('apiKey', 'tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
|
||||
}
|
||||
|
||||
|
||||
|
@@ -25,6 +25,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use Log;
|
||||
@@ -48,7 +49,8 @@ class SingleController extends Controller
|
||||
|
||||
/** @var BudgetRepositoryInterface */
|
||||
private $budgets;
|
||||
|
||||
/** @var CurrencyRepositoryInterface */
|
||||
private $currency;
|
||||
/** @var PiggyBankRepositoryInterface */
|
||||
private $piggyBanks;
|
||||
|
||||
@@ -71,6 +73,7 @@ class SingleController extends Controller
|
||||
$this->budgets = app(BudgetRepositoryInterface::class);
|
||||
$this->piggyBanks = app(PiggyBankRepositoryInterface::class);
|
||||
$this->attachments = app(AttachmentHelperInterface::class);
|
||||
$this->currency = app(CurrencyRepositoryInterface::class);
|
||||
|
||||
View::share('title', trans('firefly.transactions'));
|
||||
View::share('mainTitleIcon', 'fa-repeat');
|
||||
@@ -231,7 +234,6 @@ class SingleController extends Controller
|
||||
// view related code
|
||||
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
|
||||
|
||||
|
||||
// journal related code
|
||||
$sourceAccounts = $journal->sourceAccountList();
|
||||
$destinationAccounts = $journal->destinationAccountList();
|
||||
@@ -249,6 +251,7 @@ class SingleController extends Controller
|
||||
'destination_account_id' => $destinationAccounts->first()->id,
|
||||
'destination_account_name' => $destinationAccounts->first()->edit_name,
|
||||
'amount' => $journal->amountPositive(),
|
||||
'currency' => $journal->transactionCurrency,
|
||||
|
||||
// new custom fields:
|
||||
'due_date' => $journal->dateAsString('due_date'),
|
||||
@@ -256,8 +259,22 @@ class SingleController extends Controller
|
||||
'invoice_date' => $journal->dateAsString('invoice_date'),
|
||||
'interal_reference' => $journal->getMeta('internal_reference'),
|
||||
'notes' => $journal->getMeta('notes'),
|
||||
|
||||
// exchange rate fields
|
||||
'native_amount' => $journal->amountPositive(),
|
||||
'native_currency' => $journal->transactionCurrency,
|
||||
];
|
||||
|
||||
// if user has entered a foreign currency, update some fields
|
||||
$foreignCurrencyId = intval($journal->getMeta('foreign_currency_id'));
|
||||
if ($foreignCurrencyId > 0) {
|
||||
// update some fields in pre-filled.
|
||||
// @codeCoverageIgnoreStart
|
||||
$preFilled['amount'] = $journal->getMeta('foreign_amount');
|
||||
$preFilled['currency'] = $this->currency->find(intval($journal->getMeta('foreign_currency_id')));
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
|
||||
$preFilled['destination_account_name'] = '';
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
@@ -60,6 +61,8 @@ class TransactionController extends Controller
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param string $what
|
||||
*
|
||||
* @param string $moment
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(Request $request, JournalRepositoryInterface $repository, string $what, string $moment = '')
|
||||
@@ -179,8 +182,17 @@ class TransactionController extends Controller
|
||||
$transactions = $tasker->getTransactionsOverview($journal);
|
||||
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
|
||||
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
|
||||
$foreignCurrency = null;
|
||||
|
||||
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
|
||||
if ($journal->hasMeta('foreign_currency_id')) {
|
||||
// @codeCoverageIgnoreStart
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$foreignCurrency = $repository->find(intval($journal->getMeta('foreign_currency_id')));
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'foreignCurrency'));
|
||||
|
||||
|
||||
}
|
||||
|
@@ -43,14 +43,12 @@ class AccountFormRequest extends Request
|
||||
'accountType' => $this->string('what'),
|
||||
'currency_id' => $this->integer('currency_id'),
|
||||
'virtualBalance' => $this->float('virtualBalance'),
|
||||
'virtualBalanceCurrency' => $this->integer('amount_currency_id_virtualBalance'),
|
||||
'iban' => $this->string('iban'),
|
||||
'BIC' => $this->string('BIC'),
|
||||
'accountNumber' => $this->string('accountNumber'),
|
||||
'accountRole' => $this->string('accountRole'),
|
||||
'openingBalance' => $this->float('openingBalance'),
|
||||
'openingBalanceDate' => $this->date('openingBalanceDate'),
|
||||
'openingBalanceCurrency' => $this->integer('amount_currency_id_openingBalance'),
|
||||
'ccType' => $this->string('ccType'),
|
||||
'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'),
|
||||
];
|
||||
|
@@ -67,6 +67,11 @@ class JournalFormRequest extends Request
|
||||
'destination_account_name' => $this->string('destination_account_name'),
|
||||
'piggy_bank_id' => $this->integer('piggy_bank_id'),
|
||||
|
||||
// native amount and stuff like that:
|
||||
'native_amount' => $this->float('native_amount'),
|
||||
'source_amount' => $this->float('source_amount'),
|
||||
'destination_amount' => $this->float('destination_amount'),
|
||||
|
||||
];
|
||||
|
||||
return $data;
|
||||
@@ -101,6 +106,11 @@ class JournalFormRequest extends Request
|
||||
'destination_account_id' => 'numeric|belongsToUser:accounts,id',
|
||||
'destination_account_name' => 'between:1,255',
|
||||
'piggy_bank_id' => 'between:1,255',
|
||||
|
||||
// foreign currency amounts
|
||||
'native_amount' => 'numeric|more:0',
|
||||
'source_amount' => 'numeric|more:0',
|
||||
'destination_amount' => 'numeric|more:0',
|
||||
];
|
||||
|
||||
// some rules get an upgrade depending on the type of data:
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
|
@@ -77,13 +77,13 @@ Breadcrumbs::register(
|
||||
if ($moment === 'all') {
|
||||
$breadcrumbs->push(trans('firefly.everything'), route('accounts.show', [$account->id, 'all']));
|
||||
}
|
||||
// when is specific period:
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
// when is specific period or when empty:
|
||||
if ($moment !== 'all') {
|
||||
$title = trans(
|
||||
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
|
||||
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
|
||||
);
|
||||
$breadcrumbs->push($title, route('accounts.show', [$account->id, $moment]));
|
||||
$breadcrumbs->push($title, route('accounts.show', [$account->id, $moment, $start, $end]));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -258,7 +258,7 @@ Breadcrumbs::register(
|
||||
$breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget', ['all']));
|
||||
}
|
||||
// when is specific period:
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
if ($moment !== 'all') {
|
||||
$title = trans(
|
||||
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
|
||||
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
|
||||
@@ -274,6 +274,7 @@ Breadcrumbs::register(
|
||||
'budgets.show', function (BreadCrumbGenerator $breadcrumbs, Budget $budget) {
|
||||
$breadcrumbs->parent('budgets.index');
|
||||
$breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id]));
|
||||
$breadcrumbs->push(trans('firefly.everything'), route('budgets.show', [$budget->id]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -333,7 +334,7 @@ Breadcrumbs::register(
|
||||
$breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all']));
|
||||
}
|
||||
// when is specific period:
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
if ($moment !== 'all') {
|
||||
$title = trans(
|
||||
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
|
||||
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
|
||||
@@ -354,7 +355,7 @@ Breadcrumbs::register(
|
||||
$breadcrumbs->push(trans('firefly.everything'), route('categories.no-category', ['all']));
|
||||
}
|
||||
// when is specific period:
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
if ($moment !== 'all') {
|
||||
$title = trans(
|
||||
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
|
||||
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
|
||||
@@ -709,23 +710,33 @@ Breadcrumbs::register(
|
||||
|
||||
Breadcrumbs::register(
|
||||
'tags.edit', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) {
|
||||
$breadcrumbs->parent('tags.show', $tag);
|
||||
$breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon);
|
||||
$breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => e($tag->tag)]), route('tags.edit', [$tag->id]));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'tags.delete', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) {
|
||||
$breadcrumbs->parent('tags.show', $tag);
|
||||
$breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon);
|
||||
$breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => e($tag->tag)]), route('tags.delete', [$tag->id]));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Breadcrumbs::register(
|
||||
'tags.show', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) {
|
||||
'tags.show', function (BreadCrumbGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) {
|
||||
$breadcrumbs->parent('tags.index');
|
||||
$breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id]));
|
||||
$breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id], $moment));
|
||||
if ($moment === 'all') {
|
||||
$breadcrumbs->push(trans('firefly.everything'), route('tags.show', [$tag->id], $moment));
|
||||
}
|
||||
if ($moment !== 'all') {
|
||||
$title = trans(
|
||||
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
|
||||
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
|
||||
);
|
||||
$breadcrumbs->push($title, route('tags.show', [$tag->id], $moment));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -743,7 +754,7 @@ Breadcrumbs::register(
|
||||
}
|
||||
|
||||
// when is specific period:
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
if ($moment !== 'all') {
|
||||
$title = trans(
|
||||
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
|
||||
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
|
||||
|
@@ -211,6 +211,26 @@ class Account extends Model
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the opening balance
|
||||
*
|
||||
* @return TransactionJournal
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getOpeningBalance(): TransactionJournal
|
||||
{
|
||||
$journal = TransactionJournal::sortCorrectly()
|
||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $this->id)
|
||||
->transactionTypes([TransactionType::OPENING_BALANCE])
|
||||
->first(['transaction_journals.*']);
|
||||
if (is_null($journal)) {
|
||||
return new TransactionJournal;
|
||||
}
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of the opening balance for this account.
|
||||
*
|
||||
|
53
app/Models/CurrencyExchangeRate.php
Normal file
53
app/Models/CurrencyExchangeRate.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* CurrencyExchange.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Class CurrencyExchange
|
||||
*
|
||||
* @package FireflyIII\Models
|
||||
*/
|
||||
class CurrencyExchangeRate extends Model
|
||||
{
|
||||
|
||||
protected $dates = ['created_at', 'updated_at', 'date'];
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function fromCurrency(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(TransactionCurrency::class, 'from_currency_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function toCurrency(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(TransactionCurrency::class, 'to_currency_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
}
|
@@ -16,6 +16,7 @@ namespace FireflyIII\Models;
|
||||
use Carbon\Carbon;
|
||||
use Crypt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Steam;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@@ -67,7 +68,7 @@ class PiggyBank extends Model
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function account()
|
||||
public function account(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo('FireflyIII\Models\Account');
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
|
||||
|
@@ -43,6 +43,7 @@ use FireflyIII\Support\FireflyConfig;
|
||||
use FireflyIII\Support\Navigation;
|
||||
use FireflyIII\Support\Preferences;
|
||||
use FireflyIII\Support\Steam;
|
||||
use FireflyIII\Support\Twig\Account;
|
||||
use FireflyIII\Support\Twig\General;
|
||||
use FireflyIII\Support\Twig\Journal;
|
||||
use FireflyIII\Support\Twig\PiggyBank;
|
||||
@@ -77,6 +78,7 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
Twig::addExtension(new Translation);
|
||||
Twig::addExtension(new Transaction);
|
||||
Twig::addExtension(new Rule);
|
||||
Twig::addExtension(new Account);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -455,6 +455,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
{
|
||||
$amount = $data['openingBalance'];
|
||||
$name = $data['name'];
|
||||
$currencyId = $data['currency_id'];
|
||||
$opposing = $this->storeOpposingAccount($name);
|
||||
$transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
|
||||
/** @var TransactionJournal $journal */
|
||||
@@ -462,7 +463,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'transaction_type_id' => $transactionType->id,
|
||||
'transaction_currency_id' => $data['openingBalanceCurrency'],
|
||||
'transaction_currency_id' => $currencyId,
|
||||
'description' => 'Initial balance for "' . $account->name . '"',
|
||||
'completed' => true,
|
||||
'date' => $data['openingBalanceDate'],
|
||||
@@ -530,12 +531,8 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
}
|
||||
// opening balance data? update it!
|
||||
if (!is_null($openingBalance->id)) {
|
||||
$date = $data['openingBalanceDate'];
|
||||
$amount = $data['openingBalance'];
|
||||
|
||||
Log::debug('Opening balance journal found, update journal.');
|
||||
|
||||
$this->updateOpeningBalanceJournal($account, $openingBalance, $date, $amount);
|
||||
$this->updateOpeningBalanceJournal($account, $openingBalance, $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -589,15 +586,19 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param TransactionJournal $journal
|
||||
* @param Carbon $date
|
||||
* @param float $amount
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, Carbon $date, float $amount): bool
|
||||
protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, array $data): bool
|
||||
{
|
||||
$date = $data['openingBalanceDate'];
|
||||
$amount = $data['openingBalance'];
|
||||
$currencyId = intval($data['currency_id']);
|
||||
|
||||
// update date:
|
||||
$journal->date = $date;
|
||||
$journal->transaction_currency_id = $currencyId;
|
||||
$journal->save();
|
||||
// update transactions:
|
||||
/** @var Transaction $transaction */
|
||||
|
@@ -137,4 +137,12 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function store(array $data): Account;
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param array $data
|
||||
*
|
||||
* @return Account
|
||||
*/
|
||||
public function update(Account $account, array $data): Account;
|
||||
|
||||
}
|
||||
|
@@ -14,9 +14,10 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII\Repositories\Account;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Steam;
|
||||
@@ -31,64 +32,6 @@ class AccountTasker implements AccountTaskerInterface
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @see self::amountInPeriod
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Collection $excluded
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function amountInInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string
|
||||
{
|
||||
$idList = [
|
||||
'accounts' => $accounts->pluck('id')->toArray(),
|
||||
'exclude' => $excluded->pluck('id')->toArray(),
|
||||
];
|
||||
|
||||
Log::debug(
|
||||
'Now calling amountInInPeriod.',
|
||||
['accounts' => $idList['accounts'], 'excluded' => $idList['exclude'],
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $end->format('Y-m-d'),
|
||||
]
|
||||
);
|
||||
|
||||
return $this->amountInPeriod($idList, $start, $end, true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see self::amountInPeriod
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Collection $excluded
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function amountOutInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string
|
||||
{
|
||||
$idList = [
|
||||
'accounts' => $accounts->pluck('id')->toArray(),
|
||||
'exclude' => $excluded->pluck('id')->toArray(),
|
||||
];
|
||||
|
||||
Log::debug(
|
||||
'Now calling amountOutInPeriod.',
|
||||
['accounts' => $idList['accounts'], 'excluded' => $idList['exclude'],
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $end->format('Y-m-d'),
|
||||
]
|
||||
);
|
||||
|
||||
return $this->amountInPeriod($idList, $start, $end, false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
@@ -147,6 +90,92 @@ class AccountTasker implements AccountTaskerInterface
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array
|
||||
{
|
||||
// get all expenses for the given accounts in the given period!
|
||||
// also transfers!
|
||||
// get all transactions:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts($accounts)->setRange($start, $end);
|
||||
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||
->withOpposingAccount()
|
||||
->enableInternalFilter();
|
||||
$transactions = $collector->getJournals();
|
||||
$transactions = $transactions->filter(
|
||||
function (Transaction $transaction) {
|
||||
// return negative amounts only.
|
||||
if (bccomp($transaction->transaction_amount, '0') === -1) {
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$expenses = $this->groupByOpposing($transactions);
|
||||
|
||||
// sort the result
|
||||
// Obtain a list of columns
|
||||
$sum = [];
|
||||
foreach ($expenses as $accountId => $row) {
|
||||
$sum[$accountId] = floatval($row['sum']);
|
||||
}
|
||||
|
||||
array_multisort($sum, SORT_ASC, $expenses);
|
||||
|
||||
return $expenses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array
|
||||
{
|
||||
// get all expenses for the given accounts in the given period!
|
||||
// also transfers!
|
||||
// get all transactions:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts($accounts)->setRange($start, $end);
|
||||
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||
->withOpposingAccount()
|
||||
->enableInternalFilter();
|
||||
$transactions = $collector->getJournals();
|
||||
$transactions = $transactions->filter(
|
||||
function (Transaction $transaction) {
|
||||
// return positive amounts only.
|
||||
if (bccomp($transaction->transaction_amount, '0') === 1) {
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$income = $this->groupByOpposing($transactions);
|
||||
|
||||
// sort the result
|
||||
// Obtain a list of columns
|
||||
$sum = [];
|
||||
foreach ($income as $accountId => $row) {
|
||||
$sum[$accountId] = floatval($row['sum']);
|
||||
}
|
||||
|
||||
array_multisort($sum, SORT_DESC, $income);
|
||||
|
||||
return $income;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
@@ -156,62 +185,37 @@ class AccountTasker implements AccountTaskerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return how much money has been going out (ie. spent) by the given account(s).
|
||||
* Alternatively, will return how much money has been coming in (ie. earned) by the given accounts.
|
||||
* @param Collection $transactions
|
||||
*
|
||||
* Enter $incoming=true for any money coming in (income)
|
||||
* Enter $incoming=false for any money going out (expenses)
|
||||
*
|
||||
* This means any money going out or in. You can also submit accounts to exclude,
|
||||
* so transfers between accounts are not included.
|
||||
*
|
||||
* As a general rule:
|
||||
*
|
||||
* - Asset accounts should return both expenses and earnings. But could return 0.
|
||||
* - Expense accounts (where money is spent) should only return earnings (the account gets money).
|
||||
* - Revenue accounts (where money comes from) should only return expenses (they spend money).
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param array $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param bool $incoming
|
||||
*
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
protected function amountInPeriod(array $accounts, Carbon $start, Carbon $end, bool $incoming): string
|
||||
private function groupByOpposing(Collection $transactions): array
|
||||
{
|
||||
$joinModifier = $incoming ? '<' : '>';
|
||||
$selection = $incoming ? '>' : '<';
|
||||
|
||||
$query = Transaction::distinct()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin(
|
||||
'transactions as other_side', function (JoinClause $join) use ($joinModifier) {
|
||||
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0);
|
||||
$expenses = [];
|
||||
// join the result together:
|
||||
foreach ($transactions as $transaction) {
|
||||
$opposingId = $transaction->opposing_account_id;
|
||||
$name = $transaction->opposing_account_name;
|
||||
if (!isset($expenses[$opposingId])) {
|
||||
$expenses[$opposingId] = [
|
||||
'id' => $opposingId,
|
||||
'name' => $name,
|
||||
'sum' => '0',
|
||||
'average' => '0',
|
||||
'count' => 0,
|
||||
];
|
||||
}
|
||||
$expenses[$opposingId]['sum'] = bcadd($expenses[$opposingId]['sum'], $transaction->transaction_amount);
|
||||
$expenses[$opposingId]['count']++;
|
||||
}
|
||||
// do averages:
|
||||
foreach ($expenses as $key => $entry) {
|
||||
if ($expenses[$key]['count'] > 1) {
|
||||
$expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count']));
|
||||
}
|
||||
)
|
||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
->whereNull('transactions.deleted_at')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereIn('transactions.account_id', $accounts['accounts'])
|
||||
->where('transactions.amount', $selection, 0);
|
||||
if (count($accounts['exclude']) > 0) {
|
||||
$query->whereNotIn('other_side.account_id', $accounts['exclude']);
|
||||
}
|
||||
|
||||
$result = $query->get(['transactions.id', 'transactions.amount']);
|
||||
$sum = strval($result->sum('amount'));
|
||||
if (strlen($sum) === 0) {
|
||||
Log::debug('Sum is empty.');
|
||||
$sum = '0';
|
||||
}
|
||||
Log::debug(sprintf('Result is %s', $sum));
|
||||
|
||||
return $sum;
|
||||
return $expenses;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,30 +24,6 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
interface AccountTaskerInterface
|
||||
{
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Collection $excluded
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @see AccountTasker::amountInPeriod()
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function amountInInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string;
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Collection $excluded
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @see AccountTasker::amountInPeriod()
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function amountOutInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string;
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
@@ -57,6 +33,24 @@ interface AccountTaskerInterface
|
||||
*/
|
||||
public function getAccountReport(Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array;
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
|
@@ -14,10 +14,13 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII\Repositories\Currency;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
@@ -178,6 +181,38 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
return $preferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionCurrency $fromCurrency
|
||||
* @param TransactionCurrency $toCurrency
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return CurrencyExchangeRate
|
||||
*/
|
||||
public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate
|
||||
{
|
||||
if ($fromCurrency->id === $toCurrency->id) {
|
||||
$rate = new CurrencyExchangeRate;
|
||||
$rate->rate = 1;
|
||||
$rate->id = 0;
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
$rate = $this->user->currencyExchangeRates()
|
||||
->where('from_currency_id', $fromCurrency->id)
|
||||
->where('to_currency_id', $toCurrency->id)
|
||||
->where('date', $date->format('Y-m-d'))->first();
|
||||
if (!is_null($rate)) {
|
||||
Log::debug(sprintf('Found cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
return new CurrencyExchangeRate;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
|
@@ -14,6 +14,8 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII\Repositories\Currency;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\User;
|
||||
@@ -95,6 +97,15 @@ interface CurrencyRepositoryInterface
|
||||
*/
|
||||
public function getCurrencyByPreference(Preference $preference): TransactionCurrency;
|
||||
|
||||
/**
|
||||
* @param TransactionCurrency $fromCurrency
|
||||
* @param TransactionCurrency $toCurrency
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return CurrencyExchangeRate
|
||||
*/
|
||||
public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
|
@@ -41,7 +41,10 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
private $user;
|
||||
|
||||
/** @var array */
|
||||
private $validMetaFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes'];
|
||||
private $validMetaFields
|
||||
= ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes', 'foreign_amount',
|
||||
'foreign_currency_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
@@ -165,12 +168,17 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
public function store(array $data): TransactionJournal
|
||||
{
|
||||
// find transaction type.
|
||||
/** @var TransactionType $transactionType */
|
||||
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
|
||||
$accounts = $this->storeAccounts($transactionType, $data);
|
||||
$data = $this->verifyNativeAmount($data, $accounts);
|
||||
$currencyId = $data['currency_id'];
|
||||
$amount = strval($data['amount']);
|
||||
$journal = new TransactionJournal(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'transaction_type_id' => $transactionType->id,
|
||||
'transaction_currency_id' => $data['currency_id'],
|
||||
'transaction_currency_id' => $currencyId,
|
||||
'description' => $data['description'],
|
||||
'completed' => 0,
|
||||
'date' => $data['date'],
|
||||
@@ -181,13 +189,13 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
// store stuff:
|
||||
$this->storeCategoryWithJournal($journal, $data['category']);
|
||||
$this->storeBudgetWithJournal($journal, $data['budget_id']);
|
||||
$accounts = $this->storeAccounts($transactionType, $data);
|
||||
|
||||
|
||||
// store two transactions:
|
||||
$one = [
|
||||
'journal' => $journal,
|
||||
'account' => $accounts['source'],
|
||||
'amount' => bcmul(strval($data['amount']), '-1'),
|
||||
'amount' => bcmul($amount, '-1'),
|
||||
'description' => null,
|
||||
'category' => null,
|
||||
'budget' => null,
|
||||
@@ -198,7 +206,7 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
$two = [
|
||||
'journal' => $journal,
|
||||
'account' => $accounts['destination'],
|
||||
'amount' => $data['amount'],
|
||||
'amount' => $amount,
|
||||
'description' => null,
|
||||
'category' => null,
|
||||
'budget' => null,
|
||||
@@ -236,10 +244,22 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
*/
|
||||
public function update(TransactionJournal $journal, array $data): TransactionJournal
|
||||
{
|
||||
|
||||
// update actual journal:
|
||||
$journal->transaction_currency_id = $data['currency_id'];
|
||||
$journal->description = $data['description'];
|
||||
$journal->date = $data['date'];
|
||||
$accounts = $this->storeAccounts($journal->transactionType, $data);
|
||||
$amount = strval($data['amount']);
|
||||
|
||||
if ($data['currency_id'] !== $journal->transaction_currency_id) {
|
||||
// user has entered amount in foreign currency.
|
||||
// amount in "our" currency is $data['exchanged_amount']:
|
||||
$amount = strval($data['exchanged_amount']);
|
||||
// other values must be stored as well:
|
||||
$data['original_amount'] = $data['amount'];
|
||||
$data['original_currency_id'] = $data['currency_id'];
|
||||
|
||||
}
|
||||
|
||||
// unlink all categories, recreate them:
|
||||
$journal->categories()->detach();
|
||||
@@ -247,12 +267,9 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
|
||||
$this->storeCategoryWithJournal($journal, $data['category']);
|
||||
$this->storeBudgetWithJournal($journal, $data['budget_id']);
|
||||
$accounts = $this->storeAccounts($journal->transactionType, $data);
|
||||
|
||||
$sourceAmount = bcmul(strval($data['amount']), '-1');
|
||||
$this->updateSourceTransaction($journal, $accounts['source'], $sourceAmount); // negative because source loses money.
|
||||
|
||||
$amount = strval($data['amount']);
|
||||
$this->updateSourceTransaction($journal, $accounts['source'], bcmul($amount, '-1')); // negative because source loses money.
|
||||
$this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money.
|
||||
|
||||
$journal->save();
|
||||
@@ -745,4 +762,56 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the data array and the given accounts to verify that the native amount, currency
|
||||
* and possible the foreign currency and amount are properly saved.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $accounts
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function verifyNativeAmount(array $data, array $accounts): array
|
||||
{
|
||||
/** @var TransactionType $transactionType */
|
||||
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
|
||||
$submittedCurrencyId = $data['currency_id'];
|
||||
|
||||
// which account to check for what the native currency is?
|
||||
$check = 'source';
|
||||
if ($transactionType->type === TransactionType::DEPOSIT) {
|
||||
$check = 'destination';
|
||||
}
|
||||
switch ($transactionType->type) {
|
||||
case TransactionType::DEPOSIT:
|
||||
case TransactionType::WITHDRAWAL:
|
||||
// continue:
|
||||
$nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id'));
|
||||
|
||||
// does not match? Then user has submitted amount in a foreign currency:
|
||||
if ($nativeCurrencyId !== $submittedCurrencyId) {
|
||||
// store amount and submitted currency in "foreign currency" fields:
|
||||
$data['foreign_amount'] = $data['amount'];
|
||||
$data['foreign_currency_id'] = $submittedCurrencyId;
|
||||
|
||||
// overrule the amount and currency ID fields to be the original again:
|
||||
$data['amount'] = strval($data['native_amount']);
|
||||
$data['currency_id'] = $nativeCurrencyId;
|
||||
}
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
// source gets the original amount.
|
||||
$data['amount'] = strval($data['source_amount']);
|
||||
$data['currency_id'] = intval($accounts['source']->getMeta('currency_id'));
|
||||
$data['foreign_amount'] = strval($data['destination_amount']);
|
||||
$data['foreign_currency_id'] = intval($accounts['destination']->getMeta('currency_id'));
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type));
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@@ -246,6 +246,28 @@ class TagRepository implements TagRepositoryInterface
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon|null $start
|
||||
* @param Carbon|null $end
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string
|
||||
{
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
|
||||
if (!is_null($start) && !is_null($end)) {
|
||||
$collector->setRange($start, $end);
|
||||
}
|
||||
|
||||
$collector->setAllAssetAccounts()->setTag($tag);
|
||||
$sum = $collector->getJournals()->sum('transaction_amount');
|
||||
|
||||
return strval($sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a tag become an advance payment?
|
||||
*
|
||||
|
@@ -126,6 +126,15 @@ interface TagRepositoryInterface
|
||||
*/
|
||||
public function store(array $data): Tag;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon|null $start
|
||||
* @param Carbon|null $end
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
|
61
app/Rules/Triggers/HasAttachment.php
Normal file
61
app/Rules/Triggers/HasAttachment.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* HasAttachment.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Triggers;
|
||||
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
|
||||
class HasAttachment extends AbstractTrigger implements TriggerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A trigger is said to "match anything", or match any given transaction,
|
||||
* when the trigger value is very vague or has no restrictions. Easy examples
|
||||
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
|
||||
* has an amount of more than zero! Other examples are all the "Description"-triggers
|
||||
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
|
||||
*
|
||||
* If the user tries to create such a trigger, this method MUST return true so Firefly III
|
||||
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
|
||||
* (even if it will still include 99.9% of the users transactions), this method MUST return
|
||||
* false.
|
||||
*
|
||||
* @param null $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function willMatchEverything($value = null)
|
||||
{
|
||||
$value = intval($value);
|
||||
if ($value < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function triggered(TransactionJournal $journal): bool
|
||||
{
|
||||
$minimum = intval($this->triggerValue);
|
||||
$attachments = $journal->attachments()->count();
|
||||
if ($attachments >= $minimum) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
38
app/Services/Currency/ExchangeRateInterface.php
Normal file
38
app/Services/Currency/ExchangeRateInterface.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* ExchangeRateInterface.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Currency;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\User;
|
||||
|
||||
interface ExchangeRateInterface
|
||||
{
|
||||
/**
|
||||
* @param TransactionCurrency $fromCurrency
|
||||
* @param TransactionCurrency $toCurrency
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return CurrencyExchangeRate
|
||||
*/
|
||||
public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setUser(User $user);
|
||||
|
||||
}
|
69
app/Services/Currency/FixerIO.php
Normal file
69
app/Services/Currency/FixerIO.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* FixerIO.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Currency;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\User;
|
||||
use Log;
|
||||
use Requests;
|
||||
|
||||
/**
|
||||
* Class FixerIO
|
||||
*
|
||||
* @package FireflyIII\Services\Currency
|
||||
*/
|
||||
class FixerIO implements ExchangeRateInterface
|
||||
{
|
||||
/** @var User */
|
||||
protected $user;
|
||||
|
||||
public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate
|
||||
{
|
||||
$uri = sprintf('https://api.fixer.io/%s?base=%s&symbols=%s', $date->format('Y-m-d'), $fromCurrency->code, $toCurrency->code);
|
||||
$result = Requests::get($uri);
|
||||
$rate = 1.0;
|
||||
$content = null;
|
||||
if ($result->status_code !== 200) {
|
||||
Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from FixerIO.', $result->status_code, $result->body));
|
||||
}
|
||||
// get rate from body:
|
||||
if ($result->status_code === 200) {
|
||||
$content = json_decode($result->body, true);
|
||||
}
|
||||
if (!is_null($content)) {
|
||||
$code = $toCurrency->code;
|
||||
$rate = isset($content['rates'][$code]) ? $content['rates'][$code] : '1';
|
||||
}
|
||||
|
||||
// create new currency exchange rate object:
|
||||
$exchangeRate = new CurrencyExchangeRate;
|
||||
$exchangeRate->user()->associate($this->user);
|
||||
$exchangeRate->fromCurrency()->associate($fromCurrency);
|
||||
$exchangeRate->toCurrency()->associate($toCurrency);
|
||||
$exchangeRate->date = $date;
|
||||
$exchangeRate->rate = $rate;
|
||||
$exchangeRate->save();
|
||||
|
||||
return $exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
39
app/Support/Binder/CurrencyCode.php
Normal file
39
app/Support/Binder/CurrencyCode.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* CurrencyCode.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Binder;
|
||||
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class CurrencyCode
|
||||
*
|
||||
* @package FireflyIII\Support\Binder
|
||||
*/
|
||||
class CurrencyCode implements BinderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @param $route
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function routeBinder($value, $route)
|
||||
{
|
||||
$currency = TransactionCurrency::where('code', $value)->first();
|
||||
if (!is_null($currency)) {
|
||||
return $currency;
|
||||
}
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
}
|
@@ -40,6 +40,8 @@ class ExpandedForm
|
||||
*/
|
||||
public function amount(string $name, $value = null, array $options = []): string
|
||||
{
|
||||
$options['min'] = '0.01';
|
||||
|
||||
return $this->currencyField($name, 'amount', $value, $options);
|
||||
}
|
||||
|
||||
@@ -52,6 +54,8 @@ class ExpandedForm
|
||||
*/
|
||||
public function amountSmall(string $name, $value = null, array $options = []): string
|
||||
{
|
||||
$options['min'] = '0.01';
|
||||
|
||||
return $this->currencyField($name, 'amount-small', $value, $options);
|
||||
}
|
||||
|
||||
@@ -261,6 +265,67 @@ class ExpandedForm
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param null $value
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function nonSelectableAmount(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';
|
||||
$selectedCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency();
|
||||
unset($options['currency']);
|
||||
unset($options['placeholder']);
|
||||
|
||||
// make sure value is formatted nicely:
|
||||
if (!is_null($value) && $value !== '') {
|
||||
$value = round($value, $selectedCurrency->decimal_places);
|
||||
}
|
||||
|
||||
|
||||
$html = view('form.non-selectable-amount', compact('selectedCurrency', 'classes', 'name', 'label', 'value', 'options'))->render();
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param null $value
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function nonSelectableBalance(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';
|
||||
$selectedCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency();
|
||||
unset($options['currency']);
|
||||
unset($options['placeholder']);
|
||||
|
||||
// make sure value is formatted nicely:
|
||||
if (!is_null($value) && $value !== '') {
|
||||
$decimals = $selectedCurrency->decimal_places ?? 2;
|
||||
$value = round($value, $decimals);
|
||||
}
|
||||
|
||||
|
||||
$html = view('form.non-selectable-amount', compact('selectedCurrency', 'classes', 'name', 'label', 'value', 'options'))->render();
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
* @param $name
|
||||
|
@@ -64,6 +64,7 @@ class Search implements SearchInterface
|
||||
if (strlen($string) === 0) {
|
||||
return is_string($this->originalQuery) ? $this->originalQuery : '';
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
63
app/Support/Twig/Account.php
Normal file
63
app/Support/Twig/Account.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Account.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Twig;
|
||||
|
||||
|
||||
use FireflyIII\Models\Account as AccountModel;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Facades\Amount as AmountFacade;
|
||||
use Twig_Extension;
|
||||
use Twig_SimpleFunction;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
*
|
||||
* @package FireflyIII\Support\Twig
|
||||
*/
|
||||
class Account extends Twig_Extension
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
$this->formatAmountByAccount(),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return "active" when a part of the route matches the argument.
|
||||
* ie. "accounts" will match "accounts.index".
|
||||
*
|
||||
* @return Twig_SimpleFunction
|
||||
*/
|
||||
protected function formatAmountByAccount(): Twig_SimpleFunction
|
||||
{
|
||||
return new Twig_SimpleFunction(
|
||||
'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string {
|
||||
$currencyId = intval($account->getMeta('currency_id'));
|
||||
if ($currencyId === 0) {
|
||||
// Format using default currency:
|
||||
return AmountFacade::format($amount, $coloured);
|
||||
}
|
||||
$currency = TransactionCurrency::find($currencyId);
|
||||
|
||||
return AmountFacade::formatAnything($currency, $amount, $coloured);
|
||||
}, ['is_safe' => ['html']]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -15,6 +15,7 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII;
|
||||
|
||||
use FireflyIII\Events\RequestedNewPassword;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
@@ -119,6 +120,14 @@ class User extends Authenticatable
|
||||
return $this->hasMany('FireflyIII\Models\Category');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function currencyExchangeRates(): HasMany
|
||||
{
|
||||
return $this->hasMany(CurrencyExchangeRate::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
|
48
composer.lock
generated
48
composer.lock
generated
@@ -665,16 +665,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v5.4.17",
|
||||
"version": "v5.4.19",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "f7675d59e3863a58ecdff1a5ee1dcd0cff788f4b"
|
||||
"reference": "02444b7450350db17a7607c8a52f7268ebdb0dad"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/f7675d59e3863a58ecdff1a5ee1dcd0cff788f4b",
|
||||
"reference": "f7675d59e3863a58ecdff1a5ee1dcd0cff788f4b",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/02444b7450350db17a7607c8a52f7268ebdb0dad",
|
||||
"reference": "02444b7450350db17a7607c8a52f7268ebdb0dad",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -790,7 +790,7 @@
|
||||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2017-04-03T13:07:39+00:00"
|
||||
"time": "2017-04-16T13:33:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravelcollective/html",
|
||||
@@ -974,16 +974,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "1.0.37",
|
||||
"version": "1.0.38",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd"
|
||||
"reference": "4ba6e13f5116204b21c3afdf400ecf2b9eb1c482"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/78b5cc4feb61a882302df4fbaf63b7662e5e4ccd",
|
||||
"reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4ba6e13f5116204b21c3afdf400ecf2b9eb1c482",
|
||||
"reference": "4ba6e13f5116204b21c3afdf400ecf2b9eb1c482",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1005,12 +1005,12 @@
|
||||
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
|
||||
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
|
||||
"league/flysystem-copy": "Allows you to use Copy.com storage",
|
||||
"league/flysystem-dropbox": "Allows you to use Dropbox storage",
|
||||
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
|
||||
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
|
||||
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
|
||||
"league/flysystem-webdav": "Allows you to use WebDAV storage",
|
||||
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
|
||||
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
|
||||
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -1053,7 +1053,7 @@
|
||||
"sftp",
|
||||
"storage"
|
||||
],
|
||||
"time": "2017-03-22T15:43:14+00:00"
|
||||
"time": "2017-04-22T18:59:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
@@ -1583,16 +1583,16 @@
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v5.4.6",
|
||||
"version": "v5.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swiftmailer/swiftmailer.git",
|
||||
"reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e"
|
||||
"reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e",
|
||||
"reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4",
|
||||
"reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1633,7 +1633,7 @@
|
||||
"mail",
|
||||
"mailer"
|
||||
],
|
||||
"time": "2017-02-13T07:52:53+00:00"
|
||||
"time": "2017-04-20T17:32:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
@@ -3161,12 +3161,12 @@
|
||||
"version": "0.9.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/padraic/mockery.git",
|
||||
"url": "https://github.com/mockery/mockery.git",
|
||||
"reference": "6fdb61243844dc924071d3404bb23994ea0b6856"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/padraic/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856",
|
||||
"url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856",
|
||||
"reference": "6fdb61243844dc924071d3404bb23994ea0b6856",
|
||||
"shasum": ""
|
||||
},
|
||||
@@ -3223,16 +3223,16 @@
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe"
|
||||
"reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe",
|
||||
"reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102",
|
||||
"reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3261,7 +3261,7 @@
|
||||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"time": "2017-01-26T22:05:40+00:00"
|
||||
"time": "2017-04-12T18:52:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
|
@@ -23,7 +23,7 @@ return [
|
||||
'is_demo_site' => false,
|
||||
],
|
||||
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
|
||||
'version' => '4.3.8',
|
||||
'version' => '4.4.0',
|
||||
'maxUploadSize' => 5242880,
|
||||
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
|
||||
'list_length' => 10,
|
||||
@@ -134,6 +134,8 @@ return [
|
||||
'category' => 'FireflyIII\Models\Category',
|
||||
'transaction_type' => 'FireflyIII\Models\TransactionType',
|
||||
'currency' => 'FireflyIII\Models\TransactionCurrency',
|
||||
'fromCurrencyCode' => 'FireflyIII\Support\Binder\CurrencyCode',
|
||||
'toCurrencyCode' => 'FireflyIII\Support\Binder\CurrencyCode',
|
||||
'limitrepetition' => 'FireflyIII\Models\LimitRepetition',
|
||||
'budgetlimit' => 'FireflyIII\Models\BudgetLimit',
|
||||
'piggyBank' => 'FireflyIII\Models\PiggyBank',
|
||||
@@ -151,6 +153,7 @@ return [
|
||||
'tagList' => 'FireflyIII\Support\Binder\TagList',
|
||||
'start_date' => 'FireflyIII\Support\Binder\Date',
|
||||
'end_date' => 'FireflyIII\Support\Binder\Date',
|
||||
'date' => 'FireflyIII\Support\Binder\Date',
|
||||
],
|
||||
'rule-triggers' => [
|
||||
'user_action' => 'FireflyIII\Rules\Triggers\UserAction',
|
||||
@@ -173,6 +176,7 @@ return [
|
||||
'category_is' => 'FireflyIII\Rules\Triggers\CategoryIs',
|
||||
'budget_is' => 'FireflyIII\Rules\Triggers\BudgetIs',
|
||||
'tag_is' => 'FireflyIII\Rules\Triggers\TagIs',
|
||||
'has_attachments' => 'FireflyIII\Rules\Triggers\HasAttachment',
|
||||
],
|
||||
'rule-actions' => [
|
||||
'set_category' => 'FireflyIII\Rules\Actions\SetCategory',
|
||||
@@ -207,4 +211,9 @@ return [
|
||||
'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category',
|
||||
'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'],
|
||||
// tag notes has_attachments
|
||||
'currency_exchange_services' => [
|
||||
'fixerio' => 'FireflyIII\Services\Currency\FixerIO',
|
||||
],
|
||||
'preferred_exchange_service' => 'fixerio',
|
||||
|
||||
];
|
||||
|
@@ -159,7 +159,7 @@ return [
|
||||
'ExpandedForm' => [
|
||||
'is_safe' => [
|
||||
'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location',
|
||||
'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password',
|
||||
'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', 'nonSelectableBalance', 'nonSelectableAmount',
|
||||
],
|
||||
],
|
||||
'Form' => [
|
||||
|
@@ -36,11 +36,35 @@ $factory->define(
|
||||
}
|
||||
);
|
||||
|
||||
$factory->define(
|
||||
FireflyIII\Models\CurrencyExchangeRate::class, function (Faker\Generator $faker) {
|
||||
|
||||
return [
|
||||
'user_id' => 1,
|
||||
'from_currency_id' => 1,
|
||||
'to_currency_id' => 2,
|
||||
'date' => '2017-01-01',
|
||||
'rate' => '1.5',
|
||||
'user_rate' => null,
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
$factory->define(
|
||||
FireflyIII\Models\TransactionCurrency::class, function (Faker\Generator $faker) {
|
||||
|
||||
return [
|
||||
'name' => $faker->words(1, true),
|
||||
'code' => 'ABC',
|
||||
'symbol' => 'x',
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
$factory->define(
|
||||
FireflyIII\Models\ImportJob::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'id' => $faker->numberBetween(1, 10),
|
||||
'id' => $faker->numberBetween(1, 100),
|
||||
'user_id' => 1,
|
||||
'key' => $faker->words(1, true),
|
||||
'file_type' => 'csv',
|
||||
@@ -101,7 +125,7 @@ $factory->define(
|
||||
$factory->define(
|
||||
FireflyIII\Models\PiggyBank::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'id' => $faker->numberBetween(1, 10),
|
||||
'id' => $faker->unique()->numberBetween(100, 10000),
|
||||
'account_id' => $faker->numberBetween(1, 10),
|
||||
'name' => $faker->words(3, true),
|
||||
'target_amount' => '1000.00',
|
||||
@@ -116,7 +140,7 @@ $factory->define(
|
||||
$factory->define(
|
||||
FireflyIII\Models\Tag::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'id' => $faker->numberBetween(100, 150),
|
||||
'id' => $faker->unique()->numberBetween(200, 10000),
|
||||
'user_id' => 1,
|
||||
'tagMode' => 'nothing',
|
||||
'tag' => $faker->words(1, true),
|
||||
|
@@ -42,13 +42,6 @@ class ChangesForV431 extends Migration
|
||||
}
|
||||
);
|
||||
|
||||
// change field "start_date" to "startdate"
|
||||
// Schema::table(
|
||||
// 'budget_limits', function (Blueprint $table) {
|
||||
// $table->renameColumn('startdate', 'start_date');
|
||||
// }
|
||||
// );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
58
database/migrations/2017_04_13_163623_changes_for_v440.php
Normal file
58
database/migrations/2017_04_13_163623_changes_for_v440.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
/**
|
||||
* Class ChangesForV440
|
||||
*/
|
||||
class ChangesForV440 extends Migration
|
||||
{
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
if (Schema::hasTable('currency_exchange_rates')) {
|
||||
Schema::drop('currency_exchange_rates');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (!Schema::hasTable('currency_exchange_rates')) {
|
||||
Schema::create(
|
||||
'currency_exchange_rates', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->integer('user_id', false, true);
|
||||
$table->integer('from_currency_id', false, true);
|
||||
$table->integer('to_currency_id', false, true);
|
||||
$table->date('date');
|
||||
$table->decimal('rate', 22, 12);
|
||||
$table->decimal('user_rate', 22, 12)->nullable();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$table->foreign('from_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
|
||||
$table->foreign('to_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
|
||||
}
|
||||
);
|
||||
}
|
||||
//
|
||||
Schema::table(
|
||||
'transactions', function (Blueprint $table) {
|
||||
$table->integer('transaction_currency_id', false, true)->after('description')->nullable();
|
||||
$table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null');
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
51
phpunit.coverage.specific.xml
Executable file
51
phpunit.coverage.specific.xml
Executable file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ phpunit.coverage.specific.xml
|
||||
~ Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
~ This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
~
|
||||
~ See the LICENSE file for details.
|
||||
-->
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="bootstrap/autoload.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
stopOnFailure="true">
|
||||
<testsuites>
|
||||
<testsuite name="Feature Tests">
|
||||
<directory suffix="Test.php">./tests/Feature</directory>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="Unit Tests">
|
||||
<directory suffix="Test.php">./tests/Unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<filter>
|
||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">./app</directory>
|
||||
</whitelist>
|
||||
<blacklist>
|
||||
<directory>vendor/</directory>
|
||||
</blacklist>
|
||||
</filter>
|
||||
<logging>
|
||||
<log type="coverage-clover" target="./storage/build/clover-specific.xml" charset="UTF-8"/>
|
||||
</logging>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="QUEUE_DRIVER" value="sync"/>
|
||||
</php>
|
||||
</phpunit>
|
@@ -32,7 +32,7 @@
|
||||
</blacklist>
|
||||
</filter>
|
||||
<logging>
|
||||
<log type="coverage-clover" target="./storage/build/clover.xml" charset="UTF-8"/>
|
||||
<log type="coverage-clover" target="./storage/build/clover-all.xml" charset="UTF-8"/>
|
||||
</logging>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
|
11
public/css/firefly.css
vendored
11
public/css/firefly.css
vendored
@@ -25,11 +25,6 @@ body.waiting * {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.ui-sortable-placeholder {
|
||||
display: inline-block;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.preferences-box {
|
||||
border: 1px #ddd solid;
|
||||
border-radius: 4px 4px 0 0;
|
||||
@@ -48,12 +43,6 @@ body.waiting * {
|
||||
margin: 20px auto 0 auto;
|
||||
}
|
||||
|
||||
.ff-error-page > .headline {
|
||||
float: left;
|
||||
font-size: 100px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.ff-error-page > .error-content {
|
||||
margin-left: 190px;
|
||||
display: block;
|
||||
|
@@ -6,7 +6,7 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
/** global: Modernizr */
|
||||
/** global: Modernizr, currencies */
|
||||
|
||||
$(document).ready(function () {
|
||||
"use strict";
|
||||
@@ -17,4 +17,14 @@ $(document).ready(function () {
|
||||
}
|
||||
);
|
||||
}
|
||||
// on change currency drop down list:
|
||||
$('#ffInput_currency_id').change(updateCurrencyItems);
|
||||
updateCurrencyItems();
|
||||
|
||||
});
|
||||
|
||||
function updateCurrencyItems() {
|
||||
var value = $('#ffInput_currency_id').val();
|
||||
var symbol = currencies[value];
|
||||
$('.non-selectable-currency-symbol').text(symbol);
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
/** global: Modernizr */
|
||||
/** global: Modernizr, currencies */
|
||||
|
||||
$(document).ready(function () {
|
||||
"use strict";
|
||||
@@ -17,4 +17,14 @@ $(document).ready(function () {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// on change currency drop down list:
|
||||
$('#ffInput_currency_id').change(updateCurrencyItems);
|
||||
|
||||
});
|
||||
|
||||
function updateCurrencyItems() {
|
||||
var value = $('#ffInput_currency_id').val();
|
||||
var symbol = currencies[value];
|
||||
$('.non-selectable-currency-symbol').text(symbol);
|
||||
}
|
||||
|
@@ -15,8 +15,16 @@ $(function () {
|
||||
if (budgetLimitID > 0) {
|
||||
lineChart(budgetChartUri, 'budgetOverview');
|
||||
}
|
||||
if (budgetLimitID == 0) {
|
||||
if (budgetLimitID === 0) {
|
||||
columnChart(budgetChartUri, 'budgetOverview');
|
||||
}
|
||||
|
||||
// other three charts:
|
||||
pieChart(expenseCategoryUri, 'budget-cat-out');
|
||||
pieChart(expenseAssetUri, 'budget-asset-out');
|
||||
pieChart(expenseExpenseUri, 'budget-expense-out');
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
@@ -8,11 +8,15 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
/** global: all, current, specific */
|
||||
/** global: everything, current, specific */
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
columnChart(all, 'all');
|
||||
columnChart(current, 'period');
|
||||
columnChart(specific, 'period-specific-period');
|
||||
|
||||
console.log('Getting charts');
|
||||
columnChart(everything, 'category-everything');
|
||||
|
||||
console.log('Specific: ' + specific);
|
||||
columnChart(specific, 'specific-period');
|
||||
|
||||
});
|
@@ -42,7 +42,8 @@ var defaultChartOptions = {
|
||||
callbacks: {
|
||||
label: function (tooltipItem, data) {
|
||||
"use strict";
|
||||
return data.datasets[tooltipItem.datasetIndex].label + ': ' + accounting.formatMoney(tooltipItem.yLabel);
|
||||
return data.datasets[tooltipItem.datasetIndex].label + ': ' +
|
||||
accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ function doubleYChart(URI, container) {
|
||||
"use strict";
|
||||
|
||||
var colorData = true;
|
||||
var options = defaultChartOptions;
|
||||
var options = $.extend(true, {}, defaultChartOptions);
|
||||
options.scales.yAxes = [
|
||||
// y axis 0:
|
||||
{
|
||||
@@ -141,7 +141,7 @@ function doubleYNonStackedChart(URI, container) {
|
||||
"use strict";
|
||||
|
||||
var colorData = true;
|
||||
var options = defaultChartOptions;
|
||||
var options = $.extend(true, {}, defaultChartOptions);
|
||||
options.scales.yAxes = [
|
||||
// y axis 0:
|
||||
{
|
||||
@@ -186,7 +186,7 @@ function doubleYNonStackedChart(URI, container) {
|
||||
*/
|
||||
function columnChart(URI, container) {
|
||||
"use strict";
|
||||
|
||||
console.log('Going to draw column chart for ' + URI + ' in ' + container);
|
||||
var colorData = true;
|
||||
var options = defaultChartOptions;
|
||||
var chartType = 'bar';
|
||||
|
@@ -13,6 +13,8 @@
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
configAccounting(currencySymbol);
|
||||
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRF-Token': $('meta[name="_token"]').attr('content')
|
||||
@@ -20,7 +22,7 @@ $(function () {
|
||||
});
|
||||
|
||||
// when you click on a currency, this happens:
|
||||
$('.currency-option').click(currencySelect);
|
||||
$('.currency-option').on('click', currencySelect);
|
||||
|
||||
var ranges = {};
|
||||
ranges[dateRangeConfig.currentPeriod] = [moment(dateRangeConfig.ranges.current[0]), moment(dateRangeConfig.ranges.current[1])];
|
||||
@@ -108,10 +110,12 @@ function currencySelect(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function configAccounting(customCurrency) {
|
||||
|
||||
// Settings object that controls default parameters for library methods:
|
||||
accounting.settings = {
|
||||
currency: {
|
||||
symbol: currencySymbol, // default currency symbol is '$'
|
||||
symbol: customCurrency, // default currency symbol is '$'
|
||||
format: accountingConfig, // 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
|
||||
@@ -123,7 +127,7 @@ accounting.settings = {
|
||||
decimal: "."
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function listLengthInitial() {
|
||||
"use strict";
|
||||
|
@@ -62,7 +62,7 @@ function updateBar(data) {
|
||||
|
||||
function reportErrors(data) {
|
||||
"use strict";
|
||||
if (data.errors.length == 1) {
|
||||
if (data.errors.length === 1) {
|
||||
$('#import-status-error-intro').text(langImportSingleError);
|
||||
//'An error has occured during the import. The import can continue, however.'
|
||||
}
|
||||
@@ -93,7 +93,7 @@ function kickStartJob() {
|
||||
|
||||
function updateTimeout(data) {
|
||||
"use strict";
|
||||
if (data.stepsDone != stepCount) {
|
||||
if (data.stepsDone !== stepCount) {
|
||||
stepCount = data.stepsDone;
|
||||
currentLimit = 0;
|
||||
return;
|
||||
|
@@ -14,7 +14,7 @@ $(function () {
|
||||
"use strict";
|
||||
// do chart JS stuff.
|
||||
drawChart();
|
||||
if (showTour == true) {
|
||||
if (showTour === true) {
|
||||
$.getJSON('json/tour').done(function (data) {
|
||||
var tour = new Tour(
|
||||
{
|
||||
|
@@ -99,9 +99,9 @@ function displayAjaxPartial(data, holder) {
|
||||
|
||||
function failAjaxPartial(uri, holder) {
|
||||
"use strict";
|
||||
var holder = $('#' + holder);
|
||||
holder.parent().find('.overlay').remove();
|
||||
holder.addClass('general-chart-error');
|
||||
var holderObject = $('#' + holder);
|
||||
holderObject.parent().find('.overlay').remove();
|
||||
holderObject.addClass('general-chart-error');
|
||||
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,7 @@ $(function () {
|
||||
// set date from cookie
|
||||
var startStr = readCookie('report-start');
|
||||
var endStr = readCookie('report-end');
|
||||
if (startStr !== null && endStr !== null && startStr.length == 8 && endStr.length == 8) {
|
||||
if (startStr !== null && endStr !== null && startStr.length === 8 && endStr.length === 8) {
|
||||
var startDate = moment(startStr, "YYYY-MM-DD");
|
||||
var endDate = moment(endStr, "YYYY-MM-DD");
|
||||
var datePicker = $('#inputDateRange').data('daterangepicker');
|
||||
|
@@ -104,14 +104,14 @@ function addNewAction() {
|
||||
function removeTrigger(e) {
|
||||
"use strict";
|
||||
var target = $(e.target);
|
||||
if (target.prop("tagName") == "I") {
|
||||
if (target.prop("tagName") === "I") {
|
||||
target = target.parent();
|
||||
}
|
||||
// remove grand parent:
|
||||
target.parent().parent().remove();
|
||||
|
||||
// if now at zero, immediatly add one again:
|
||||
if ($('.rule-trigger-tbody tr').length == 0) {
|
||||
if ($('.rule-trigger-tbody tr').length === 0) {
|
||||
addNewTrigger();
|
||||
}
|
||||
return false;
|
||||
@@ -125,14 +125,14 @@ function removeTrigger(e) {
|
||||
function removeAction(e) {
|
||||
"use strict";
|
||||
var target = $(e.target);
|
||||
if (target.prop("tagName") == "I") {
|
||||
if (target.prop("tagName") === "I") {
|
||||
target = target.parent();
|
||||
}
|
||||
// remove grand parent:
|
||||
target.parent().parent().remove();
|
||||
|
||||
// if now at zero, immediatly add one again:
|
||||
if ($('.rule-action-tbody tr').length == 0) {
|
||||
if ($('.rule-action-tbody tr').length === 0) {
|
||||
addNewAction();
|
||||
}
|
||||
return false;
|
||||
|
@@ -71,7 +71,7 @@ function initialize() {
|
||||
/*
|
||||
Maybe place marker?
|
||||
*/
|
||||
if(doPlaceMarker == true) {
|
||||
if (doPlaceMarker === true) {
|
||||
var myLatlng = new google.maps.LatLng(latitude, longitude);
|
||||
var fakeEvent = {};
|
||||
fakeEvent.latLng = myLatlng;
|
||||
|
42
public/js/ff/tags/show.js
Normal file
42
public/js/ff/tags/show.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* show.js
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
/*
|
||||
Some vars as prep for the map:
|
||||
*/
|
||||
var map;
|
||||
var markers = [];
|
||||
var setTag = false;
|
||||
|
||||
var mapOptions = {
|
||||
zoom: zoomLevel,
|
||||
center: new google.maps.LatLng(latitude, longitude),
|
||||
disableDefaultUI: true,
|
||||
zoomControl: false,
|
||||
scaleControl: true,
|
||||
draggable: false
|
||||
};
|
||||
|
||||
|
||||
function initialize() {
|
||||
"use strict";
|
||||
if (doPlaceMarker === true) {
|
||||
/*
|
||||
Create new map:
|
||||
*/
|
||||
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
|
||||
|
||||
var myLatlng = new google.maps.LatLng(latitude, longitude);
|
||||
var marker = new google.maps.Marker({
|
||||
position: myLatlng,
|
||||
map: map
|
||||
});
|
||||
marker.setMap(map);
|
||||
}
|
||||
}
|
||||
google.maps.event.addDomListener(window, 'load', initialize);
|
@@ -72,8 +72,8 @@ function countChecked() {
|
||||
"use strict";
|
||||
var checked = $('.select_all_single:checked').length;
|
||||
if (checked > 0) {
|
||||
$('.mass_edit span').text(edit_selected_txt + ' (' + checked + ')')
|
||||
$('.mass_delete span').text(delete_selected_txt + ' (' + checked + ')')
|
||||
$('.mass_edit span').text(edit_selected_txt + ' (' + checked + ')');
|
||||
$('.mass_delete span').text(delete_selected_txt + ' (' + checked + ')');
|
||||
$('.mass_button_options').show();
|
||||
|
||||
} else {
|
||||
|
206
public/js/ff/transactions/single/common.js
Normal file
206
public/js/ff/transactions/single/common.js
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* common.js
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
$(document).ready(function () {
|
||||
"use strict";
|
||||
setCommonAutocomplete();
|
||||
runModernizer();
|
||||
});
|
||||
|
||||
/**
|
||||
* Give date a datepicker if not natively supported.
|
||||
*/
|
||||
function runModernizer() {
|
||||
if (!Modernizr.inputtypes.date) {
|
||||
$('input[type="date"]').datepicker(
|
||||
{
|
||||
dateFormat: 'yy-mm-dd'
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto complete things in both edit and create routines:
|
||||
*/
|
||||
function setCommonAutocomplete() {
|
||||
$.getJSON('json/tags').done(function (data) {
|
||||
var opt = {
|
||||
typeahead: {
|
||||
source: data,
|
||||
afterSelect: function () {
|
||||
this.$element.val("");
|
||||
}
|
||||
}
|
||||
};
|
||||
$('input[name="tags"]').tagsinput(
|
||||
opt
|
||||
);
|
||||
});
|
||||
|
||||
if ($('input[name="destination_account_name"]').length > 0) {
|
||||
$.getJSON('json/expense-accounts').done(function (data) {
|
||||
$('input[name="destination_account_name"]').typeahead({source: data});
|
||||
});
|
||||
}
|
||||
|
||||
if ($('input[name="source_account_name"]').length > 0) {
|
||||
$.getJSON('json/revenue-accounts').done(function (data) {
|
||||
$('input[name="source_account_name"]').typeahead({source: data});
|
||||
});
|
||||
}
|
||||
|
||||
$.getJSON('json/categories').done(function (data) {
|
||||
$('input[name="category"]').typeahead({source: data});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user changes the currency in the amount drop down, it may jump from being
|
||||
* the native currency to a foreign currency. This triggers the display of several
|
||||
* information things that make sure that the user always supplies the amount in the native currency.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function selectsForeignCurrency() {
|
||||
var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val());
|
||||
var selectedAccountId = getAccountId();
|
||||
var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency);
|
||||
|
||||
if (foreignCurrencyId !== nativeCurrencyId) {
|
||||
console.log('User has selected currency #' + foreignCurrencyId + ' and this is different from native currency #' + nativeCurrencyId);
|
||||
|
||||
// the input where the native amount is entered gets the symbol for the native currency:
|
||||
$('.non-selectable-currency-symbol').text(currencyInfo[nativeCurrencyId].symbol);
|
||||
|
||||
// the instructions get updated:
|
||||
$('#ffInput_exchange_rate_instruction').text(getExchangeInstructions());
|
||||
|
||||
// both holders are shown to the user:
|
||||
$('#exchange_rate_instruction_holder').show();
|
||||
$('#native_amount_holder').show();
|
||||
|
||||
// if possible the amount is already exchanged for the foreign currency
|
||||
convertForeignToNative();
|
||||
|
||||
}
|
||||
if (foreignCurrencyId === nativeCurrencyId) {
|
||||
console.log('User has selected currency #' + foreignCurrencyId + ' and this is equal to native currency #' + nativeCurrencyId + ' (phew).');
|
||||
$('#exchange_rate_instruction_holder').hide();
|
||||
$('#native_amount_holder').hide();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts any foreign amount to the native currency.
|
||||
*/
|
||||
function convertForeignToNative() {
|
||||
var accountId = getAccountId();
|
||||
var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val());
|
||||
var nativeCurrencyId = parseInt(accountInfo[accountId].preferredCurrency);
|
||||
var foreignCurrencyCode = currencyInfo[foreignCurrencyId].code;
|
||||
var nativeCurrencyCode = currencyInfo[nativeCurrencyId].code;
|
||||
var date = $('#ffInput_date').val();
|
||||
var amount = $('#ffInput_amount').val();
|
||||
var uri = 'json/rate/' + foreignCurrencyCode + '/' + nativeCurrencyCode + '/' + date + '?amount=' + amount;
|
||||
console.log('Will grab ' + uri);
|
||||
$.get(uri).done(updateNativeAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the data has been grabbed will update the field in the form.
|
||||
* @param data
|
||||
*/
|
||||
function updateNativeAmount(data) {
|
||||
console.log('Returned data:');
|
||||
console.log(data);
|
||||
$('#ffInput_native_amount').val(data.amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions for transfers
|
||||
*/
|
||||
function getTransferExchangeInstructions() {
|
||||
var sourceAccount = $('select[name="source_account_id"]').val();
|
||||
var destAccount = $('select[name="destination_account_id"]').val();
|
||||
|
||||
var sourceCurrency = accountInfo[sourceAccount].preferredCurrency;
|
||||
var destinationCurrency = accountInfo[destAccount].preferredCurrency;
|
||||
|
||||
return transferInstructions.replace('@source_name', accountInfo[sourceAccount].name)
|
||||
.replace('@dest_name', accountInfo[destAccount].name)
|
||||
.replace(/@source_currency/g, currencyInfo[sourceCurrency].name)
|
||||
.replace(/@dest_currency/g, currencyInfo[destinationCurrency].name);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the transaction to create is a transfer some more checks are necessary.
|
||||
*/
|
||||
function validateCurrencyForTransfer() {
|
||||
if (what !== "transfer") {
|
||||
return;
|
||||
}
|
||||
$('#source_amount_holder').show();
|
||||
var sourceAccount = $('select[name="source_account_id"]').val();
|
||||
var destAccount = $('select[name="destination_account_id"]').val();
|
||||
var sourceCurrency = accountInfo[sourceAccount].preferredCurrency;
|
||||
var sourceSymbol = currencyInfo[sourceCurrency].symbol;
|
||||
var destinationCurrency = accountInfo[destAccount].preferredCurrency;
|
||||
var destinationSymbol = currencyInfo[destinationCurrency].symbol;
|
||||
|
||||
$('#source_amount_holder').show().find('.non-selectable-currency-symbol').text(sourceSymbol);
|
||||
|
||||
if (sourceCurrency === destinationCurrency) {
|
||||
console.log('Both accounts accept ' + sourceCurrency);
|
||||
$('#destination_amount_holder').hide();
|
||||
$('#amount_holder').hide();
|
||||
return;
|
||||
}
|
||||
console.log('Source accepts #' + sourceCurrency + ', destination #' + destinationCurrency);
|
||||
$('#ffInput_exchange_rate_instruction').text(getTransferExchangeInstructions());
|
||||
$('#exchange_rate_instruction_holder').show();
|
||||
$('input[name="source_amount"]').val($('input[name="amount"]').val());
|
||||
convertSourceToDestination();
|
||||
|
||||
$('#destination_amount_holder').show().find('.non-selectable-currency-symbol').text(destinationSymbol);
|
||||
$('#amount_holder').hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from source amount currency to destination currency for transfers.
|
||||
*
|
||||
*/
|
||||
function convertSourceToDestination() {
|
||||
var sourceAccount = $('select[name="source_account_id"]').val();
|
||||
var destAccount = $('select[name="destination_account_id"]').val();
|
||||
|
||||
var sourceCurrency = accountInfo[sourceAccount].preferredCurrency;
|
||||
var destinationCurrency = accountInfo[destAccount].preferredCurrency;
|
||||
|
||||
var sourceCurrencyCode = currencyInfo[sourceCurrency].code;
|
||||
var destinationCurrencyCode = currencyInfo[destinationCurrency].code;
|
||||
|
||||
var date = $('#ffInput_date').val();
|
||||
var amount = $('#ffInput_source_amount').val();
|
||||
$('#ffInput_amount').val(amount);
|
||||
var uri = 'json/rate/' + sourceCurrencyCode + '/' + destinationCurrencyCode + '/' + date + '?amount=' + amount;
|
||||
console.log('Will grab ' + uri);
|
||||
$.get(uri).done(updateDestinationAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the data has been grabbed will update the field (for transfers)
|
||||
* @param data
|
||||
*/
|
||||
function updateDestinationAmount(data) {
|
||||
console.log('Returned data:');
|
||||
console.log(data);
|
||||
$('#ffInput_destination_amount').val(data.amount);
|
||||
}
|
@@ -6,88 +6,85 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
/** global: what,Modernizr, title, breadcrumbs, middleCrumbName, button, piggiesLength, txt, middleCrumbUrl */
|
||||
/** global: what,Modernizr, title, breadcrumbs, middleCrumbName, button, piggiesLength, txt, middleCrumbUrl,exchangeRateInstructions */
|
||||
|
||||
$(document).ready(function () {
|
||||
"use strict";
|
||||
|
||||
// respond to switch buttons
|
||||
// hide ALL exchange things and AMOUNT things
|
||||
$('#exchange_rate_instruction_holder').hide();
|
||||
$('#native_amount_holder').hide();
|
||||
$('#amount_holder').hide();
|
||||
$('#source_amount_holder').hide();
|
||||
$('#destination_amount_holder').hide();
|
||||
|
||||
// respond to switch buttons (first time always triggers)
|
||||
updateButtons();
|
||||
updateForm();
|
||||
updateLayout();
|
||||
updateDescription();
|
||||
updateNativeCurrency();
|
||||
|
||||
if (!Modernizr.inputtypes.date) {
|
||||
$('input[type="date"]').datepicker(
|
||||
{
|
||||
dateFormat: 'yy-mm-dd'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// update currency
|
||||
$('select[name="source_account_id"]').on('change', updateCurrency)
|
||||
// when user changes source account or destination, native currency may be different.
|
||||
$('select[name="source_account_id"]').on('change', updateNativeCurrency);
|
||||
$('select[name="destination_account_id"]').on('change', updateNativeCurrency);
|
||||
|
||||
// get JSON things:
|
||||
getJSONautocomplete();
|
||||
// convert foreign currency to native currency (when input changes, exchange rate)
|
||||
$('#ffInput_amount').on('change', convertForeignToNative);
|
||||
|
||||
// convert source currency to destination currency (slightly different routine for transfers)
|
||||
$('#ffInput_source_amount').on('change', convertSourceToDestination);
|
||||
|
||||
// when user selects different currency,
|
||||
$('.currency-option').on('click', selectsForeignCurrency);
|
||||
});
|
||||
|
||||
/**
|
||||
* This function generates a small helper text to explain the user
|
||||
* that they have selected a foreign currency.
|
||||
* @returns {XML|string|void}
|
||||
*/
|
||||
function getExchangeInstructions() {
|
||||
var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val());
|
||||
var selectedAccountId = getAccountId();
|
||||
var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency);
|
||||
|
||||
function updateCurrency() {
|
||||
// get value:
|
||||
var accountId = $('select[name="source_account_id"]').val();
|
||||
console.log('account id is ' + accountId);
|
||||
var currencyPreference = accountInfo[accountId].preferredCurrency;
|
||||
console.log('currency pref is ' + currencyPreference);
|
||||
var text = exchangeRateInstructions.replace('@name', accountInfo[selectedAccountId].name);
|
||||
text = text.replace(/@native_currency/g, currencyInfo[nativeCurrencyId].name);
|
||||
text = text.replace(/@foreign_currency/g, currencyInfo[foreignCurrencyId].name);
|
||||
return text;
|
||||
}
|
||||
|
||||
$('.currency-option[data-id="' + currencyPreference + '"]').click();
|
||||
/**
|
||||
* There is an input that shows the currency symbol that is native to the selected
|
||||
* acccount. So when the user changes the selected account, the native currency is updated:
|
||||
*/
|
||||
function updateNativeCurrency() {
|
||||
var newAccountId = getAccountId();
|
||||
var nativeCurrencyId = accountInfo[newAccountId].preferredCurrency;
|
||||
|
||||
console.log('User selected account #' + newAccountId + '. Native currency is #' + nativeCurrencyId);
|
||||
|
||||
$('.currency-option[data-id="' + nativeCurrencyId + '"]').click();
|
||||
$('[data-toggle="dropdown"]').parent().removeClass('open');
|
||||
$('select[name="source_account_id"]').focus();
|
||||
|
||||
validateCurrencyForTransfer();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function updateDescription() {
|
||||
$.getJSON('json/transaction-journals/' + what).done(function (data) {
|
||||
$('input[name="description"]').typeahead('destroy').typeahead({source: data});
|
||||
});
|
||||
}
|
||||
|
||||
function getJSONautocomplete() {
|
||||
|
||||
// for withdrawals
|
||||
$.getJSON('json/expense-accounts').done(function (data) {
|
||||
$('input[name="destination_account_name"]').typeahead({source: data});
|
||||
});
|
||||
|
||||
// for tags:
|
||||
if ($('input[name="tags"]').length > 0) {
|
||||
$.getJSON('json/tags').done(function (data) {
|
||||
|
||||
var opt = {
|
||||
typeahead: {
|
||||
source: data,
|
||||
afterSelect: function () {
|
||||
this.$element.val("");
|
||||
}
|
||||
}
|
||||
};
|
||||
$('input[name="tags"]').tagsinput(
|
||||
opt
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// for deposits
|
||||
$.getJSON('json/revenue-accounts').done(function (data) {
|
||||
$('input[name="source_account_name"]').typeahead({source: data});
|
||||
});
|
||||
|
||||
$.getJSON('json/categories').done(function (data) {
|
||||
$('input[name="category"]').typeahead({source: data});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function updateLayout() {
|
||||
"use strict";
|
||||
$('#subTitle').text(title[what]);
|
||||
@@ -96,78 +93,96 @@ function updateLayout() {
|
||||
$('#transaction-btn').text(button[what]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function updateForm() {
|
||||
"use strict";
|
||||
|
||||
$('input[name="what"]').val(what);
|
||||
|
||||
var destName = $('#ffInput_destination_account_name');
|
||||
var srcName = $('#ffInput_source_account_name');
|
||||
|
||||
switch (what) {
|
||||
case 'withdrawal':
|
||||
// show source_id and dest_name:
|
||||
$('#source_account_id_holder').show();
|
||||
$('#destination_account_name_holder').show();
|
||||
// show source_id and dest_name
|
||||
document.getElementById('source_account_id_holder').style.display = 'block';
|
||||
document.getElementById('destination_account_name_holder').style.display = 'block';
|
||||
|
||||
// hide others:
|
||||
$('#source_account_name_holder').hide();
|
||||
$('#destination_account_id_holder').hide();
|
||||
|
||||
// show budget:
|
||||
$('#budget_id_holder').show();
|
||||
document.getElementById('source_account_name_holder').style.display = 'none';
|
||||
document.getElementById('destination_account_id_holder').style.display = 'none';
|
||||
document.getElementById('budget_id_holder').style.display = 'block';
|
||||
|
||||
// hide piggy bank:
|
||||
$('#piggy_bank_id_holder').hide();
|
||||
document.getElementById('piggy_bank_id_holder').style.display = 'none';
|
||||
|
||||
// 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());
|
||||
// copy destination account name to source account name:
|
||||
if (destName.val().length > 0) {
|
||||
srcName.val(destName.val());
|
||||
}
|
||||
|
||||
// exchange / foreign currencies:
|
||||
// hide explanation, hide source and destination amounts, show normal amount
|
||||
document.getElementById('exchange_rate_instruction_holder').style.display = 'none';
|
||||
document.getElementById('source_amount_holder').style.display = 'none';
|
||||
document.getElementById('destination_amount_holder').style.display = 'none';
|
||||
document.getElementById('amount_holder').style.display = 'block';
|
||||
break;
|
||||
case 'deposit':
|
||||
// show source_name and dest_id:
|
||||
$('#source_account_name_holder').show();
|
||||
$('#destination_account_id_holder').show();
|
||||
document.getElementById('source_account_name_holder').style.display = 'block';
|
||||
document.getElementById('destination_account_id_holder').style.display = 'block';
|
||||
|
||||
// hide others:
|
||||
$('#source_account_id_holder').hide();
|
||||
$('#destination_account_name_holder').hide();
|
||||
document.getElementById('source_account_id_holder').style.display = 'none';
|
||||
document.getElementById('destination_account_name_holder').style.display = 'none';
|
||||
|
||||
// hide budget
|
||||
$('#budget_id_holder').hide();
|
||||
document.getElementById('budget_id_holder').style.display = 'none';
|
||||
|
||||
// hide piggy bank
|
||||
$('#piggy_bank_id_holder').hide();
|
||||
document.getElementById('piggy_bank_id_holder').style.display = 'none';
|
||||
|
||||
if ($('#ffInput_source_account_name').val().length > 0) {
|
||||
$('#ffInput_destination_account_name').val($('#ffInput_source_account_name').val());
|
||||
// copy name
|
||||
if (srcName.val().length > 0) {
|
||||
destName.val(srcName.val());
|
||||
}
|
||||
|
||||
// exchange / foreign currencies:
|
||||
// hide explanation, hide source and destination amounts, show amount
|
||||
document.getElementById('exchange_rate_instruction_holder').style.display = 'none';
|
||||
document.getElementById('source_amount_holder').style.display = 'none';
|
||||
document.getElementById('destination_amount_holder').style.display = 'none';
|
||||
document.getElementById('amount_holder').style.display = 'block';
|
||||
break;
|
||||
case 'transfer':
|
||||
// show source_id and dest_id:
|
||||
$('#source_account_id_holder').show();
|
||||
$('#destination_account_id_holder').show();
|
||||
document.getElementById('source_account_id_holder').style.display = 'block';
|
||||
document.getElementById('destination_account_id_holder').style.display = 'block';
|
||||
|
||||
// hide others:
|
||||
$('#source_account_name_holder').hide();
|
||||
$('#destination_account_name_holder').hide();
|
||||
|
||||
document.getElementById('source_account_name_holder').style.display = 'none';
|
||||
document.getElementById('destination_account_name_holder').style.display = 'none';
|
||||
|
||||
// hide budget
|
||||
$('#budget_id_holder').hide();
|
||||
document.getElementById('budget_id_holder').style.display = 'none';
|
||||
|
||||
// optional piggies
|
||||
var showPiggies = 'block';
|
||||
if (piggiesLength === 0) {
|
||||
$('#piggy_bank_id_holder').hide();
|
||||
} else {
|
||||
$('#piggy_bank_id_holder').show();
|
||||
showPiggies = 'none';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// no action.
|
||||
document.getElementById('piggy_bank_id_holder').style.display = showPiggies;
|
||||
break;
|
||||
}
|
||||
updateNativeCurrency();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function updateButtons() {
|
||||
"use strict";
|
||||
$('.switch').each(function (i, v) {
|
||||
@@ -178,7 +193,7 @@ function updateButtons() {
|
||||
// new click event:
|
||||
button.bind('click', clickButton);
|
||||
|
||||
if (button.data('what') == what) {
|
||||
if (button.data('what') === what) {
|
||||
button.removeClass('btn-default').addClass('btn-info').html('<i class="fa fa-fw fa-check"></i> ' + txt[button.data('what')]);
|
||||
} else {
|
||||
button.removeClass('btn-info').addClass('btn-default').text(txt[button.data('what')]);
|
||||
@@ -186,11 +201,16 @@ function updateButtons() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function clickButton(e) {
|
||||
"use strict";
|
||||
var button = $(e.target);
|
||||
var newWhat = button.data('what');
|
||||
if (newWhat != what) {
|
||||
if (newWhat !== what) {
|
||||
what = newWhat;
|
||||
updateButtons();
|
||||
updateForm();
|
||||
@@ -199,3 +219,15 @@ function clickButton(e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accountID based on some meta info.
|
||||
*/
|
||||
function getAccountId() {
|
||||
if (what === "withdrawal") {
|
||||
return $('select[name="source_account_id"]').val();
|
||||
}
|
||||
if (what === "deposit" || what === "transfer") {
|
||||
return $('select[name="destination_account_id"]').val();
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user