mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-01-09 20:11:22 +00:00
Merge branch 'release/4.6.4'
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
language: php
|
||||
php:
|
||||
- 7.0
|
||||
- 7.1
|
||||
|
||||
cache:
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -2,11 +2,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [4.6.4] - 2017-08-13
|
||||
### Added
|
||||
- PHP7.1 support
|
||||
- Routine to decrypt attachments from the command line, for issue #671
|
||||
- A routine that can check if your password has been stolen in the past.
|
||||
- Split transaction shows amount left to be split
|
||||
|
||||
|
||||
### Changed
|
||||
- Importer can (potentially) handle new import routines such as banks.
|
||||
- Importer can fall back from JSON errors
|
||||
|
||||
### Deprecated
|
||||
- Initial release.
|
||||
|
||||
### Removed
|
||||
- PHP7.0 support
|
||||
- Support for extended tag modes
|
||||
- Remove "time jumps" to non-empty periods
|
||||
|
||||
|
||||
### Fixed
|
||||
- #717, reported by @NiceGuyIT
|
||||
- #718, reported by @wtercato
|
||||
- #722, reported by @simonsmiley
|
||||
- #648, reported by @skibbipl
|
||||
- #730, reported by @ragnarkarlsson
|
||||
- #733, reported by @xpfgsyb
|
||||
- #735, reported by @kristophr
|
||||
- #739, reported by @skibbipl
|
||||
- #515, reported by @schwalberich
|
||||
- #743, reported by @simonsmiley
|
||||
- #746, reported by @tannie
|
||||
- #747, reported by @tannie
|
||||
|
||||
### Security
|
||||
- Initial release.
|
||||
|
||||
|
||||
|
||||
## [4.6.3.1] - 2017-07-23
|
||||
### Fixed
|
||||
- Hotfix to close issue #715
|
||||
|
||||
|
||||
## [4.6.3] - 2017-07-23
|
||||
|
||||
This will be the last release to support PHP 7.0.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Firefly III: A personal finances manager
|
||||
|
||||
[](https://secure.php.net/downloads.php) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://creativecommons.org/licenses/by-sa/4.0/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||
[](https://secure.php.net/downloads.php) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://creativecommons.org/licenses/by-sa/4.0/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||
|
||||
[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
|
||||
|
||||
|
||||
104
app/Console/Commands/DecryptAttachment.php
Normal file
104
app/Console/Commands/DecryptAttachment.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* DecryptAttachment.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\Console\Commands;
|
||||
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class DecryptAttachment
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class DecryptAttachment extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Decrypts an attachment and dumps the content in a file in the given directory.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature
|
||||
= 'firefly:decrypt-attachment {id:The ID of the attachment.} {name:The file name of the attachment.}
|
||||
{directory:Where the file must be stored.}';
|
||||
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** @var AttachmentRepositoryInterface $repository */
|
||||
$repository = app(AttachmentRepositoryInterface::class);
|
||||
$attachmentId = intval($this->argument('id'));
|
||||
$attachment = $repository->findWithoutUser($attachmentId);
|
||||
$attachmentName = trim($this->argument('name'));
|
||||
$storagePath = realpath(trim($this->argument('directory')));
|
||||
if (is_null($attachment->id)) {
|
||||
$this->error(sprintf('No attachment with id #%d', $attachmentId));
|
||||
Log::error(sprintf('DecryptAttachment: No attachment with id #%d', $attachmentId));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($attachmentName !== $attachment->filename) {
|
||||
$this->error('File name does not match.');
|
||||
Log::error('DecryptAttachment: File name does not match.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir($storagePath)) {
|
||||
$this->error(sprintf('Path "%s" is not a directory.', $storagePath));
|
||||
Log::error(sprintf('DecryptAttachment: Path "%s" is not a directory.', $storagePath));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_writable($storagePath)) {
|
||||
$this->error(sprintf('Path "%s" is not writable.', $storagePath));
|
||||
Log::error(sprintf('DecryptAttachment: Path "%s" is not writable.', $storagePath));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fullPath = $storagePath . DIRECTORY_SEPARATOR . $attachment->filename;
|
||||
$content = $repository->getContent($attachment);
|
||||
$this->line(sprintf('Going to write content for attachment #%d into file "%s"', $attachment->id, $fullPath));
|
||||
$result = file_put_contents($fullPath, $content);
|
||||
if ($result === false) {
|
||||
$this->error('Could not write to file.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->info(sprintf('%d bytes written. Exiting now..', $result));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* UseEncryption.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.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class UseEncryption
|
||||
*/
|
||||
class UseEncryption extends Command
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console;
|
||||
|
||||
use FireflyIII\Console\Commands\CreateImport;
|
||||
use FireflyIII\Console\Commands\DecryptAttachment;
|
||||
use FireflyIII\Console\Commands\EncryptFile;
|
||||
use FireflyIII\Console\Commands\Import;
|
||||
use FireflyIII\Console\Commands\ScanAttachments;
|
||||
@@ -63,6 +64,7 @@ class Kernel extends ConsoleKernel
|
||||
ScanAttachments::class,
|
||||
UpgradeDatabase::class,
|
||||
UseEncryption::class,
|
||||
DecryptAttachment::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,11 +45,11 @@ final class Entry
|
||||
|
||||
public $transaction_type;
|
||||
|
||||
public $source_account_id;
|
||||
public $source_account_name;
|
||||
public $asset_account_id;
|
||||
public $asset_account_name;
|
||||
|
||||
public $destination_account_id;
|
||||
public $destination_account_name;
|
||||
public $opposing_account_id;
|
||||
public $opposing_account_name;
|
||||
|
||||
public $budget_id;
|
||||
public $budget_name;
|
||||
@@ -71,21 +71,21 @@ final class Entry
|
||||
*/
|
||||
public static function fromObject($object): Entry
|
||||
{
|
||||
$entry = new self;
|
||||
$entry->journal_id = $object->transaction_journal_id;
|
||||
$entry->description = Steam::decrypt(intval($object->journal_encrypted), $object->journal_description);
|
||||
$entry->amount = $object->amount;
|
||||
$entry->date = $object->date;
|
||||
$entry->transaction_type = $object->transaction_type;
|
||||
$entry->currency_code = $object->transaction_currency_code;
|
||||
$entry->source_account_id = $object->account_id;
|
||||
$entry->source_account_name = Steam::decrypt(intval($object->account_name_encrypted), $object->account_name);
|
||||
$entry->destination_account_id = $object->opposing_account_id;
|
||||
$entry->destination_account_name = Steam::decrypt(intval($object->opposing_account_encrypted), $object->opposing_account_name);
|
||||
$entry->category_id = $object->category_id ?? '';
|
||||
$entry->category_name = $object->category_name ?? '';
|
||||
$entry->budget_id = $object->budget_id ?? '';
|
||||
$entry->budget_name = $object->budget_name ?? '';
|
||||
$entry = new self;
|
||||
$entry->journal_id = $object->transaction_journal_id;
|
||||
$entry->description = Steam::decrypt(intval($object->journal_encrypted), $object->journal_description);
|
||||
$entry->amount = $object->amount;
|
||||
$entry->date = $object->date;
|
||||
$entry->transaction_type = $object->transaction_type;
|
||||
$entry->currency_code = $object->transaction_currency_code;
|
||||
$entry->asset_account_id = $object->account_id;
|
||||
$entry->asset_account_name = Steam::decrypt(intval($object->account_name_encrypted), $object->account_name);
|
||||
$entry->opposing_account_id = $object->opposing_account_id;
|
||||
$entry->opposing_account_name = Steam::decrypt(intval($object->opposing_account_encrypted), $object->opposing_account_name);
|
||||
$entry->category_id = $object->category_id ?? '';
|
||||
$entry->category_name = $object->category_name ?? '';
|
||||
$entry->budget_id = $object->budget_id ?? '';
|
||||
$entry->budget_name = $object->budget_name ?? '';
|
||||
|
||||
// update description when transaction description is different:
|
||||
if (!is_null($object->description) && $object->description !== $entry->description) {
|
||||
|
||||
@@ -99,7 +99,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveAttachmentsForModel(Model $model, array $files = null): bool
|
||||
public function saveAttachmentsForModel(Model $model, ?array $files): bool
|
||||
{
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $entry) {
|
||||
|
||||
@@ -55,6 +55,6 @@ interface AttachmentHelperInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveAttachmentsForModel(Model $model, array $files = null): bool;
|
||||
public function saveAttachmentsForModel(Model $model, ?array $files): bool;
|
||||
|
||||
}
|
||||
|
||||
@@ -64,22 +64,22 @@ class JournalCollector implements JournalCollectorInterface
|
||||
'transaction_journals.bill_id',
|
||||
'bills.name as bill_name',
|
||||
'bills.name_encrypted as bill_name_encrypted',
|
||||
'transactions.id as id',
|
||||
|
||||
'transactions.id as id',
|
||||
'transactions.description as transaction_description',
|
||||
'transactions.account_id',
|
||||
'transactions.identifier',
|
||||
'transactions.transaction_journal_id',
|
||||
|
||||
'transactions.amount as transaction_amount',
|
||||
|
||||
'transactions.transaction_currency_id as transaction_currency_id',
|
||||
|
||||
'transaction_currencies.code as transaction_currency_code',
|
||||
'transaction_currencies.symbol as transaction_currency_symbol',
|
||||
'transaction_currencies.decimal_places as transaction_currency_dp',
|
||||
|
||||
'transactions.foreign_amount as transaction_foreign_amount',
|
||||
'transactions.foreign_currency_id as foreign_currency_id',
|
||||
|
||||
'foreign_currencies.code as foreign_currency_code',
|
||||
'foreign_currencies.symbol as foreign_currency_symbol',
|
||||
'foreign_currencies.decimal_places as foreign_currency_dp',
|
||||
@@ -399,6 +399,10 @@ class JournalCollector implements JournalCollectorInterface
|
||||
*/
|
||||
public function setPage(int $page): JournalCollectorInterface
|
||||
{
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
$this->page = $page;
|
||||
|
||||
if ($page > 0) {
|
||||
@@ -673,6 +677,7 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id');
|
||||
$this->query->whereNull('opposing.deleted_at');
|
||||
|
||||
$this->fields[] = 'opposing.id as opposing_id';
|
||||
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
||||
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
||||
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
||||
|
||||
@@ -41,11 +41,13 @@ class TransferFilter implements FilterInterface
|
||||
continue;
|
||||
}
|
||||
// make property string:
|
||||
$journalId = $transaction->transaction_journal_id;
|
||||
$amount = Steam::positive($transaction->transaction_amount);
|
||||
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
|
||||
$journalId = $transaction->transaction_journal_id;
|
||||
$amount = Steam::positive($transaction->transaction_amount);
|
||||
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
|
||||
$transactionIds = [$transaction->id, intval($transaction->opposing_id)];
|
||||
sort($accountIds);
|
||||
$key = $journalId . '-' . join(',', $accountIds) . '-' . $amount;
|
||||
sort($transactionIds);
|
||||
$key = $journalId . '-' . join(',', $transactionIds) . '-' . join(',', $accountIds) . '-' . $amount;
|
||||
if (!isset($count[$key])) {
|
||||
// not yet counted? add to new set and count it:
|
||||
$new->push($transaction);
|
||||
|
||||
@@ -253,7 +253,7 @@ class AccountController extends Controller
|
||||
$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'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$chartUri = route('chart.account.single', [$account->id]);
|
||||
$start = null;
|
||||
@@ -274,55 +274,31 @@ class AccountController extends Controller
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
$start = new Carbon($moment);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$fStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
|
||||
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
|
||||
$periods = $this->getPeriodOverview($account);
|
||||
}
|
||||
|
||||
// prep for current period
|
||||
// prep for current period view
|
||||
if (strlen($moment) === 0) {
|
||||
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$fStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
|
||||
$periods = $this->getPeriodOverview($account);
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
Log::info('Count is zero, search for journals.');
|
||||
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||
if (!is_null($start)) {
|
||||
$collector->setRange($start, $end);
|
||||
}
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('accounts/show/' . $account->id . '/' . $moment);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
// grab journals:
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||
if (!is_null($start)) {
|
||||
$collector->setRange($start, $end);
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('accounts.show', [$account->id, $moment]));
|
||||
|
||||
return view(
|
||||
'accounts.show',
|
||||
@@ -421,7 +397,7 @@ class AccountController extends Controller
|
||||
$start = $repository->oldestJournalDate($account);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
|
||||
// properties for cache
|
||||
|
||||
@@ -17,6 +17,7 @@ use Config;
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\UserRegistrationRequest;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -52,11 +53,11 @@ class RegisterController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param UserRegistrationRequest|Request $request
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function register(Request $request)
|
||||
public function register(UserRegistrationRequest $request)
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
||||
|
||||
@@ -206,7 +206,7 @@ class BillController extends Controller
|
||||
/** @var Carbon $date */
|
||||
$date = session('start');
|
||||
$year = $date->year;
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$yearAverage = $repository->getYearAverage($bill, $date);
|
||||
$overallAverage = $repository->getOverallAverage($bill);
|
||||
@@ -217,7 +217,7 @@ class BillController extends Controller
|
||||
$collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setLimit($pageSize)->setPage($page)->withBudgetInformation()
|
||||
->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/bills/show/' . $bill->id);
|
||||
$journals->setPath(route('bills.show', [$bill->id]));
|
||||
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
|
||||
$hideBill = true;
|
||||
|
||||
@@ -293,37 +293,15 @@ class BudgetController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at no-budget loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info(sprintf('Count is zero, search for journals between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
|
||||
->withoutBudget()->withOpposingAccount();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/list/no-budget');
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.without_budget_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
|
||||
->withoutBudget()->withOpposingAccount();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('budgets.no-budget'));
|
||||
|
||||
return view('budgets.no-budget', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
|
||||
}
|
||||
@@ -357,7 +335,7 @@ class BudgetController extends Controller
|
||||
/** @var Carbon $start */
|
||||
$start = session('first', Carbon::create()->startOfYear());
|
||||
$end = new Carbon;
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$limits = $this->getLimits($budget, $start, $end);
|
||||
$repetition = null;
|
||||
@@ -366,7 +344,7 @@ class BudgetController extends Controller
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/show/' . $budget->id);
|
||||
$journals->setPath(route('budgets.show', [$budget->id]));
|
||||
|
||||
|
||||
$subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
|
||||
@@ -388,7 +366,7 @@ class BudgetController extends Controller
|
||||
throw new FireflyException('This budget limit is not part of this budget.');
|
||||
}
|
||||
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$subTitle = trans(
|
||||
'firefly.budget_in_period', [
|
||||
@@ -404,7 +382,7 @@ class BudgetController extends Controller
|
||||
$collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date)
|
||||
->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/show/' . $budget->id . '/' . $budgetLimit->id);
|
||||
$journals->setPath(route('budgets.show', [$budget->id, $budgetLimit->id]));
|
||||
|
||||
|
||||
$start = session('first', Carbon::create()->startOfYear());
|
||||
@@ -567,7 +545,7 @@ class BudgetController extends Controller
|
||||
$start = $first->date ?? new Carbon;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
|
||||
// properties for cache
|
||||
|
||||
@@ -164,10 +164,12 @@ class CategoryController extends Controller
|
||||
public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
|
||||
{
|
||||
// default values:
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
|
||||
// prep for "all" view.
|
||||
if ($moment === 'all') {
|
||||
@@ -199,37 +201,13 @@ class CategoryController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at no-cat loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info('Count is zero, search for journals.');
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/categories/list/no-category');
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.without_category_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('categories.no-category'));
|
||||
|
||||
return view('categories.no-category', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
|
||||
}
|
||||
@@ -247,10 +225,8 @@ class CategoryController extends Controller
|
||||
// default values:
|
||||
$subTitle = $category->name;
|
||||
$subTitleIcon = 'fa-bar-chart';
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
@@ -287,34 +263,15 @@ class CategoryController extends Controller
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at category loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info('Count is zero, search for journals.');
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('categories/show/' . $category->id);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_category',
|
||||
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('categories.show', [$category->id]));
|
||||
|
||||
|
||||
return view('categories.show', compact('category', 'moment', 'journals', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
|
||||
}
|
||||
@@ -382,7 +339,7 @@ class CategoryController extends Controller
|
||||
$start = $first->date ?? new Carbon;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
|
||||
// properties for cache
|
||||
@@ -468,7 +425,7 @@ class CategoryController extends Controller
|
||||
}
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$first = Navigation::startOfPeriod($first, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
|
||||
// properties for entries with their amounts.
|
||||
|
||||
@@ -163,7 +163,7 @@ class BudgetController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseAsset(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
@@ -208,7 +208,7 @@ class BudgetController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseCategory(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
@@ -255,7 +255,7 @@ class BudgetController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseExpense(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
|
||||
@@ -138,7 +138,7 @@ class HomeController extends Controller
|
||||
}
|
||||
|
||||
return view(
|
||||
'index', compact('count', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage', 'billCount')
|
||||
'index', compact('count', 'subTitle', 'transactions', 'showDepositsFrontpage', 'billCount')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
49
app/Http/Controllers/Import/BankController.php
Normal file
49
app/Http/Controllers/Import/BankController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* BankController.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\Import;
|
||||
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface;
|
||||
|
||||
class BankController extends Controller
|
||||
{
|
||||
|
||||
public function postPrerequisites()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $bank
|
||||
*/
|
||||
public function prerequisites(string $bank)
|
||||
{
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
|
||||
if ($object->hasPrerequisites()) {
|
||||
$view = $object->getView();
|
||||
$parameters = $object->getViewParameters();
|
||||
|
||||
return view($view, $parameters);
|
||||
}
|
||||
|
||||
if (!$object->hasPrerequisites()) {
|
||||
echo 'redirect to import form.';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
306
app/Http/Controllers/Import/FileController.php
Normal file
306
app/Http/Controllers/Import/FileController.php
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
/**
|
||||
* FileController.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\Import;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\ImportUploadRequest;
|
||||
use FireflyIII\Import\Configurator\ConfiguratorInterface;
|
||||
use FireflyIII\Import\Routine\ImportRoutine;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response as LaravelResponse;
|
||||
use Log;
|
||||
use Response;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
|
||||
/**
|
||||
* Class FileController.
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Import
|
||||
*/
|
||||
class FileController extends Controller
|
||||
{
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
public $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('mainTitleIcon', 'fa-archive');
|
||||
View::share('title', trans('firefly.import_index_title'));
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 3. This repeats until the job is configured.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return View
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function configure(ImportJob $job)
|
||||
{
|
||||
// create configuration class:
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
$this->repository->updateStatus($job, 'configured');
|
||||
|
||||
return redirect(route('import.file.status', [$job->key]));
|
||||
}
|
||||
$view = $configurator->getNextView();
|
||||
$data = $configurator->getNextData();
|
||||
$subTitle = trans('firefly.import_config_bread_crumb');
|
||||
$subTitleIcon = 'fa-wrench';
|
||||
|
||||
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JSON file of the job's configuration and send it to the user.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return LaravelResponse
|
||||
*/
|
||||
public function download(ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in download()', ['job' => $job->key]);
|
||||
$config = $job->configuration;
|
||||
|
||||
// This is CSV import specific:
|
||||
$config['column-roles-complete'] = false;
|
||||
$config['column-mapping-complete'] = false;
|
||||
$config['initial-config-complete'] = false;
|
||||
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
|
||||
|
||||
$result = json_encode($config, JSON_PRETTY_PRINT);
|
||||
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($result, 200);
|
||||
$response->header('Content-disposition', 'attachment; filename=' . $name)
|
||||
->header('Content-Type', 'application/json')
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', strlen($result));
|
||||
|
||||
return $response;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 1. Upload a file.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$subTitle = trans('firefly.import_index_sub_title');
|
||||
$subTitleIcon = 'fa-home';
|
||||
$importFileTypes = [];
|
||||
$defaultImportType = config('firefly.default_import_format');
|
||||
|
||||
foreach (array_keys(config('firefly.import_formats')) as $type) {
|
||||
$importFileTypes[$type] = trans('firefly.import_file_type_' . $type);
|
||||
}
|
||||
|
||||
return view('import.file.index', compact('subTitle', 'subTitleIcon', 'importFileTypes', 'defaultImportType'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 2. It creates an Import Job. Stores the import.
|
||||
*
|
||||
* @param ImportUploadRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function initialize(ImportUploadRequest $request)
|
||||
{
|
||||
Log::debug('Now in initialize()');
|
||||
|
||||
// create import job:
|
||||
$type = $request->get('import_file_type');
|
||||
$job = $this->repository->create($type);
|
||||
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
|
||||
|
||||
// process file:
|
||||
$this->repository->processFile($job, $request->files->get('import_file'));
|
||||
|
||||
// process config, if present:
|
||||
if ($request->files->has('configuration_file')) {
|
||||
$this->repository->processConfiguration($job, $request->files->get('configuration_file'));
|
||||
}
|
||||
|
||||
$this->repository->updateStatus($job, 'initialized');
|
||||
|
||||
return redirect(route('import.file.configure', [$job->key]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Show status of import job in JSON.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function json(ImportJob $job)
|
||||
{
|
||||
$result = [
|
||||
'started' => false,
|
||||
'finished' => false,
|
||||
'running' => false,
|
||||
'errors' => array_values($job->extended_status['errors']),
|
||||
'percentage' => 0,
|
||||
'show_percentage' => false,
|
||||
'steps' => $job->extended_status['steps'],
|
||||
'done' => $job->extended_status['done'],
|
||||
'statusText' => trans('firefly.import_status_job_' . $job->status),
|
||||
'status' => $job->status,
|
||||
'finishedText' => '',
|
||||
];
|
||||
|
||||
if ($job->extended_status['steps'] !== 0) {
|
||||
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
|
||||
$result['show_percentage'] = true;
|
||||
}
|
||||
|
||||
if ($job->status === 'finished') {
|
||||
$tagId = $job->extended_status['tag'];
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$tag = $repository->find($tagId);
|
||||
$result['finished'] = true;
|
||||
$result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
|
||||
}
|
||||
|
||||
if ($job->status === 'running') {
|
||||
$result['started'] = true;
|
||||
$result['running'] = true;
|
||||
}
|
||||
|
||||
return Response::json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4. Save the configuration.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postConfigure(Request $request, ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in postConfigure()', ['job' => $job->key]);
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
return redirect(route('import.file.status', [$job->key]));
|
||||
}
|
||||
$data = $request->all();
|
||||
$configurator->configureJob($data);
|
||||
|
||||
// get possible warning from configurator:
|
||||
$warning = $configurator->getWarningMessage();
|
||||
|
||||
if (strlen($warning) > 0) {
|
||||
Session::flash('warning', $warning);
|
||||
}
|
||||
|
||||
// return to configure
|
||||
return redirect(route('import.file.configure', [$job->key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function start(ImportJob $job)
|
||||
{
|
||||
/** @var ImportRoutine $routine */
|
||||
$routine = app(ImportRoutine::class);
|
||||
$routine->setJob($job);
|
||||
$result = $routine->run();
|
||||
if ($result) {
|
||||
return Response::json(['run' => 'ok']);
|
||||
}
|
||||
|
||||
throw new FireflyException('Job did not complete succesfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function status(ImportJob $job)
|
||||
{
|
||||
$statuses = ['configured', 'running', 'finished'];
|
||||
if (!in_array($job->status, $statuses)) {
|
||||
return redirect(route('import.file.configure', [$job->key]));
|
||||
}
|
||||
$subTitle = trans('firefly.import_status_sub_title');
|
||||
$subTitleIcon = 'fa-star';
|
||||
|
||||
return view('import.file.status', compact('job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return ConfiguratorInterface
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function makeConfigurator(ImportJob $job): ConfiguratorInterface
|
||||
{
|
||||
$type = $job->file_type;
|
||||
$key = sprintf('firefly.import_configurators.%s', $type);
|
||||
$className = config($key);
|
||||
if (is_null($className)) {
|
||||
throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var ConfiguratorInterface $configurator */
|
||||
$configurator = app($className);
|
||||
$configurator->setJob($job);
|
||||
|
||||
|
||||
return $configurator;
|
||||
}
|
||||
}
|
||||
@@ -12,17 +12,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Requests\ImportUploadRequest;
|
||||
use FireflyIII\Import\Configurator\ConfiguratorInterface;
|
||||
use FireflyIII\Import\Routine\ImportRoutine;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response as LaravelResponse;
|
||||
use Log;
|
||||
use Response;
|
||||
use View;
|
||||
|
||||
/**
|
||||
@@ -53,72 +43,9 @@ class ImportController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 3. This repeats until the job is configured.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return View
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function configure(ImportJob $job)
|
||||
{
|
||||
// create configuration class:
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
$this->repository->updateStatus($job, 'configured');
|
||||
|
||||
return redirect(route('import.status', [$job->key]));
|
||||
}
|
||||
$view = $configurator->getNextView();
|
||||
$data = $configurator->getNextData();
|
||||
$subTitle = trans('firefly.import_config_bread_crumb');
|
||||
$subTitleIcon = 'fa-wrench';
|
||||
|
||||
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JSON file of the job's configuration and send it to the user.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return LaravelResponse
|
||||
*/
|
||||
public function download(ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in download()', ['job' => $job->key]);
|
||||
$config = $job->configuration;
|
||||
|
||||
// This is CSV import specific:
|
||||
$config['column-roles-complete'] = false;
|
||||
$config['column-mapping-complete'] = false;
|
||||
$config['initial-config-complete'] = false;
|
||||
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
|
||||
|
||||
$result = json_encode($config, JSON_PRETTY_PRINT);
|
||||
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($result, 200);
|
||||
$response->header('Content-disposition', 'attachment; filename=' . $name)
|
||||
->header('Content-Type', 'application/json')
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', strlen($result));
|
||||
|
||||
return $response;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 1. Upload a file.
|
||||
* General import index
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
@@ -136,160 +63,4 @@ class ImportController extends Controller
|
||||
return view('import.index', compact('subTitle', 'subTitleIcon', 'importFileTypes', 'defaultImportType'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 2. It creates an Import Job. Stores the import.
|
||||
*
|
||||
* @param ImportUploadRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function initialize(ImportUploadRequest $request)
|
||||
{
|
||||
Log::debug('Now in initialize()');
|
||||
|
||||
// create import job:
|
||||
$type = $request->get('import_file_type');
|
||||
$job = $this->repository->create($type);
|
||||
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
|
||||
|
||||
// process file:
|
||||
$this->repository->processFile($job, $request->files->get('import_file'));
|
||||
|
||||
// process config, if present:
|
||||
if ($request->files->has('configuration_file')) {
|
||||
$this->repository->processConfiguration($job, $request->files->get('configuration_file'));
|
||||
}
|
||||
|
||||
$this->repository->updateStatus($job, 'initialized');
|
||||
|
||||
return redirect(route('import.configure', [$job->key]));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Show status of import job in JSON.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function json(ImportJob $job)
|
||||
{
|
||||
$result = [
|
||||
'started' => false,
|
||||
'finished' => false,
|
||||
'running' => false,
|
||||
'errors' => array_values($job->extended_status['errors']),
|
||||
'percentage' => 0,
|
||||
'show_percentage' => false,
|
||||
'steps' => $job->extended_status['steps'],
|
||||
'done' => $job->extended_status['done'],
|
||||
'statusText' => trans('firefly.import_status_job_' . $job->status),
|
||||
'status' => $job->status,
|
||||
'finishedText' => '',
|
||||
];
|
||||
|
||||
if ($job->extended_status['steps'] !== 0) {
|
||||
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
|
||||
$result['show_percentage'] = true;
|
||||
}
|
||||
|
||||
if ($job->status === 'finished') {
|
||||
$tagId = $job->extended_status['tag'];
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$tag = $repository->find($tagId);
|
||||
$result['finished'] = true;
|
||||
$result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
|
||||
}
|
||||
|
||||
if ($job->status === 'running') {
|
||||
$result['started'] = true;
|
||||
$result['running'] = true;
|
||||
}
|
||||
|
||||
return Response::json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4. Save the configuration.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postConfigure(Request $request, ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in postConfigure()', ['job' => $job->key]);
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
return redirect(route('import.status', [$job->key]));
|
||||
}
|
||||
$data = $request->all();
|
||||
$configurator->configureJob($data);
|
||||
|
||||
// return to configure
|
||||
return redirect(route('import.configure', [$job->key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function start(ImportJob $job)
|
||||
{
|
||||
/** @var ImportRoutine $routine */
|
||||
$routine = app(ImportRoutine::class);
|
||||
$routine->setJob($job);
|
||||
$result = $routine->run();
|
||||
if ($result) {
|
||||
return Response::json(['run' => 'ok']);
|
||||
}
|
||||
|
||||
throw new FireflyException('Job did not complete succesfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function status(ImportJob $job)
|
||||
{
|
||||
$statuses = ['configured', 'running', 'finished'];
|
||||
if (!in_array($job->status, $statuses)) {
|
||||
return redirect(route('import.configure', [$job->key]));
|
||||
}
|
||||
$subTitle = trans('firefly.import_status_sub_title');
|
||||
$subTitleIcon = 'fa-star';
|
||||
|
||||
return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return ConfiguratorInterface
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function makeConfigurator(ImportJob $job): ConfiguratorInterface
|
||||
{
|
||||
$type = $job->file_type;
|
||||
$key = sprintf('firefly.import_configurators.%s', $type);
|
||||
$className = config($key);
|
||||
if (is_null($className)) {
|
||||
throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var ConfiguratorInterface $configurator */
|
||||
$configurator = app($className);
|
||||
$configurator->setJob($job);
|
||||
|
||||
|
||||
return $configurator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
@@ -19,9 +20,9 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Navigation;
|
||||
use Preferences;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class JavascriptController
|
||||
@@ -88,11 +89,6 @@ class JavascriptController extends Controller
|
||||
*/
|
||||
public function variables(Request $request)
|
||||
{
|
||||
$picker = $this->getDateRangePicker();
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$linkTitle = sprintf('%s - %s', $start->formatLocalized($this->monthAndDayFormat), $end->formatLocalized($this->monthAndDayFormat));
|
||||
$firstDate = session('first')->format('Y-m-d');
|
||||
$localeconv = localeconv();
|
||||
$accounting = Amount::getJsConfig($localeconv);
|
||||
$localeconv = localeconv();
|
||||
@@ -100,15 +96,16 @@ class JavascriptController extends Controller
|
||||
$localeconv['frac_digits'] = $defaultCurrency->decimal_places;
|
||||
$pref = Preferences::get('language', config('firefly.default_language', 'en_US'));
|
||||
$lang = $pref->data;
|
||||
$data = [
|
||||
'picker' => $picker,
|
||||
'linkTitle' => $linkTitle,
|
||||
'firstDate' => $firstDate,
|
||||
'currencyCode' => Amount::getCurrencyCode(),
|
||||
'currencySymbol' => Amount::getCurrencySymbol(),
|
||||
'accounting' => $accounting,
|
||||
'localeconv' => $localeconv,
|
||||
'language' => $lang,
|
||||
$dateRange = $this->getDateRangeConfig();
|
||||
|
||||
$data = [
|
||||
'currencyCode' => Amount::getCurrencyCode(),
|
||||
'currencySymbol' => Amount::getCurrencySymbol(),
|
||||
'accounting' => $accounting,
|
||||
'localeconv' => $localeconv,
|
||||
'language' => $lang,
|
||||
'dateRangeTitle' => $dateRange['title'],
|
||||
'dateRangeConfig' => $dateRange['configuration'],
|
||||
];
|
||||
$request->session()->keep(['two-factor-secret']);
|
||||
|
||||
@@ -119,40 +116,73 @@ class JavascriptController extends Controller
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getDateRangePicker(): array
|
||||
private function getDateRangeConfig(): array
|
||||
{
|
||||
$viewRange = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$start = session('start');
|
||||
$end = session('end');
|
||||
$first = session('first');
|
||||
$title = sprintf('%s - %s', $start->formatLocalized($this->monthAndDayFormat), $end->formatLocalized($this->monthAndDayFormat));
|
||||
$isCustom = session('is_custom_range');
|
||||
$ranges = [
|
||||
// first range is the current range:
|
||||
$title => [$start, $end],
|
||||
];
|
||||
Log::debug(sprintf('viewRange is %s', $viewRange));
|
||||
|
||||
$prevStart = clone $start;
|
||||
$prevEnd = clone $start;
|
||||
$nextStart = clone $end;
|
||||
$nextEnd = clone $end;
|
||||
if ($viewRange === 'custom') {
|
||||
$days = $start->diffInDays($end);
|
||||
$prevStart->subDays($days);
|
||||
$nextEnd->addDays($days);
|
||||
unset($days);
|
||||
// get the format for the ranges:
|
||||
$format = $this->getFormatByRange($viewRange);
|
||||
|
||||
// when current range is a custom range, add the current period as the next range.
|
||||
if ($isCustom) {
|
||||
Log::debug('Custom is true.');
|
||||
$index = $start->formatLocalized($format);
|
||||
$customPeriodStart = Navigation::startOfPeriod($start, $viewRange);
|
||||
$customPeriodEnd = Navigation::endOfPeriod($customPeriodStart, $viewRange);
|
||||
$ranges[$index] = [$customPeriodStart, $customPeriodEnd];
|
||||
}
|
||||
// then add previous range and next range
|
||||
$previousDate = Navigation::subtractPeriod($start, $viewRange);
|
||||
$index = $previousDate->formatLocalized($format);
|
||||
$previousStart = Navigation::startOfPeriod($previousDate, $viewRange);
|
||||
$previousEnd = Navigation::endOfPeriod($previousStart, $viewRange);
|
||||
$ranges[$index] = [$previousStart, $previousEnd];
|
||||
|
||||
if ($viewRange !== 'custom') {
|
||||
$prevStart = Navigation::subtractPeriod($start, $viewRange);// subtract for previous period
|
||||
$prevEnd = Navigation::endOfPeriod($prevStart, $viewRange);
|
||||
$nextStart = Navigation::addPeriod($start, $viewRange, 0); // add for previous period
|
||||
$nextEnd = Navigation::endOfPeriod($nextStart, $viewRange);
|
||||
}
|
||||
$nextDate = Navigation::addPeriod($start, $viewRange, 0);
|
||||
$index = $nextDate->formatLocalized($format);
|
||||
$nextStart = Navigation::startOfPeriod($nextDate, $viewRange);
|
||||
$nextEnd = Navigation::endOfPeriod($nextStart, $viewRange);
|
||||
$ranges[$index] = [$nextStart, $nextEnd];
|
||||
|
||||
$ranges = [];
|
||||
$ranges['current'] = [$start->format('Y-m-d'), $end->format('Y-m-d')];
|
||||
$ranges['previous'] = [$prevStart->format('Y-m-d'), $prevEnd->format('Y-m-d')];
|
||||
$ranges['next'] = [$nextStart->format('Y-m-d'), $nextEnd->format('Y-m-d')];
|
||||
// everything
|
||||
$index = strval(trans('firefly.everything'));
|
||||
$ranges[$index] = [$first, new Carbon];
|
||||
|
||||
$return = [
|
||||
'title' => $title,
|
||||
'configuration' => [
|
||||
'apply' => strval(trans('firefly.apply')),
|
||||
'cancel' => strval(trans('firefly.cancel')),
|
||||
'from' => strval(trans('firefly.from')),
|
||||
'to' => strval(trans('firefly.to')),
|
||||
'customRange' => strval(trans('firefly.customRange')),
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $end->format('Y-m-d'),
|
||||
'ranges' => $ranges,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
return $return;
|
||||
|
||||
}
|
||||
|
||||
private function getFormatByRange(string $viewRange): string
|
||||
{
|
||||
switch ($viewRange) {
|
||||
default:
|
||||
throw new FireflyException('The date picker does not yet support "' . $viewRange . '".'); // @codeCoverageIgnore
|
||||
throw new FireflyException(sprintf('The date picker does not yet support "%s".', $viewRange)); // @codeCoverageIgnore
|
||||
case '1D':
|
||||
case 'custom':
|
||||
$format = (string)trans('config.month_and_day');
|
||||
@@ -174,18 +204,6 @@ class JavascriptController extends Controller
|
||||
break;
|
||||
}
|
||||
|
||||
$current = $start->formatLocalized($format);
|
||||
$next = $nextStart->formatLocalized($format);
|
||||
$prev = $prevStart->formatLocalized($format);
|
||||
|
||||
return [
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $end->format('Y-m-d'),
|
||||
'current' => $current,
|
||||
'previous' => $prev,
|
||||
'next' => $next,
|
||||
'ranges' => $ranges,
|
||||
];
|
||||
return $format;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,4 +163,4 @@ class IntroController
|
||||
return $steps;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Navigation;
|
||||
use Preferences;
|
||||
use Session;
|
||||
@@ -79,22 +78,16 @@ class TagController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* Create a new tag.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function create(Request $request)
|
||||
public function create()
|
||||
{
|
||||
$subTitle = trans('firefly.new_tag');
|
||||
$subTitleIcon = 'fa-tag';
|
||||
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
|
||||
|
||||
$preFilled = [
|
||||
'tagMode' => 'nothing',
|
||||
];
|
||||
if (!$request->old('tagMode')) {
|
||||
Session::flash('preFilled', $preFilled);
|
||||
}
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('tags.create.fromStore') !== true) {
|
||||
$this->rememberPreviousUri('tags.create.uri');
|
||||
@@ -107,6 +100,8 @@ class TagController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a tag
|
||||
*
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return View
|
||||
@@ -141,38 +136,18 @@ class TagController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* Edit a tag
|
||||
*
|
||||
* @param TagRepositoryInterface $repository
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Tag $tag, TagRepositoryInterface $repository)
|
||||
public function edit(Tag $tag)
|
||||
{
|
||||
$subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]);
|
||||
$subTitleIcon = 'fa-tag';
|
||||
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
|
||||
|
||||
/*
|
||||
* Default tag options (again)
|
||||
*/
|
||||
$tagOptions = $this->tagOptions;
|
||||
|
||||
/*
|
||||
* Can this tag become another type?
|
||||
*/
|
||||
$allowAdvance = $repository->tagAllowAdvance($tag);
|
||||
$allowToBalancingAct = $repository->tagAllowBalancing($tag);
|
||||
|
||||
// edit tag options:
|
||||
if ($allowAdvance === false) {
|
||||
unset($tagOptions['advancePayment']);
|
||||
}
|
||||
if ($allowToBalancingAct === false) {
|
||||
unset($tagOptions['balancingAct']);
|
||||
}
|
||||
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('tags.edit.fromUpdate') !== true) {
|
||||
$this->rememberPreviousUri('tags.edit.uri');
|
||||
@@ -181,10 +156,12 @@ class TagController extends Controller
|
||||
Session::flash('gaEventCategory', 'tags');
|
||||
Session::flash('gaEventAction', 'edit');
|
||||
|
||||
return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon', 'tagOptions', 'apiKey'));
|
||||
return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon', 'apiKey'));
|
||||
}
|
||||
|
||||
/**
|
||||
* View all tags
|
||||
*
|
||||
* @param TagRepositoryInterface $repository
|
||||
*
|
||||
* @return View
|
||||
@@ -194,6 +171,8 @@ class TagController extends Controller
|
||||
$title = 'Tags';
|
||||
$mainTitleIcon = 'fa-tags';
|
||||
$types = ['nothing', 'balancingAct', 'advancePayment'];
|
||||
$hasTypes = 0; // which types of tag the user actually has.
|
||||
$counts = []; // how many of each type?
|
||||
$count = $repository->count();
|
||||
|
||||
// loop each types and get the tags, group them by year.
|
||||
@@ -209,10 +188,13 @@ class TagController extends Controller
|
||||
return strtolower($date . $tag->tag);
|
||||
}
|
||||
);
|
||||
if ($tags->count() > 0) {
|
||||
$hasTypes++;
|
||||
}
|
||||
$counts[$type] = $tags->count();
|
||||
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
|
||||
$year = is_null($tag->date) ? trans('firefly.no_year') : $tag->date->year;
|
||||
$monthFormatted = is_null($tag->date) ? trans('firefly.no_month') : $tag->date->formatLocalized($this->monthFormat);
|
||||
|
||||
@@ -220,7 +202,7 @@ class TagController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return view('tags.index', compact('title', 'mainTitleIcon', 'types', 'collection', 'count'));
|
||||
return view('tags.index', compact('title', 'mainTitleIcon', 'counts', 'hasTypes', 'types', 'collection', 'count'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,17 +218,15 @@ class TagController extends Controller
|
||||
// default values:
|
||||
$subTitle = $tag->tag;
|
||||
$subTitleIcon = 'fa-tag';
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
|
||||
$sum = '0';
|
||||
$path = 'tags/show/' . $tag->id;
|
||||
$path = route('tags.show', [$tag->id]);
|
||||
|
||||
|
||||
// prep for "all" view.
|
||||
@@ -254,8 +234,8 @@ 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);
|
||||
$path = 'tags/show/' . $tag->id . '/all';
|
||||
$sum = $repository->sumOfTag($tag, null, null);
|
||||
$path = route('tags.show', [$tag->id, 'all']);
|
||||
}
|
||||
|
||||
// prep for "specific date" view.
|
||||
@@ -269,6 +249,7 @@ class TagController extends Controller
|
||||
);
|
||||
$periods = $this->getPeriodOverview($tag);
|
||||
$sum = $repository->sumOfTag($tag, $start, $end);
|
||||
$path = route('tags.show', [$tag->id, $moment]);
|
||||
}
|
||||
|
||||
// prep for current period
|
||||
@@ -281,32 +262,14 @@ class TagController extends Controller
|
||||
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at tag loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info(sprintf('Count is zero, search for journals between %s and %s (pagesize %d, page %d).', $start, $end, $pageSize, $page));
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_tag',
|
||||
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
|
||||
|
||||
return view('tags.show', compact('apiKey', 'tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class MassController extends Controller
|
||||
foreach ($ids as $journalId) {
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $repository->find(intval($journalId));
|
||||
if (!is_null($journal->id) && $journalId === $journal->id) {
|
||||
if (!is_null($journal->id) && intval($journalId) === $journal->id) {
|
||||
$set->push($journal);
|
||||
}
|
||||
}
|
||||
@@ -131,10 +131,9 @@ class MassController extends Controller
|
||||
$filtered = new Collection;
|
||||
$messages = [];
|
||||
/**
|
||||
* @var int $index
|
||||
* @var TransactionJournal $journal
|
||||
*/
|
||||
foreach ($journals as $index => $journal) {
|
||||
foreach ($journals as $journal) {
|
||||
$sources = $journal->sourceAccountList();
|
||||
$destinations = $journal->destinationAccountList();
|
||||
if ($sources->count() > 1) {
|
||||
|
||||
@@ -199,10 +199,10 @@ class SingleController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $transactionJournal
|
||||
* @param TransactionJournal $transactionJournal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @internal param JournalRepositoryInterface $repository
|
||||
*/
|
||||
public function destroy(TransactionJournal $transactionJournal)
|
||||
{
|
||||
|
||||
@@ -114,7 +114,7 @@ class SplitController extends Controller
|
||||
'transactions.split.edit',
|
||||
compact(
|
||||
'subTitleIcon', 'currencies', 'optionalFields',
|
||||
'preFilled', 'subTitle', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts',
|
||||
'preFilled', 'subTitle', 'uploadSize', 'assetAccounts',
|
||||
'budgets', 'journal'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -70,15 +70,13 @@ class TransactionController extends Controller
|
||||
// default values:
|
||||
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
|
||||
$types = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$path = '/transactions/' . $what;
|
||||
$path = route('transactions.index', [$what]);
|
||||
|
||||
// prep for "all" view.
|
||||
if ($moment === 'all') {
|
||||
@@ -86,14 +84,14 @@ class TransactionController extends Controller
|
||||
$first = $repository->first();
|
||||
$start = $first->date ?? new Carbon;
|
||||
$end = new Carbon;
|
||||
$path = '/transactions/' . $what . '/all/';
|
||||
$path = route('transactions.index', [$what, 'all']);
|
||||
}
|
||||
|
||||
// prep for "specific date" view.
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
$start = new Carbon($moment);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
$path = '/transactions/' . $what . '/' . $moment;
|
||||
$path = route('transactions.index', [$what, $moment]);
|
||||
$subTitle = trans(
|
||||
'firefly.title_' . $what . '_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
@@ -111,32 +109,14 @@ class TransactionController extends Controller
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at transaction loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info('Count is zero, search for journals.');
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.title_' . $what . '_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
|
||||
|
||||
return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'periods', 'start', 'end', 'moment'));
|
||||
|
||||
@@ -204,7 +184,7 @@ class TransactionController extends Controller
|
||||
$start = $first->date ?? new Carbon;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
$types = config('firefly.transactionTypesByWhat.' . $what);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class ProfileFormRequest extends Request
|
||||
{
|
||||
return [
|
||||
'current_password' => 'required',
|
||||
'new_password' => 'required|confirmed',
|
||||
'new_password' => 'required|confirmed|secure_password',
|
||||
'new_password_confirmation' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ class TagFormRequest extends Request
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
'zoomLevel' => $zoomLevel,
|
||||
'tagMode' => $this->string('tagMode'),
|
||||
];
|
||||
|
||||
return $data;
|
||||
@@ -84,7 +83,6 @@ class TagFormRequest extends Request
|
||||
'latitude' => 'numeric|min:-90|max:90',
|
||||
'longitude' => 'numeric|min:-90|max:90',
|
||||
'zoomLevel' => 'numeric|min:0|max:80',
|
||||
'tagMode' => 'required|in:nothing,balancingAct,advancePayment',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class UserFormRequest extends Request
|
||||
return [
|
||||
'id' => 'required|exists:users,id',
|
||||
'email' => 'email|required',
|
||||
'password' => 'confirmed',
|
||||
'password' => 'confirmed|secure_password',
|
||||
'blocked_code' => 'between:0,30',
|
||||
'blocked' => 'between:0,1|numeric',
|
||||
];
|
||||
|
||||
42
app/Http/Requests/UserRegistrationRequest.php
Normal file
42
app/Http/Requests/UserRegistrationRequest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* UserRegistrationRequest.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\Requests;
|
||||
|
||||
/**
|
||||
* Class UserRegistrationRequest
|
||||
*
|
||||
*
|
||||
* @package FireflyIII\Http\Requests
|
||||
*/
|
||||
class UserRegistrationRequest extends Request
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
// Only everybody
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'email|required',
|
||||
'password' => 'confirmed|secure_password',
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -471,16 +471,26 @@ Breadcrumbs::register(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* FILE IMPORT
|
||||
*/
|
||||
Breadcrumbs::register(
|
||||
'import.configure', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
|
||||
'import.file.index', function (BreadCrumbGenerator $breadcrumbs) {
|
||||
$breadcrumbs->parent('import.index');
|
||||
$breadcrumbs->push(trans('firefly.import_config_sub_title', ['key' => $job->key]), route('import.configure', [$job->key]));
|
||||
$breadcrumbs->push(trans('firefly.import_file'), route('import.file.index'));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'import.file.configure', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
|
||||
$breadcrumbs->parent('import.file.index');
|
||||
$breadcrumbs->push(trans('firefly.import_config_sub_title', ['key' => $job->key]), route('import.file.configure', [$job->key]));
|
||||
}
|
||||
);
|
||||
Breadcrumbs::register(
|
||||
'import.status', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
|
||||
$breadcrumbs->parent('import.index');
|
||||
$breadcrumbs->push(trans('firefly.import_status_bread_crumb', ['key' => $job->key]), route('import.status', [$job->key]));
|
||||
'import.file.status', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) {
|
||||
$breadcrumbs->parent('import.file.index');
|
||||
$breadcrumbs->push(trans('firefly.import_status_bread_crumb', ['key' => $job->key]), route('import.file.status', [$job->key]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -805,7 +815,7 @@ Breadcrumbs::register(
|
||||
* MASS TRANSACTION EDIT / DELETE
|
||||
*/
|
||||
Breadcrumbs::register(
|
||||
'transactions.mass.edit', function (BreadCrumbGenerator $breadcrumbs, Collection $journals) {
|
||||
'transactions.mass.edit', function (BreadCrumbGenerator $breadcrumbs, Collection $journals): void {
|
||||
|
||||
if ($journals->count() > 0) {
|
||||
$journalIds = $journals->pluck('id')->toArray();
|
||||
@@ -817,6 +827,8 @@ Breadcrumbs::register(
|
||||
}
|
||||
|
||||
$breadcrumbs->parent('index');
|
||||
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -48,6 +48,13 @@ interface ConfiguratorInterface
|
||||
*/
|
||||
public function getNextView(): string;
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string;
|
||||
|
||||
/**
|
||||
* Returns true when the initial configuration for this job is complete.
|
||||
*
|
||||
|
||||
@@ -26,8 +26,12 @@ use Log;
|
||||
*/
|
||||
class CsvConfigurator implements ConfiguratorInterface
|
||||
{
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
/**
|
||||
* ConfiguratorInterface constructor.
|
||||
*/
|
||||
@@ -50,8 +54,10 @@ class CsvConfigurator implements ConfiguratorInterface
|
||||
/** @var ConfigurationInterface $object */
|
||||
$object = new $class($this->job);
|
||||
$object->setJob($job);
|
||||
$result = $object->storeConfiguration($data);
|
||||
$this->warning = $object->getWarningMessage();
|
||||
|
||||
return $object->storeConfiguration($data);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,6 +97,16 @@ class CsvConfigurator implements ConfiguratorInterface
|
||||
throw new FireflyException('No view for state');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return $this->warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,8 @@ class Amount implements ConverterInterface
|
||||
* @param $value
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
public function convert($value): string
|
||||
{
|
||||
|
||||
@@ -58,7 +58,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual job:
|
||||
* Does the actual job.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -66,34 +66,46 @@ class CsvProcessor implements FileProcessorInterface
|
||||
{
|
||||
Log::debug('Now in CsvProcessor run(). Job is now running...');
|
||||
|
||||
$entries = $this->getImportArray();
|
||||
$index = 0;
|
||||
$entries = new Collection($this->getImportArray());
|
||||
Log::notice('Building importable objects from CSV file.');
|
||||
foreach ($entries as $index => $row) {
|
||||
// verify if not exists already:
|
||||
if ($this->rowAlreadyImported($row)) {
|
||||
$message = sprintf('Row #%d has already been imported.', $index);
|
||||
$this->job->addError($index, $message);
|
||||
$this->job->addStepsDone(5); // all steps.
|
||||
Log::info($message);
|
||||
continue;
|
||||
}
|
||||
$this->objects->push($this->importRow($index, $row));
|
||||
$this->job->addStepsDone(1);
|
||||
}
|
||||
// if job has no step count, set it now:
|
||||
$extended = $this->job->extended_status;
|
||||
if ($extended['steps'] === 0) {
|
||||
$extended['steps'] = $index * 5;
|
||||
$this->job->extended_status = $extended;
|
||||
$this->job->save();
|
||||
}
|
||||
Log::debug(sprintf('Number of entries: %d', $entries->count()));
|
||||
$notImported = $entries->filter(
|
||||
function (array $row, int $index) {
|
||||
if ($this->rowAlreadyImported($row)) {
|
||||
$message = sprintf('Row #%d has already been imported.', $index);
|
||||
$this->job->addError($index, $message);
|
||||
$this->job->addStepsDone(5); // all steps.
|
||||
Log::info($message);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
);
|
||||
Log::debug(sprintf('Number of entries left: %d', $notImported->count()));
|
||||
|
||||
// set (new) number of steps:
|
||||
$status = $this->job->extended_status;
|
||||
$status['steps'] = $notImported->count() * 5;
|
||||
$this->job->extended_status = $status;
|
||||
$this->job->save();
|
||||
Log::debug(sprintf('Number of steps: %d', $notImported->count() * 5));
|
||||
|
||||
$notImported->each(
|
||||
function (array $row, int $index) {
|
||||
$journal = $this->importRow($index, $row);
|
||||
$this->objects->push($journal);
|
||||
$this->job->addStepsDone(1);
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set import job for this processor.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return FileProcessorInterface
|
||||
@@ -116,7 +128,6 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
private function annotateValue(int $index, string $value)
|
||||
{
|
||||
$value = trim($value);
|
||||
$config = $this->job->configuration;
|
||||
$role = $config['column-roles'][$index] ?? '_ignore';
|
||||
$mapped = $config['column-mapping-config'][$index][$value] ?? null;
|
||||
@@ -151,6 +162,64 @@ class CsvProcessor implements FileProcessorInterface
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return string representation of JSON error code.
|
||||
*
|
||||
* @param int $jsonError
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getJsonError(int $jsonError): string
|
||||
{
|
||||
switch ($jsonError) {
|
||||
default:
|
||||
return 'Unknown JSON error';
|
||||
case JSON_ERROR_NONE:
|
||||
return 'No JSON error';
|
||||
case JSON_ERROR_DEPTH:
|
||||
return 'JSON_ERROR_DEPTH';
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
return 'JSON_ERROR_STATE_MISMATCH';
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
return 'JSON_ERROR_CTRL_CHAR';
|
||||
case JSON_ERROR_SYNTAX:
|
||||
return 'JSON_ERROR_SYNTAX';
|
||||
case JSON_ERROR_UTF8:
|
||||
return 'JSON_ERROR_UTF8';
|
||||
case JSON_ERROR_RECURSION:
|
||||
return 'JSON_ERROR_RECURSION';
|
||||
case JSON_ERROR_INF_OR_NAN:
|
||||
return 'JSON_ERROR_INF_OR_NAN';
|
||||
case JSON_ERROR_UNSUPPORTED_TYPE:
|
||||
return 'JSON_ERROR_UNSUPPORTED_TYPE';
|
||||
case JSON_ERROR_INVALID_PROPERTY_NAME:
|
||||
return 'JSON_ERROR_INVALID_PROPERTY_NAME';
|
||||
case JSON_ERROR_UTF16:
|
||||
return 'JSON_ERROR_UTF16';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash an array and return the result.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getRowHash(array $array): string
|
||||
{
|
||||
$json = json_encode($array);
|
||||
$jsonError = json_last_error();
|
||||
|
||||
if ($json === false) {
|
||||
throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError)));
|
||||
}
|
||||
$hash = hash('sha256', $json);
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a row, build import journal by annotating each value and storing it in the import journal.
|
||||
*
|
||||
@@ -158,15 +227,22 @@ class CsvProcessor implements FileProcessorInterface
|
||||
* @param array $row
|
||||
*
|
||||
* @return ImportJournal
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function importRow(int $index, array $row): ImportJournal
|
||||
{
|
||||
Log::debug(sprintf('Now at row %d', $index));
|
||||
$row = $this->specifics($row);
|
||||
$row = $this->specifics($row);
|
||||
$hash = $this->getRowHash($row);
|
||||
|
||||
$journal = new ImportJournal;
|
||||
$journal->setUser($this->job->user);
|
||||
$journal->setHash(hash('sha256', json_encode($row)));
|
||||
$journal->setHash($hash);
|
||||
|
||||
/**
|
||||
* @var int $rowIndex
|
||||
* @var string $value
|
||||
*/
|
||||
foreach ($row as $rowIndex => $value) {
|
||||
$value = trim($value);
|
||||
if (strlen($value) > 0) {
|
||||
@@ -175,7 +251,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
$journal->setValue($annotated);
|
||||
}
|
||||
}
|
||||
Log::debug('ImportJournal complete, returning.');
|
||||
// set some extra info:
|
||||
$journal->asset->setDefaultAccountId($this->job->configuration['import-account']);
|
||||
|
||||
return $journal;
|
||||
}
|
||||
@@ -189,13 +266,12 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
private function rowAlreadyImported(array $array): bool
|
||||
{
|
||||
$string = json_encode($array);
|
||||
$hash = hash('sha256', json_encode($string));
|
||||
$json = json_encode($hash);
|
||||
$entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('data', $json)
|
||||
->where('name', 'importHash')
|
||||
->first();
|
||||
$hash = $this->getRowHash($array);
|
||||
$json = json_encode($hash);
|
||||
$entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('data', $json)
|
||||
->where('name', 'importHash')
|
||||
->first();
|
||||
if (!is_null($entry)) {
|
||||
return true;
|
||||
}
|
||||
@@ -215,8 +291,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
private function specifics(array $row): array
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
//
|
||||
foreach ($config['specifics'] as $name => $enabled) {
|
||||
$names = array_keys($config['specifics']);
|
||||
foreach ($names as $name) {
|
||||
|
||||
if (!in_array($name, $this->validSpecifics)) {
|
||||
throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
|
||||
|
||||
@@ -20,6 +20,7 @@ use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class ImportAccount
|
||||
*
|
||||
* @package FireflyIII\Import\Object
|
||||
@@ -41,6 +42,14 @@ class ImportAccount
|
||||
private $defaultAccountId = 0;
|
||||
/** @var string */
|
||||
private $expectedType = '';
|
||||
/**
|
||||
* This value is used to indicate the other account ID (the opposing transaction's account),
|
||||
* if it is know. If so, this particular importaccount may never return an Account with this ID.
|
||||
* If it would, this would result in a transaction from-to the same account.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $forbiddenAccountId = 0;
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var User */
|
||||
@@ -125,6 +134,14 @@ class ImportAccount
|
||||
$this->defaultAccountId = $defaultAccountId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $forbiddenAccountId
|
||||
*/
|
||||
public function setForbiddenAccountId(int $forbiddenAccountId)
|
||||
{
|
||||
$this->forbiddenAccountId = $forbiddenAccountId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
@@ -148,7 +165,9 @@ class ImportAccount
|
||||
if (count($this->accountId) === 3) {
|
||||
Log::debug(sprintf('Finding account of type %d and ID %d', $accountType->id, $this->accountId['value']));
|
||||
/** @var Account $account */
|
||||
$account = $this->user->accounts()->where('account_type_id', $accountType->id)->where('id', $this->accountId['value'])->first();
|
||||
$account = $this->user->accounts()->where('id', '!=', $this->forbiddenAccountId)->where('account_type_id', $accountType->id)->where(
|
||||
'id', $this->accountId['value']
|
||||
)->first();
|
||||
if (!is_null($account)) {
|
||||
Log::debug(sprintf('Found unmapped %s account by ID (#%d): %s', $this->expectedType, $account->id, $account->name));
|
||||
|
||||
@@ -164,7 +183,7 @@ class ImportAccount
|
||||
Log::debug(sprintf('Finding account of type %d and IBAN %s', $accountType->id, $iban));
|
||||
$filtered = $accounts->filter(
|
||||
function (Account $account) use ($iban) {
|
||||
if ($account->iban === $iban) {
|
||||
if ($account->iban === $iban && $account->id !== $this->forbiddenAccountId) {
|
||||
Log::debug(
|
||||
sprintf('Found unmapped %s account by IBAN (#%d): %s (%s)', $this->expectedType, $account->id, $account->name, $account->iban)
|
||||
);
|
||||
@@ -187,7 +206,7 @@ class ImportAccount
|
||||
Log::debug(sprintf('Finding account of type %d and name %s', $accountType->id, $name));
|
||||
$filtered = $accounts->filter(
|
||||
function (Account $account) use ($name) {
|
||||
if ($account->name === $name) {
|
||||
if ($account->name === $name && $account->id !== $this->forbiddenAccountId) {
|
||||
Log::debug(sprintf('Found unmapped %s account by name (#%d): %s', $this->expectedType, $account->id, $account->name));
|
||||
|
||||
return $account;
|
||||
@@ -316,7 +335,7 @@ class ImportAccount
|
||||
}
|
||||
$this->expectedType = $oldExpectedType;
|
||||
|
||||
// if search for an asset account, fall back to given "default account" (mandatory)
|
||||
// 4: if search for an asset account, fall back to given "default account" (mandatory)
|
||||
if ($this->expectedType === AccountType::ASSET) {
|
||||
$this->account = $this->repository->find($this->defaultAccountId);
|
||||
Log::debug(sprintf('Fall back to default account #%d "%s"', $this->account->id, $this->account->name));
|
||||
@@ -324,6 +343,7 @@ class ImportAccount
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5: then maybe, create one:
|
||||
Log::debug(sprintf('Found no account of type %s so must create one ourselves.', $this->expectedType));
|
||||
|
||||
$data = [
|
||||
|
||||
@@ -16,9 +16,8 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Converter\Amount;
|
||||
use FireflyIII\Import\Converter\ConverterInterface;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Import\MapperPreProcess\PreProcessorInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
use Log;
|
||||
use Steam;
|
||||
@@ -40,8 +39,6 @@ class ImportJournal
|
||||
public $category;
|
||||
/** @var string */
|
||||
public $description = '';
|
||||
/** @var Collection */
|
||||
public $errors;
|
||||
/** @var string */
|
||||
public $hash;
|
||||
/** @var array */
|
||||
@@ -50,18 +47,18 @@ class ImportJournal
|
||||
public $notes = '';
|
||||
/** @var ImportAccount */
|
||||
public $opposing;
|
||||
/** @var array */
|
||||
public $tags = [];
|
||||
/** @var string */
|
||||
private $amount = '0';
|
||||
private $amount;
|
||||
/** @var ImportCurrency */
|
||||
private $currency;
|
||||
public $currency;
|
||||
/** @var string */
|
||||
private $date = '';
|
||||
/** @var string */
|
||||
private $externalId = '';
|
||||
/** @var array */
|
||||
private $modifiers = [];
|
||||
/** @var array */
|
||||
private $tags = [];
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
@@ -70,7 +67,6 @@ class ImportJournal
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->errors = new Collection;
|
||||
$this->asset = new ImportAccount;
|
||||
$this->opposing = new ImportAccount;
|
||||
$this->bill = new ImportBill;
|
||||
@@ -87,45 +83,29 @@ class ImportJournal
|
||||
$this->modifiers[] = $modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TransactionJournal
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createTransactionJournal(): TransactionJournal
|
||||
{
|
||||
exit('does not work yet');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAmount(): string
|
||||
{
|
||||
|
||||
/** @var ConverterInterface $amountConverter */
|
||||
$amountConverter = app(Amount::class);
|
||||
$this->amount = $amountConverter->convert($this->amount);
|
||||
// modify
|
||||
foreach ($this->modifiers as $modifier) {
|
||||
$class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role'])));
|
||||
/** @var ConverterInterface $converter */
|
||||
$converter = app($class);
|
||||
if ($converter->convert($modifier['value']) === -1) {
|
||||
$this->amount = Steam::negative($this->amount);
|
||||
if (is_null($this->amount)) {
|
||||
/** @var ConverterInterface $amountConverter */
|
||||
$amountConverter = app(Amount::class);
|
||||
$this->amount = $amountConverter->convert($this->amount);
|
||||
// modify
|
||||
foreach ($this->modifiers as $modifier) {
|
||||
$class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role'])));
|
||||
/** @var ConverterInterface $converter */
|
||||
$converter = app($class);
|
||||
if ($converter->convert($modifier['value']) === -1) {
|
||||
$this->amount = Steam::negative($this->amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImportCurrency
|
||||
*/
|
||||
public function getCurrency(): ImportCurrency
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $format
|
||||
*
|
||||
@@ -269,7 +249,7 @@ class ImportJournal
|
||||
break;
|
||||
case 'tags-comma':
|
||||
case 'tags-space':
|
||||
$this->tags[] = $array;
|
||||
$this->setTags($array);
|
||||
break;
|
||||
case 'date-interest':
|
||||
$this->metaDates['interest_date'] = $array['value'];
|
||||
@@ -282,4 +262,18 @@ class ImportJournal
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*/
|
||||
private function setTags(array $array): void
|
||||
{
|
||||
$preProcessorClass = config(sprintf('csv.import_roles.%s.pre-process-mapper', $array['role']));
|
||||
/** @var PreProcessorInterface $preProcessor */
|
||||
$preProcessor = app(sprintf('\FireflyIII\Import\MapperPreProcess\%s', $preProcessorClass));
|
||||
$tags = $preProcessor->run($array['value']);
|
||||
$this->tags = array_merge($this->tags, $tags);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,28 +11,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Storage;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Object\ImportAccount;
|
||||
use FireflyIII\Import\Object\ImportJournal;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Steam;
|
||||
|
||||
/**
|
||||
* Is capable of storing individual ImportJournal objects.
|
||||
@@ -42,16 +26,16 @@ use Steam;
|
||||
*/
|
||||
class ImportStorage
|
||||
{
|
||||
use ImportSupport;
|
||||
|
||||
/** @var Collection */
|
||||
public $errors;
|
||||
/** @var Collection */
|
||||
public $journals;
|
||||
/** @var CurrencyRepositoryInterface */
|
||||
private $currencyRepository;
|
||||
/** @var int */
|
||||
protected $defaultCurrencyId = 1;
|
||||
/** @var string */
|
||||
private $dateFormat = 'Ymd';
|
||||
/** @var TransactionCurrency */
|
||||
private $defaultCurrency;
|
||||
private $dateFormat = 'Ymd'; // yes, hard coded
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
/** @var Collection */
|
||||
@@ -59,6 +43,9 @@ class ImportStorage
|
||||
/** @var Collection */
|
||||
private $rules;
|
||||
|
||||
/** @var array */
|
||||
private $transfers = [];
|
||||
|
||||
/**
|
||||
* ImportStorage constructor.
|
||||
*/
|
||||
@@ -82,11 +69,11 @@ class ImportStorage
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$repository->setUser($job->user);
|
||||
$this->currencyRepository = $repository;
|
||||
$this->rules = $this->getUserRules();
|
||||
$this->job = $job;
|
||||
$currency = app('amount')->getDefaultCurrencyByUser($this->job->user);
|
||||
$this->defaultCurrencyId = $currency->id;
|
||||
$this->transfers = $this->getTransfers();
|
||||
$this->rules = $this->getRules();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,316 +85,82 @@ class ImportStorage
|
||||
}
|
||||
|
||||
/**
|
||||
* Do storage of import objects.
|
||||
* Do storage of import objects. Is the main function.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function store()
|
||||
public function store(): bool
|
||||
{
|
||||
$this->defaultCurrency = Amount::getDefaultCurrencyByUser($this->job->user);
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
* @var ImportJournal $object
|
||||
*/
|
||||
foreach ($this->objects as $index => $object) {
|
||||
try {
|
||||
$this->storeImportJournal($index, $object);
|
||||
} catch (FireflyException $e) {
|
||||
$this->errors->push($e->getMessage());
|
||||
Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage()));
|
||||
$this->objects->each(
|
||||
function (ImportJournal $importJournal, int $index) {
|
||||
try {
|
||||
$this->storeImportJournal($index, $importJournal);
|
||||
} catch (FireflyException $e) {
|
||||
$this->errors->push($e->getMessage());
|
||||
Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
Log::info('ImportStorage has finished.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function applyRules(TransactionJournal $journal): bool
|
||||
{
|
||||
if ($this->rules->count() > 0) {
|
||||
/** @var Rule $rule */
|
||||
foreach ($this->rules as $rule) {
|
||||
Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id));
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($journal);
|
||||
|
||||
if ($rule->stop_processing) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $journalId
|
||||
* @param int $accountId
|
||||
* @param int $currencyId
|
||||
* @param string $amount
|
||||
* @param int $index
|
||||
* @param ImportJournal $importJournal
|
||||
*
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createTransaction(int $journalId, int $accountId, int $currencyId, string $amount): bool
|
||||
{
|
||||
$transaction = new Transaction;
|
||||
$transaction->account_id = $accountId;
|
||||
$transaction->transaction_journal_id = $journalId;
|
||||
$transaction->transaction_currency_id = $currencyId;
|
||||
$transaction->amount = $amount;
|
||||
$transaction->save();
|
||||
if (is_null($transaction->id)) {
|
||||
$errorText = join(', ', $transaction->getErrors()->all());
|
||||
throw new FireflyException($errorText);
|
||||
}
|
||||
Log::debug(sprintf('Created transaction with ID #%d, account #%d, amount %s', $transaction->id, $accountId, $amount));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $set
|
||||
* @param ImportJournal $importJournal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function filterTransferSet(Collection $set, ImportJournal $importJournal): bool
|
||||
{
|
||||
$amount = Steam::positive($importJournal->getAmount());
|
||||
$asset = $importJournal->asset->getAccount();
|
||||
$opposing = $this->getOpposingAccount($importJournal->opposing, $amount);
|
||||
$description = $importJournal->getDescription();
|
||||
|
||||
$filtered = $set->filter(
|
||||
function (TransactionJournal $journal) use ($asset, $opposing, $description) {
|
||||
$match = true;
|
||||
$original = [app('steam')->tryDecrypt($journal->source_name), app('steam')->tryDecrypt($journal->destination_name)];
|
||||
$compare = [$asset->name, $opposing->name];
|
||||
sort($original);
|
||||
sort($compare);
|
||||
|
||||
// description does not match? Then cannot be duplicate.
|
||||
if ($journal->description !== $description) {
|
||||
$match = false;
|
||||
}
|
||||
// not both accounts in journal? Then cannot be duplicate.
|
||||
if ($original !== $compare) {
|
||||
$match = false;
|
||||
}
|
||||
|
||||
if ($match) {
|
||||
return $journal;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
if (count($filtered) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJournal $importJournal
|
||||
*
|
||||
* @param Account $account
|
||||
*
|
||||
* @return TransactionCurrency
|
||||
*/
|
||||
private function getCurrency(ImportJournal $importJournal, Account $account): TransactionCurrency
|
||||
{
|
||||
// start with currency pref of account, if any:
|
||||
$currency = $this->currencyRepository->find(intval($account->getMeta('currency_id')));
|
||||
if (!is_null($currency->id)) {
|
||||
return $currency;
|
||||
}
|
||||
|
||||
// use given currency
|
||||
$currency = $importJournal->getCurrency()->getTransactionCurrency();
|
||||
if (!is_null($currency->id)) {
|
||||
return $currency;
|
||||
}
|
||||
|
||||
// backup to default
|
||||
$currency = $this->defaultCurrency;
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportAccount $account
|
||||
* @param $amount
|
||||
*
|
||||
* @return Account
|
||||
*/
|
||||
private function getOpposingAccount(ImportAccount $account, $amount): Account
|
||||
{
|
||||
if (bccomp($amount, '0') === -1) {
|
||||
Log::debug(sprintf('%s is negative, create opposing expense account.', $amount));
|
||||
$account->setExpectedType(AccountType::EXPENSE);
|
||||
|
||||
return $account->getAccount();
|
||||
}
|
||||
Log::debug(sprintf('%s is positive, create opposing revenue account.', $amount));
|
||||
// amount is positive, it's a deposit, opposing is an revenue:
|
||||
$account->setExpectedType(AccountType::REVENUE);
|
||||
|
||||
$databaseAccount = $account->getAccount();
|
||||
|
||||
return $databaseAccount;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $amount
|
||||
*
|
||||
* @return TransactionType
|
||||
*/
|
||||
private function getTransactionType(string $amount): TransactionType
|
||||
{
|
||||
$transactionType = new TransactionType();
|
||||
// amount is negative, it's a withdrawal, opposing is an expense:
|
||||
if (bccomp($amount, '0') === -1) {
|
||||
$transactionType = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
|
||||
}
|
||||
if (bccomp($amount, '0') === 1) {
|
||||
$transactionType = TransactionType::whereType(TransactionType::DEPOSIT)->first();
|
||||
}
|
||||
|
||||
return $transactionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
private function getUserRules(): Collection
|
||||
{
|
||||
$set = Rule::distinct()
|
||||
->where('rules.user_id', $this->job->user->id)
|
||||
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_groups.active', 1)
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', 1)
|
||||
->orderBy('rule_groups.order', 'ASC')
|
||||
->orderBy('rules.order', 'ASC')
|
||||
->get(['rules.*', 'rule_groups.order']);
|
||||
Log::debug(sprintf('Found %d user rules.', $set->count()));
|
||||
|
||||
return $set;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Bill $bill
|
||||
*/
|
||||
private function storeBill(TransactionJournal $journal, Bill $bill)
|
||||
{
|
||||
if (!is_null($bill->id)) {
|
||||
Log::debug(sprintf('Linked bill #%d to journal #%d', $bill->id, $journal->id));
|
||||
$journal->bill()->associate($bill);
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Budget $budget
|
||||
*/
|
||||
private function storeBudget(TransactionJournal $journal, Budget $budget)
|
||||
{
|
||||
if (!is_null($budget->id)) {
|
||||
Log::debug(sprintf('Linked budget #%d to journal #%d', $budget->id, $journal->id));
|
||||
$journal->budgets()->save($budget);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Category $category
|
||||
*/
|
||||
private function storeCategory(TransactionJournal $journal, Category $category)
|
||||
{
|
||||
|
||||
if (!is_null($category->id)) {
|
||||
Log::debug(sprintf('Linked category #%d to journal #%d', $category->id, $journal->id));
|
||||
$journal->categories()->save($category);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function storeImportJournal(int $index, ImportJournal $importJournal): bool
|
||||
protected function storeImportJournal(int $index, ImportJournal $importJournal): bool
|
||||
{
|
||||
Log::debug(sprintf('Going to store object #%d with description "%s"', $index, $importJournal->getDescription()));
|
||||
$importJournal->asset->setDefaultAccountId($this->job->configuration['import-account']);
|
||||
$asset = $importJournal->asset->getAccount();
|
||||
$amount = $importJournal->getAmount();
|
||||
$currency = $this->getCurrency($importJournal, $asset);
|
||||
$date = $importJournal->getDate($this->dateFormat);
|
||||
$transactionType = $this->getTransactionType($amount);
|
||||
$opposing = $this->getOpposingAccount($importJournal->opposing, $amount);
|
||||
|
||||
// if opposing is an asset account, it's a transfer:
|
||||
if ($opposing->accountType->type === AccountType::ASSET) {
|
||||
Log::debug(sprintf('Opposing account #%d %s is an asset account, make transfer.', $opposing->id, $opposing->name));
|
||||
$transactionType = TransactionType::whereType(TransactionType::TRANSFER)->first();
|
||||
}
|
||||
|
||||
// verify that opposing account is of the correct type:
|
||||
if ($opposing->accountType->type === AccountType::EXPENSE && $transactionType->type !== TransactionType::WITHDRAWAL) {
|
||||
$message = sprintf('Row #%d is imported as a %s but opposing is an expense account. This cannot be!', $index, $transactionType->type);
|
||||
Log::error($message);
|
||||
throw new FireflyException($message);
|
||||
|
||||
}
|
||||
$assetAccount = $importJournal->asset->getAccount();
|
||||
$amount = $importJournal->getAmount();
|
||||
$currencyId = $this->getCurrencyId($importJournal);
|
||||
$foreignCurrencyId = $this->getForeignCurrencyId($importJournal, $currencyId);
|
||||
$date = $importJournal->getDate($this->dateFormat)->format('Y-m-d');
|
||||
$opposingAccount = $this->getOpposingAccount($importJournal->opposing, $assetAccount->id, $amount);
|
||||
$transactionType = $this->getTransactionType($amount, $opposingAccount);
|
||||
$description = $importJournal->getDescription();
|
||||
|
||||
/*** First step done! */
|
||||
$this->job->addStepsDone(1);
|
||||
|
||||
// could be that transfer is double: verify this.
|
||||
if ($this->verifyDoubleTransfer($transactionType, $importJournal)) {
|
||||
// add three steps:
|
||||
/**
|
||||
* Check for double transfer.
|
||||
*/
|
||||
$parameters = [
|
||||
'type' => $transactionType,
|
||||
'description' => $description,
|
||||
'amount' => $amount,
|
||||
'date' => $date,
|
||||
'asset' => $assetAccount->name,
|
||||
'opposing' => $opposingAccount->name,
|
||||
];
|
||||
if ($this->isDoubleTransfer($parameters) || $this->hashAlreadyImported($importJournal->hash)) {
|
||||
$this->job->addStepsDone(3);
|
||||
// throw error
|
||||
throw new FireflyException('Detected a possible duplicate, skip this one.');
|
||||
|
||||
}
|
||||
unset($parameters);
|
||||
|
||||
// create a journal:
|
||||
$journal = new TransactionJournal;
|
||||
$journal->user_id = $this->job->user_id;
|
||||
$journal->transaction_type_id = $transactionType->id;
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
$journal->description = $importJournal->getDescription();
|
||||
$journal->date = $date->format('Y-m-d');
|
||||
$journal->order = 0;
|
||||
$journal->tag_count = 0;
|
||||
$journal->completed = false;
|
||||
// store journal and create transactions:
|
||||
$parameters = [
|
||||
'type' => $transactionType,
|
||||
'currency' => $currencyId,
|
||||
'foreign_currency' => $foreignCurrencyId,
|
||||
'asset' => $assetAccount,
|
||||
'opposing' => $opposingAccount,
|
||||
'description' => $description,
|
||||
'date' => $date,
|
||||
'hash' => $importJournal->hash,
|
||||
'amount' => $amount,
|
||||
|
||||
if (!$journal->save()) {
|
||||
$errorText = join(', ', $journal->getErrors()->all());
|
||||
// add three steps:
|
||||
$this->job->addStepsDone(3);
|
||||
// throw error
|
||||
throw new FireflyException($errorText);
|
||||
}
|
||||
|
||||
// save meta data:
|
||||
$journal->setMeta('importHash', $importJournal->hash);
|
||||
Log::debug(sprintf('Created journal with ID #%d', $journal->id));
|
||||
|
||||
// create transactions:
|
||||
$this->createTransaction($journal->id, $asset->id, $currency->id, $amount);
|
||||
$this->createTransaction($journal->id, $opposing->id, $currency->id, Steam::opposite($amount));
|
||||
];
|
||||
$journal = $this->storeJournal($parameters);
|
||||
unset($parameters);
|
||||
|
||||
/*** Another step done! */
|
||||
$this->job->addStepsDone(1);
|
||||
@@ -417,94 +170,57 @@ class ImportStorage
|
||||
$this->storeBudget($journal, $importJournal->budget->getBudget());
|
||||
$this->storeBill($journal, $importJournal->bill->getBill());
|
||||
$this->storeMeta($journal, $importJournal->metaDates);
|
||||
|
||||
// sepa thing as note:
|
||||
if (strlen($importJournal->notes) > 0) {
|
||||
$journal->setMeta('notes', $importJournal->notes);
|
||||
}
|
||||
$journal->setMeta('notes', $importJournal->notes);
|
||||
$this->storeTags($importJournal->tags, $journal);
|
||||
|
||||
// set journal completed:
|
||||
$journal->completed = true;
|
||||
$journal->save();
|
||||
|
||||
/*** Another step done! */
|
||||
$this->job->addStepsDone(1);
|
||||
|
||||
// run rules:
|
||||
$this->applyRules($journal);
|
||||
/*** Another step done! */
|
||||
$this->job->addStepsDone(1);
|
||||
$this->journals->push($journal);
|
||||
|
||||
Log::info(
|
||||
sprintf(
|
||||
'Imported new journal #%d with description "%s" and amount %s %s.', $journal->id, $journal->description, $journal->transactionCurrency->code,
|
||||
$amount
|
||||
)
|
||||
);
|
||||
Log::info(sprintf('Imported new journal #%d: "%s", amount %s %s.', $journal->id, $journal->description, $journal->transactionCurrency->code, $amount));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $dates
|
||||
*/
|
||||
private function storeMeta(TransactionJournal $journal, array $dates)
|
||||
{
|
||||
// all other date fields as meta thing:
|
||||
foreach ($dates as $name => $value) {
|
||||
try {
|
||||
$date = new Carbon($value);
|
||||
$journal->setMeta($name, $date);
|
||||
} catch (\Exception $e) {
|
||||
// don't care, ignore:
|
||||
Log::warning(sprintf('Could not parse "%s" into a valid Date object for field %s', $value, $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the given transaction is a transfer and if so, if it might be a duplicate of an already imported transfer.
|
||||
* This is important for import files that cover multiple accounts (and include both A<>B and B<>A transactions).
|
||||
*
|
||||
* @param TransactionType $transactionType
|
||||
* @param ImportJournal $importJournal
|
||||
* @param array $parameters
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function verifyDoubleTransfer(TransactionType $transactionType, ImportJournal $importJournal): bool
|
||||
private function isDoubleTransfer(array $parameters): bool
|
||||
{
|
||||
if ($transactionType->type === TransactionType::TRANSFER) {
|
||||
$amount = Steam::positive($importJournal->getAmount());
|
||||
$date = $importJournal->getDate($this->dateFormat);
|
||||
$set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->leftJoin(
|
||||
'transactions AS source', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 'source.transaction_journal_id')->where('source.amount', '<', 0);
|
||||
}
|
||||
)
|
||||
->leftJoin(
|
||||
'transactions AS destination', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 'destination.transaction_journal_id')->where(
|
||||
'destination.amount', '>', 0
|
||||
);
|
||||
}
|
||||
)
|
||||
->leftJoin('accounts as source_accounts', 'source.account_id', '=', 'source_accounts.id')
|
||||
->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
|
||||
->where('transaction_journals.user_id', $this->job->user_id)
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->where('transaction_journals.date', $date->format('Y-m-d'))
|
||||
->where('destination.amount', $amount)
|
||||
->get(
|
||||
['transaction_journals.id', 'transaction_journals.encrypted', 'transaction_journals.description',
|
||||
'source_accounts.name as source_name', 'destination_accounts.name as destination_name']
|
||||
);
|
||||
|
||||
return $this->filterTransferSet($set, $importJournal);
|
||||
if ($parameters['type'] !== TransactionType::TRANSFER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$amount = app('steam')->positive($parameters['amount']);
|
||||
$names = [$parameters['asset'], $parameters['opposing']];
|
||||
sort($names);
|
||||
|
||||
return false;
|
||||
foreach ($this->transfers as $transfer) {
|
||||
if ($parameters['description'] !== $transfer['description']) {
|
||||
return false;
|
||||
}
|
||||
if ($names !== $transfer['names']) {
|
||||
return false;
|
||||
}
|
||||
if (bccomp($amount, $transfer['amount']) !== 0) {
|
||||
return false;
|
||||
}
|
||||
if ($parameters['date'] !== $transfer['date']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
444
app/Import/Storage/ImportSupport.php
Normal file
444
app/Import/Storage/ImportSupport.php
Normal file
@@ -0,0 +1,444 @@
|
||||
<?php
|
||||
/**
|
||||
* ImportSupport.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\Import\Storage;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Object\ImportAccount;
|
||||
use FireflyIII\Import\Object\ImportJournal;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
trait ImportSupport
|
||||
{
|
||||
/** @var int */
|
||||
protected $defaultCurrencyId = 1;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function applyRules(TransactionJournal $journal): bool
|
||||
{
|
||||
if ($this->rules->count() > 0) {
|
||||
$this->rules->each(
|
||||
function (Rule $rule) use ($journal) {
|
||||
Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id));
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($journal);
|
||||
if ($rule->stop_processing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
*
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createTransaction(array $parameters): bool
|
||||
{
|
||||
$transaction = new Transaction;
|
||||
$transaction->account_id = $parameters['account'];
|
||||
$transaction->transaction_journal_id = $parameters['id'];
|
||||
$transaction->transaction_currency_id = $parameters['currency'];
|
||||
$transaction->amount = $parameters['amount'];
|
||||
$transaction->foreign_currency_id = $parameters['foreign_currency'];
|
||||
$transaction->foreign_amount = $parameters['foreign_amount'];
|
||||
$transaction->save();
|
||||
if (is_null($transaction->id)) {
|
||||
$errorText = join(', ', $transaction->getErrors()->all());
|
||||
throw new FireflyException($errorText);
|
||||
}
|
||||
Log::debug(sprintf('Created transaction with ID #%d, account #%d, amount %s', $transaction->id, $parameters['account'], $parameters['amount']));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method finds out what the import journal's currency should be. The account itself
|
||||
* is favoured (and usually it stops there). If no preference is found, the journal has a say
|
||||
* and thirdly the default currency is used.
|
||||
*
|
||||
* @param ImportJournal $importJournal
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getCurrencyId(ImportJournal $importJournal): int
|
||||
{
|
||||
// start with currency pref of account, if any:
|
||||
$account = $importJournal->asset->getAccount();
|
||||
$currencyId = intval($account->getMeta('currency_id'));
|
||||
if ($currencyId > 0) {
|
||||
return $currencyId;
|
||||
}
|
||||
|
||||
// use given currency
|
||||
$currency = $importJournal->currency->getTransactionCurrency();
|
||||
if (!is_null($currency->id)) {
|
||||
return $currency->id;
|
||||
}
|
||||
|
||||
// backup to default
|
||||
$currency = $this->defaultCurrencyId;
|
||||
|
||||
return $currency;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The foreign currency is only returned when the journal has a different value from the
|
||||
* currency id (see other method).
|
||||
*
|
||||
* @param ImportJournal $importJournal
|
||||
* @param int $currencyId
|
||||
*
|
||||
* @see ImportSupport::getCurrencyId
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
private function getForeignCurrencyId(ImportJournal $importJournal, int $currencyId): ?int
|
||||
{
|
||||
// use given currency by import journal.
|
||||
$currency = $importJournal->currency->getTransactionCurrency();
|
||||
if (!is_null($currency->id) && $currency->id !== $currencyId) {
|
||||
return $currency->id;
|
||||
}
|
||||
|
||||
// return null, because no different:
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The search for the opposing account is complex. Firstly, we forbid the ImportAccount to resolve into the asset
|
||||
* account to prevent a situation where the transaction flows from A to A. Given the amount, we "expect" the opposing
|
||||
* account to be an expense or a revenue account. However, the mapping given by the user may return something else
|
||||
* entirely (usually an asset account). So whatever the expectation, the result may be anything.
|
||||
*
|
||||
* When the result does not match the expected type (a negative amount cannot be linked to a revenue account) the next step
|
||||
* will return an error.
|
||||
*
|
||||
* @param ImportAccount $account
|
||||
* @param int $forbiddenAccount
|
||||
* @param string $amount
|
||||
*
|
||||
* @see ImportSupport::getTransactionType
|
||||
*
|
||||
* @return Account
|
||||
*/
|
||||
private function getOpposingAccount(ImportAccount $account, int $forbiddenAccount, string $amount): Account
|
||||
{
|
||||
$account->setForbiddenAccountId($forbiddenAccount);
|
||||
if (bccomp($amount, '0') === -1) {
|
||||
Log::debug(sprintf('%s is negative, create opposing expense account.', $amount));
|
||||
$account->setExpectedType(AccountType::EXPENSE);
|
||||
|
||||
return $account->getAccount();
|
||||
}
|
||||
Log::debug(sprintf('%s is positive, create opposing revenue account.', $amount));
|
||||
// amount is positive, it's a deposit, opposing is an revenue:
|
||||
$account->setExpectedType(AccountType::REVENUE);
|
||||
|
||||
$databaseAccount = $account->getAccount();
|
||||
|
||||
return $databaseAccount;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
private function getRules(): Collection
|
||||
{
|
||||
$set = Rule::distinct()
|
||||
->where('rules.user_id', $this->job->user->id)
|
||||
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_groups.active', 1)
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', 1)
|
||||
->orderBy('rule_groups.order', 'ASC')
|
||||
->orderBy('rules.order', 'ASC')
|
||||
->get(['rules.*', 'rule_groups.order']);
|
||||
Log::debug(sprintf('Found %d user rules.', $set->count()));
|
||||
|
||||
return $set;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the amount and the opposing account its easy to define which kind of transaction type should be associated with the new
|
||||
* import. This may however fail when there is an unexpected mismatch between the transaction type and the opposing account.
|
||||
*
|
||||
* @param string $amount
|
||||
* @param Account $account
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
* @see ImportSupport::getOpposingAccount()
|
||||
*/
|
||||
private function getTransactionType(string $amount, Account $account): string
|
||||
{
|
||||
$transactionType = '';
|
||||
// amount is negative, it's a withdrawal, opposing is an expense:
|
||||
if (bccomp($amount, '0') === -1) {
|
||||
$transactionType = TransactionType::WITHDRAWAL;
|
||||
}
|
||||
|
||||
if (bccomp($amount, '0') === 1) {
|
||||
$transactionType = TransactionType::DEPOSIT;
|
||||
}
|
||||
|
||||
// if opposing is an asset account, it's a transfer:
|
||||
if ($account->accountType->type === AccountType::ASSET) {
|
||||
Log::debug(sprintf('Opposing account #%d %s is an asset account, make transfer.', $account->id, $account->name));
|
||||
$transactionType = TransactionType::TRANSFER;
|
||||
}
|
||||
|
||||
// verify that opposing account is of the correct type:
|
||||
if ($account->accountType->type === AccountType::EXPENSE && $transactionType !== TransactionType::WITHDRAWAL) {
|
||||
$message = 'This row is imported as a withdrawal but opposing is an expense account. This cannot be!';
|
||||
Log::error($message);
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
|
||||
return $transactionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a collection of the current transfers in the system and some meta data for
|
||||
* this set. This can later be used to see if the journal that firefly is trying to import
|
||||
* is not already present.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTransfers(): array
|
||||
{
|
||||
$set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->leftJoin(
|
||||
'transactions AS source', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 'source.transaction_journal_id')->where('source.amount', '<', 0);
|
||||
}
|
||||
)
|
||||
->leftJoin(
|
||||
'transactions AS destination', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 'destination.transaction_journal_id')->where(
|
||||
'destination.amount', '>', 0
|
||||
);
|
||||
}
|
||||
)
|
||||
->leftJoin('accounts as source_accounts', 'source.account_id', '=', 'source_accounts.id')
|
||||
->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
|
||||
->where('transaction_journals.user_id', $this->job->user_id)
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->get(
|
||||
['transaction_journals.id', 'transaction_journals.encrypted', 'transaction_journals.description',
|
||||
'source_accounts.name as source_name', 'destination_accounts.name as destination_name', 'destination.amount'
|
||||
, 'transaction_journals.date']
|
||||
);
|
||||
$array = [];
|
||||
/** @var TransactionJournal $entry */
|
||||
foreach ($set as $entry) {
|
||||
$original = [app('steam')->tryDecrypt($entry->source_name), app('steam')->tryDecrypt($entry->destination_name)];
|
||||
sort($original);
|
||||
$array[] = [
|
||||
'names' => $original,
|
||||
'amount' => $entry->amount,
|
||||
'date' => $entry->date->format('Y-m-d'),
|
||||
'description' => $entry->description,
|
||||
];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the import journal has not been imported before.
|
||||
*
|
||||
* @param string $hash
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hashAlreadyImported(string $hash): bool
|
||||
{
|
||||
$json = json_encode($hash);
|
||||
$entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('data', $json)
|
||||
->where('name', 'importHash')
|
||||
->first();
|
||||
if (!is_null($entry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Bill $bill
|
||||
*/
|
||||
private function storeBill(TransactionJournal $journal, Bill $bill)
|
||||
{
|
||||
if (!is_null($bill->id)) {
|
||||
Log::debug(sprintf('Linked bill #%d to journal #%d', $bill->id, $journal->id));
|
||||
$journal->bill()->associate($bill);
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Budget $budget
|
||||
*/
|
||||
private function storeBudget(TransactionJournal $journal, Budget $budget)
|
||||
{
|
||||
if (!is_null($budget->id)) {
|
||||
Log::debug(sprintf('Linked budget #%d to journal #%d', $budget->id, $journal->id));
|
||||
$journal->budgets()->save($budget);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Category $category
|
||||
*/
|
||||
private function storeCategory(TransactionJournal $journal, Category $category)
|
||||
{
|
||||
|
||||
if (!is_null($category->id)) {
|
||||
Log::debug(sprintf('Linked category #%d to journal #%d', $category->id, $journal->id));
|
||||
$journal->categories()->save($category);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function storeJournal(array $parameters): TransactionJournal
|
||||
{
|
||||
// find transaction type:
|
||||
$transactionType = TransactionType::whereType($parameters['type'])->first();
|
||||
|
||||
// create a journal:
|
||||
$journal = new TransactionJournal;
|
||||
$journal->user_id = $this->job->user_id;
|
||||
$journal->transaction_type_id = $transactionType->id;
|
||||
$journal->transaction_currency_id = $parameters['currency'];
|
||||
$journal->description = $parameters['description'];
|
||||
$journal->date = $parameters['date'];
|
||||
$journal->order = 0;
|
||||
$journal->tag_count = 0;
|
||||
$journal->completed = false;
|
||||
|
||||
if (!$journal->save()) {
|
||||
$errorText = join(', ', $journal->getErrors()->all());
|
||||
// add three steps:
|
||||
$this->job->addStepsDone(3);
|
||||
// throw error
|
||||
throw new FireflyException($errorText);
|
||||
}
|
||||
// save meta data:
|
||||
$journal->setMeta('importHash', $parameters['hash']);
|
||||
Log::debug(sprintf('Created journal with ID #%d', $journal->id));
|
||||
|
||||
// create transactions:
|
||||
$one = [
|
||||
'id' => $journal->id,
|
||||
'account' => $parameters['asset']->id,
|
||||
'currency' => $parameters['currency'],
|
||||
'amount' => $parameters['amount'],
|
||||
'foreign_currency' => $parameters['foreign_currency'],
|
||||
'foreign_amount' => is_null($parameters['foreign_currency']) ? null : $parameters['amount'],
|
||||
];
|
||||
$opposite = app('steam')->opposite($parameters['amount']);
|
||||
$two = [
|
||||
'id' => $journal->id,
|
||||
'account' => $parameters['opposing']->id,
|
||||
'currency' => $parameters['currency'],
|
||||
'amount' => $opposite,
|
||||
'foreign_currency' => $parameters['foreign_currency'],
|
||||
'foreign_amount' => is_null($parameters['foreign_currency']) ? null : $opposite,
|
||||
];
|
||||
$this->createTransaction($one);
|
||||
$this->createTransaction($two);
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $dates
|
||||
*/
|
||||
private function storeMeta(TransactionJournal $journal, array $dates)
|
||||
{
|
||||
// all other date fields as meta thing:
|
||||
foreach ($dates as $name => $value) {
|
||||
try {
|
||||
$date = new Carbon($value);
|
||||
$journal->setMeta($name, $date);
|
||||
} catch (Exception $e) {
|
||||
// don't care, ignore:
|
||||
Log::warning(sprintf('Could not parse "%s" into a valid Date object for field %s', $value, $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $tags
|
||||
* @param TransactionJournal $journal
|
||||
*/
|
||||
private function storeTags(array $tags, TransactionJournal $journal): void
|
||||
{
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$repository->setUser($journal->user);
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$dbTag = $repository->findByTag($tag);
|
||||
if (is_null($dbTag->id)) {
|
||||
$dbTag = $repository->store(
|
||||
['tag' => $tag, 'date' => null, 'description' => null, 'latitude' => null, 'longitude' => null,
|
||||
'zoomLevel' => null, 'tagMode' => 'nothing']
|
||||
);
|
||||
}
|
||||
$journal->tags()->save($dbTag);
|
||||
Log::debug(sprintf('Linked tag %d ("%s") to journal #%d', $dbTag->id, $dbTag->tag, $journal->id));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* RegisteredUser.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.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Mail;
|
||||
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* RequestedNewPassword.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.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Mail;
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ use FireflyIII\Helpers\Report\ReportHelper;
|
||||
use FireflyIII\Helpers\Report\ReportHelperInterface;
|
||||
use FireflyIII\Repositories\User\UserRepository;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\Services\Password\PwndVerifier;
|
||||
use FireflyIII\Services\Password\Verifier;
|
||||
use FireflyIII\Support\Amount;
|
||||
use FireflyIII\Support\ExpandedForm;
|
||||
use FireflyIII\Support\FireflyConfig;
|
||||
@@ -147,6 +149,9 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
$this->app->bind(FiscalHelperInterface::class, FiscalHelper::class);
|
||||
$this->app->bind(BalanceReportHelperInterface::class, BalanceReportHelper::class);
|
||||
$this->app->bind(BudgetReportHelperInterface::class, BudgetReportHelper::class);
|
||||
|
||||
// password verifier thing
|
||||
$this->app->bind(Verifier::class, PwndVerifier::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
{
|
||||
// update the account:
|
||||
$account->name = $data['name'];
|
||||
$account->active = $data['active'] === '1' ? true : false;
|
||||
$account->active = $data['active'];
|
||||
$account->virtual_balance = $data['virtualBalance'];
|
||||
$account->iban = $data['iban'];
|
||||
$account->save();
|
||||
@@ -500,7 +500,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
private function filterIban(string $iban = null)
|
||||
private function filterIban(?string $iban)
|
||||
{
|
||||
if (is_null($iban)) {
|
||||
return null;
|
||||
|
||||
@@ -206,7 +206,8 @@ class AccountTasker implements AccountTaskerInterface
|
||||
$expenses[$opposingId]['count']++;
|
||||
}
|
||||
// do averages:
|
||||
foreach ($expenses as $key => $entry) {
|
||||
$keys = array_keys($expenses);
|
||||
foreach ($keys as $key) {
|
||||
if ($expenses[$key]['count'] > 1) {
|
||||
$expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count']));
|
||||
}
|
||||
|
||||
@@ -62,6 +62,36 @@ class AttachmentRepository implements AttachmentRepositoryInterface
|
||||
return $disk->exists($attachment->fileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
public function find(int $id): Attachment
|
||||
{
|
||||
$attachment = $this->user->attachments()->find($id);
|
||||
if (is_null($attachment)) {
|
||||
return new Attachment;
|
||||
}
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
public function findWithoutUser(int $id): Attachment
|
||||
{
|
||||
$attachment = Attachment::find($id);
|
||||
if (is_null($attachment)) {
|
||||
return new Attachment;
|
||||
}
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
|
||||
@@ -40,6 +40,20 @@ interface AttachmentRepositoryInterface
|
||||
*/
|
||||
public function exists(Attachment $attachment): bool;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
public function find(int $id): Attachment;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
public function findWithoutUser(int $id): Attachment;
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
|
||||
@@ -191,6 +191,9 @@ class JournalTasker implements JournalTaskerInterface
|
||||
$journalId = intval($transaction->transaction_journal_id);
|
||||
$identifier = intval($transaction->identifier);
|
||||
|
||||
// also add the virtual balance to the balance:
|
||||
$virtualBalance = strval($transaction->account->virtual_balance);
|
||||
|
||||
// go!
|
||||
$sum = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->where('account_id', $transaction->account_id)
|
||||
@@ -224,6 +227,6 @@ class JournalTasker implements JournalTaskerInterface
|
||||
}
|
||||
)->sum('transactions.amount');
|
||||
|
||||
return strval($sum);
|
||||
return bcadd(strval($sum), $virtualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy(RuleGroup $ruleGroup, RuleGroup $moveTo = null): bool
|
||||
public function destroy(RuleGroup $ruleGroup, ?RuleGroup $moveTo): bool
|
||||
{
|
||||
/** @var Rule $rule */
|
||||
foreach ($ruleGroup->rules as $rule) {
|
||||
|
||||
@@ -39,7 +39,7 @@ interface RuleGroupRepositoryInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy(RuleGroup $ruleGroup, RuleGroup $moveTo = null): bool;
|
||||
public function destroy(RuleGroup $ruleGroup, ?RuleGroup $moveTo): bool;
|
||||
|
||||
/**
|
||||
* @param int $ruleGroupId
|
||||
|
||||
@@ -51,21 +51,11 @@ class TagRepository implements TagRepositoryInterface
|
||||
|
||||
return false;
|
||||
}
|
||||
Log::debug(sprintf('Tag #%d connected', $tag->id));
|
||||
$journal->tags()->save($tag);
|
||||
$journal->save();
|
||||
|
||||
switch ($tag->tagMode) {
|
||||
case 'nothing':
|
||||
Log::debug(sprintf('Tag #%d connected', $tag->id));
|
||||
$journal->tags()->save($tag);
|
||||
$journal->save();
|
||||
|
||||
return true;
|
||||
case 'balancingAct':
|
||||
return $this->connectBalancingAct($journal, $tag);
|
||||
case 'advancePayment':
|
||||
return $this->connectAdvancePayment($journal, $tag);
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,7 +227,7 @@ class TagRepository implements TagRepositoryInterface
|
||||
$tag->latitude = $data['latitude'];
|
||||
$tag->longitude = $data['longitude'];
|
||||
$tag->zoomLevel = $data['zoomLevel'];
|
||||
$tag->tagMode = $data['tagMode'];
|
||||
$tag->tagMode = 'nothing';
|
||||
$tag->user()->associate($this->user);
|
||||
$tag->save();
|
||||
|
||||
@@ -253,7 +243,7 @@ class TagRepository implements TagRepositoryInterface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string
|
||||
public function sumOfTag(Tag $tag, ?Carbon $start, ?Carbon $end): string
|
||||
{
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
@@ -268,73 +258,6 @@ class TagRepository implements TagRepositoryInterface
|
||||
return strval($sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a tag become an advance payment?
|
||||
*
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function tagAllowAdvance(Tag $tag): bool
|
||||
{
|
||||
/*
|
||||
* If this tag is a balancing act, and it contains transfers, it cannot be
|
||||
* changed to an advancePayment.
|
||||
*/
|
||||
|
||||
if ($tag->tagMode === 'balancingAct' || $tag->tagMode === 'nothing') {
|
||||
foreach ($tag->transactionjournals as $journal) {
|
||||
if ($journal->isTransfer()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If this tag contains more than one expenses, it cannot become an advance payment.
|
||||
*/
|
||||
$count = 0;
|
||||
foreach ($tag->transactionjournals as $journal) {
|
||||
if ($journal->isWithdrawal()) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
if ($count > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a tag become a balancing act?
|
||||
*
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function tagAllowBalancing(Tag $tag): bool
|
||||
{
|
||||
/*
|
||||
* If has more than two transactions already, cannot become a balancing act:
|
||||
*/
|
||||
if ($tag->transactionjournals->count() > 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If any transaction is a deposit, cannot become a balancing act.
|
||||
*/
|
||||
foreach ($tag->transactionjournals as $journal) {
|
||||
if ($journal->isDeposit()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param array $data
|
||||
@@ -349,183 +272,9 @@ class TagRepository implements TagRepositoryInterface
|
||||
$tag->latitude = $data['latitude'];
|
||||
$tag->longitude = $data['longitude'];
|
||||
$tag->zoomLevel = $data['zoomLevel'];
|
||||
$tag->tagMode = $data['tagMode'];
|
||||
$tag->save();
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function connectAdvancePayment(TransactionJournal $journal, Tag $tag): bool
|
||||
{
|
||||
$type = $journal->transactionType->type;
|
||||
$withdrawals = $tag->transactionJournals()
|
||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::WITHDRAWAL)->count();
|
||||
$deposits = $tag->transactionJournals()
|
||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::DEPOSIT)->count();
|
||||
|
||||
if ($type === TransactionType::TRANSFER) { // advance payments cannot accept transfers:
|
||||
Log::error(sprintf('Journal #%d is a transfer and cannot connect to tag #%d', $journal->id, $tag->id));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// the first transaction to be attached to this tag is attached just like that:
|
||||
if ($withdrawals < 1 && $deposits < 1) {
|
||||
Log::debug(sprintf('Tag #%d has 0 withdrawals and 0 deposits so its fine.', $tag->id));
|
||||
$journal->tags()->save($tag);
|
||||
$journal->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if withdrawal and already has a withdrawal, return false:
|
||||
if ($type === TransactionType::WITHDRAWAL && $withdrawals > 0) {
|
||||
Log::error(sprintf('Journal #%d is a withdrawal but tag already has %d withdrawal(s).', $journal->id, $withdrawals));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// if already has transaction journals, must match ALL asset account id's:
|
||||
if ($deposits > 0 || $withdrawals === 1) {
|
||||
Log::debug('Need to match all asset accounts.');
|
||||
|
||||
return $this->matchAll($journal, $tag);
|
||||
}
|
||||
|
||||
// this statement is unreachable.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function connectBalancingAct(TransactionJournal $journal, Tag $tag): bool
|
||||
{
|
||||
$type = $journal->transactionType->type;
|
||||
$withdrawals = $tag->transactionJournals()
|
||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::WITHDRAWAL)->count();
|
||||
$transfers = $tag->transactionJournals()
|
||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)->count();
|
||||
|
||||
|
||||
Log::debug(sprintf('Journal #%d is a %s', $journal->id, $type));
|
||||
|
||||
// only if this is the only withdrawal.
|
||||
if ($type === TransactionType::WITHDRAWAL && $withdrawals < 1) {
|
||||
Log::debug('Will connect this journal because it is the only withdrawal in this tag.');
|
||||
$journal->tags()->save($tag);
|
||||
$journal->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
// and only if this is the only transfer
|
||||
if ($type === TransactionType::TRANSFER && $transfers < 1) {
|
||||
Log::debug('Will connect this journal because it is the only transfer in this tag.');
|
||||
$journal->tags()->save($tag);
|
||||
$journal->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::error(
|
||||
sprintf(
|
||||
'Tag #%d has %d withdrawals and %d transfers and cannot contain %s #%d',
|
||||
$tag->id, $withdrawals, $transfers, $type, $journal->id
|
||||
)
|
||||
);
|
||||
|
||||
// ignore expense
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The incoming journal ($journal)'s accounts (source accounts for a withdrawal, destination accounts for a deposit)
|
||||
* must match the already existing transaction's accounts exactly.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
* @param Tag $tag
|
||||
*
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function matchAll(TransactionJournal $journal, Tag $tag): bool
|
||||
{
|
||||
$journalSources = join(',', array_unique($journal->sourceAccountList()->pluck('id')->toArray()));
|
||||
$journalDestinations = join(',', array_unique($journal->destinationAccountList()->pluck('id')->toArray()));
|
||||
$match = true;
|
||||
$journals = $tag->transactionJournals()->get(['transaction_journals.*']);
|
||||
|
||||
Log::debug(sprintf('Tag #%d has %d journals to verify:', $tag->id, $journals->count()));
|
||||
|
||||
/** @var TransactionJournal $existing */
|
||||
foreach ($journals as $existing) {
|
||||
Log::debug(sprintf('Now existingcomparing new journal #%d to existing journal #%d', $journal->id, $existing->id));
|
||||
// $checkAccount is the source_account for a withdrawal
|
||||
// $checkAccount is the destination_account for a deposit
|
||||
$existingSources = join(',', array_unique($existing->sourceAccountList()->pluck('id')->toArray()));
|
||||
$existingDestinations = join(',', array_unique($existing->destinationAccountList()->pluck('id')->toArray()));
|
||||
|
||||
if ($existing->isWithdrawal() && $existingSources !== $journalDestinations) {
|
||||
/*
|
||||
* There can only be one withdrawal. And the source account(s) of the withdrawal
|
||||
* must be the same as the destination of the deposit. Because any transaction that arrives
|
||||
* here ($journal) must be a deposit.
|
||||
*/
|
||||
Log::debug(sprintf('Existing journal #%d is a withdrawal.', $existing->id));
|
||||
Log::debug(sprintf('New journal #%d must have these destination accounts: %s', $journal->id, $existingSources));
|
||||
Log::debug(sprintf('New journal #%d actually these destination accounts: %s', $journal->id, $journalDestinations));
|
||||
Log::debug('So match is FALSE');
|
||||
|
||||
$match = false;
|
||||
}
|
||||
if ($existing->isDeposit() && $journal->isDeposit() && $existingDestinations !== $journalDestinations) {
|
||||
/*
|
||||
* There can be multiple deposits.
|
||||
* They must have the destination the same as the other deposits.
|
||||
*/
|
||||
Log::debug(sprintf('Existing journal #%d is a deposit.', $existing->id));
|
||||
Log::debug(sprintf('Journal #%d must have these destination accounts: %s', $journal->id, $existingDestinations));
|
||||
Log::debug(sprintf('Journal #%d actually these destination accounts: %s', $journal->id, $journalDestinations));
|
||||
Log::debug('So match is FALSE');
|
||||
|
||||
$match = false;
|
||||
}
|
||||
|
||||
if ($existing->isDeposit() && $journal->isWithdrawal() && $existingDestinations !== $journalSources) {
|
||||
/*
|
||||
* There can be one new withdrawal only. It must have the same source as the existing has destination.
|
||||
*/
|
||||
Log::debug(sprintf('Existing journal #%d is a deposit.', $existing->id));
|
||||
Log::debug(sprintf('Journal #%d must have these source accounts: %s', $journal->id, $existingDestinations));
|
||||
Log::debug(sprintf('Journal #%d actually these source accounts: %s', $journal->id, $journalSources));
|
||||
Log::debug('So match is FALSE');
|
||||
|
||||
$match = false;
|
||||
}
|
||||
|
||||
}
|
||||
if ($match) {
|
||||
Log::debug(sprintf('Match is true, connect journal #%d with tag #%d.', $journal->id, $tag->id));
|
||||
$journal->tags()->save($tag);
|
||||
$journal->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,21 +133,7 @@ interface TagRepositoryInterface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function tagAllowAdvance(Tag $tag): bool;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function tagAllowBalancing(Tag $tag): bool;
|
||||
public function sumOfTag(Tag $tag, ?Carbon $start, ?Carbon $end): string;
|
||||
|
||||
/**
|
||||
* Update a tag.
|
||||
|
||||
51
app/Services/Password/PwndVerifier.php
Normal file
51
app/Services/Password/PwndVerifier.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* PwndVerifier.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\Password;
|
||||
|
||||
use Log;
|
||||
use Requests;
|
||||
use Requests_Exception;
|
||||
|
||||
/**
|
||||
* Class PwndVerifier
|
||||
*
|
||||
* @package FireflyIII\Services\Password
|
||||
*/
|
||||
class PwndVerifier implements Verifier
|
||||
{
|
||||
|
||||
/**
|
||||
* Verify the given password against (some) service.
|
||||
*
|
||||
* @param string $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validPassword(string $password): bool
|
||||
{
|
||||
$hash = sha1($password);
|
||||
$uri = sprintf('https://haveibeenpwned.com/api/v2/pwnedpassword/%s', $hash);
|
||||
$opt = ['useragent' => 'Firefly III v' . config('firefly.version'), 'timeout' => 2];
|
||||
|
||||
try {
|
||||
$result = Requests::get($uri, ['originalPasswordIsAHash' => 'true'], $opt);
|
||||
} catch (Requests_Exception $e) {
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('Status code returned is %d', $result->status_code));
|
||||
if ($result->status_code === 404) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
30
app/Services/Password/Verifier.php
Normal file
30
app/Services/Password/Verifier.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Verifier.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\Password;
|
||||
|
||||
/**
|
||||
* Interface Verifier
|
||||
*
|
||||
* @package FireflyIII\Services\Password
|
||||
*/
|
||||
interface Verifier
|
||||
{
|
||||
/**
|
||||
* Verify the given password against (some) service.
|
||||
*
|
||||
* @param string $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validPassword(string $password): bool;
|
||||
|
||||
}
|
||||
@@ -15,8 +15,6 @@ namespace FireflyIII\Support;
|
||||
|
||||
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Collection;
|
||||
use Preferences as Prefs;
|
||||
|
||||
@@ -97,25 +95,6 @@ class CacheProperties
|
||||
{
|
||||
$this->md5 = '';
|
||||
foreach ($this->properties as $property) {
|
||||
|
||||
if ($property instanceof Collection || $property instanceof EloquentCollection) {
|
||||
$this->md5 .= json_encode($property->toArray());
|
||||
continue;
|
||||
}
|
||||
if ($property instanceof Carbon) {
|
||||
$this->md5 .= $property->toRfc3339String();
|
||||
continue;
|
||||
}
|
||||
if (is_object($property)) {
|
||||
$this->md5 .= $property->__toString();
|
||||
}
|
||||
if (is_bool($property) && $property === false) {
|
||||
$this->md5 .= 'false';
|
||||
}
|
||||
if (is_bool($property) && $property === true) {
|
||||
$this->md5 .= 'true';
|
||||
}
|
||||
|
||||
$this->md5 .= json_encode($property);
|
||||
}
|
||||
$this->md5 = md5($this->md5);
|
||||
|
||||
@@ -40,8 +40,6 @@ class ExpandedForm
|
||||
*/
|
||||
public function amount(string $name, $value = null, array $options = []): string
|
||||
{
|
||||
$options['min'] = '0.01';
|
||||
|
||||
return $this->currencyField($name, 'amount', $value, $options);
|
||||
}
|
||||
|
||||
@@ -54,8 +52,6 @@ class ExpandedForm
|
||||
*/
|
||||
public function amountSmall(string $name, $value = null, array $options = []): string
|
||||
{
|
||||
$options['min'] = '0.01';
|
||||
|
||||
return $this->currencyField($name, 'amount-small', $value, $options);
|
||||
}
|
||||
|
||||
@@ -279,7 +275,6 @@ class ExpandedForm
|
||||
$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']);
|
||||
|
||||
@@ -27,6 +27,13 @@ interface ConfigurationInterface
|
||||
*/
|
||||
public function getData(): array;
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string;
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
|
||||
@@ -61,6 +61,16 @@ class Initial implements ConfigurationInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
@@ -124,7 +134,8 @@ class Initial implements ConfigurationInterface
|
||||
{
|
||||
// loop specifics.
|
||||
if (isset($data['specifics']) && is_array($data['specifics'])) {
|
||||
foreach ($data['specifics'] as $name => $enabled) {
|
||||
$names = array_keys($data['specifics']);
|
||||
foreach ($names as $name) {
|
||||
// verify their content.
|
||||
$className = sprintf('FireflyIII\Import\Specifics\%s', $name);
|
||||
if (class_exists($className)) {
|
||||
|
||||
@@ -90,10 +90,12 @@ class Map implements ConfigurationInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($this->data as $index => $entry) {
|
||||
$setIndexes = array_keys($this->data);
|
||||
foreach ($setIndexes as $index) {
|
||||
$this->data[$index]['values'] = array_unique($this->data[$index]['values']);
|
||||
asort($this->data[$index]['values']);
|
||||
}
|
||||
unset($setIndexes);
|
||||
|
||||
// save number of rows, thus number of steps, in job:
|
||||
$steps = $rowIndex * 5;
|
||||
@@ -106,6 +108,16 @@ class Map implements ConfigurationInterface
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
@@ -223,8 +235,8 @@ class Map implements ConfigurationInterface
|
||||
{
|
||||
// run specifics here:
|
||||
// and this is the point where the specifix go to work.
|
||||
foreach ($this->configuration['specifics'] as $name => $enabled) {
|
||||
|
||||
$names = array_keys($this->job->configuration['specifics']);
|
||||
foreach ($names as $name) {
|
||||
if (!in_array($name, $this->validSpecifics)) {
|
||||
throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ class Roles implements ConfigurationInterface
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
/**
|
||||
* Get the data necessary to show the configuration screen.
|
||||
*
|
||||
@@ -46,9 +49,11 @@ class Roles implements ConfigurationInterface
|
||||
$end = $start + config('csv.example_rows');
|
||||
|
||||
// set data:
|
||||
$roles = $this->getRoles();
|
||||
asort($roles);
|
||||
$this->data = [
|
||||
'examples' => [],
|
||||
'roles' => $this->getRoles(),
|
||||
'roles' => $roles,
|
||||
'total' => 0,
|
||||
'headers' => $config['has-headers'] ? $reader->fetchOne(0) : [],
|
||||
];
|
||||
@@ -68,6 +73,16 @@ class Roles implements ConfigurationInterface
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return $this->warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
@@ -215,7 +230,8 @@ class Roles implements ConfigurationInterface
|
||||
*/
|
||||
private function processSpecifics(array $row): array
|
||||
{
|
||||
foreach ($this->job->configuration['specifics'] as $name => $enabled) {
|
||||
$names = array_keys($this->job->configuration['specifics']);
|
||||
foreach ($names as $name) {
|
||||
/** @var SpecificInterface $specific */
|
||||
$specific = app('FireflyIII\Import\Specifics\\' . $name);
|
||||
$row = $specific->run($row);
|
||||
@@ -246,6 +262,10 @@ class Roles implements ConfigurationInterface
|
||||
$config['column-roles-complete'] = true;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
$this->warning = '';
|
||||
}
|
||||
if ($assigned === 0 || !$hasAmount) {
|
||||
$this->warning = strval(trans('csv.roles_warning'));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
71
app/Support/Import/Prerequisites/BunqPrerequisites.php
Normal file
71
app/Support/Import/Prerequisites/BunqPrerequisites.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* BunqPrerequisites.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\Import\Prerequisites;
|
||||
|
||||
use FireflyIII\User;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
* Class BunqPrerequisites
|
||||
*
|
||||
* @package FireflyIII\Support\Import\Prerequisites
|
||||
*/
|
||||
class BunqPrerequisites implements PrerequisitesInterface
|
||||
{
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Returns view name that allows user to fill in prerequisites.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getView(): string
|
||||
{
|
||||
return 'import.bunq.prerequisites';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any values required for the prerequisites-view.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getViewParameters(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this import method has any special prerequisites such as config
|
||||
* variables or other things.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrerequisites(): bool
|
||||
{
|
||||
$apiKey = Preferences::getForUser($this->user, 'bunq_api_key', false);
|
||||
|
||||
return ($apiKey->data === false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user for this Prerequisites-routine. Class is expected to implement and save this.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
47
app/Support/Import/Prerequisites/PrerequisitesInterface.php
Normal file
47
app/Support/Import/Prerequisites/PrerequisitesInterface.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* PrerequisitesInterface.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\Import\Prerequisites;
|
||||
|
||||
|
||||
use FireflyIII\User;
|
||||
|
||||
interface PrerequisitesInterface
|
||||
{
|
||||
/**
|
||||
* Returns view name that allows user to fill in prerequisites.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getView(): string;
|
||||
|
||||
/**
|
||||
* Returns any values required for the prerequisites-view.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getViewParameters(): array;
|
||||
|
||||
/**
|
||||
* Returns if this import method has any special prerequisites such as config
|
||||
* variables or other things.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrerequisites(): bool;
|
||||
|
||||
/**
|
||||
* Set the user for this Prerequisites-routine. Class is expected to implement and save this.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): void;
|
||||
}
|
||||
@@ -135,7 +135,7 @@ class Navigation
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
public function endOfX(Carbon $theCurrentEnd, string $repeatFreq, Carbon $maxDate = null): Carbon
|
||||
public function endOfX(Carbon $theCurrentEnd, string $repeatFreq, ?Carbon $maxDate): Carbon
|
||||
{
|
||||
$functionMap = [
|
||||
'1D' => 'endOfDay',
|
||||
|
||||
@@ -113,13 +113,12 @@ class Search implements SearchInterface
|
||||
}
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$set = $collector->getPaginatedJournals()->getCollection();
|
||||
$words = $this->words;
|
||||
|
||||
Log::debug(sprintf('Found %d journals to check. ', $set->count()));
|
||||
|
||||
// Filter transactions that match the given triggers.
|
||||
$filtered = $set->filter(
|
||||
function (Transaction $transaction) use ($words) {
|
||||
function (Transaction $transaction) {
|
||||
|
||||
if ($this->matchModifiers($transaction)) {
|
||||
return $transaction;
|
||||
@@ -237,7 +236,7 @@ class Search implements SearchInterface
|
||||
return false;
|
||||
}
|
||||
foreach ($needle as $what) {
|
||||
if (($pos = strpos($haystack, $what)) !== false) {
|
||||
if (strpos($haystack, $what) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,12 +181,12 @@ class Steam
|
||||
$modified = is_null($entry->modified) ? '0' : strval($entry->modified);
|
||||
$foreignModified = is_null($entry->modified_foreign) ? '0' : strval($entry->modified_foreign);
|
||||
$amount = '0';
|
||||
if ($currencyId === $entry->transaction_currency_id) {
|
||||
if ($currencyId === $entry->transaction_currency_id || $currencyId === 0) {
|
||||
// use normal amount:
|
||||
$amount = $modified;
|
||||
}
|
||||
if ($currencyId === $entry->foreign_currency_id) {
|
||||
// use normal amount:
|
||||
// use foreign amount:
|
||||
$amount = $foreignModified;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ class General extends Twig_Extension
|
||||
protected function balance(): Twig_SimpleFilter
|
||||
{
|
||||
return new Twig_SimpleFilter(
|
||||
'balance', function (Account $account = null): string {
|
||||
'balance', function (?Account $account): string {
|
||||
if (is_null($account)) {
|
||||
return 'NULL';
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Rules\Triggers\TriggerInterface;
|
||||
use FireflyIII\Services\Password\Verifier;
|
||||
use FireflyIII\User;
|
||||
use Google2FA;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
@@ -274,6 +275,29 @@ class FireflyValidator extends Validator
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $attribute
|
||||
* @param $value
|
||||
* @param $parameters
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateSecurePassword($attribute, $value, $parameters): bool
|
||||
{
|
||||
$verify = false;
|
||||
if (isset($this->data['verify_password'])) {
|
||||
$verify = intval($this->data['verify_password']) === 1;
|
||||
}
|
||||
if ($verify) {
|
||||
/** @var Verifier $service */
|
||||
$service = app(Verifier::class);
|
||||
|
||||
return $service->validPassword($value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
* @param $attribute
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"php": ">=7.1.0",
|
||||
"ext-intl": "*",
|
||||
"ext-bcmath": "*",
|
||||
"ext-mbstring": "*",
|
||||
|
||||
300
composer.lock
generated
300
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3570b1bf4768d7a7db0667317e82c668",
|
||||
"content-hash": "527cbfea501cb9d9a9f36637c357f971",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -152,21 +152,21 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
"version": "v1.4.0",
|
||||
"version": "v1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/annotations.git",
|
||||
"reference": "54cacc9b81758b14e3ce750f205a393d52339e97"
|
||||
"reference": "5beebb01b025c94e93686b7a0ed3edae81fe3e7f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97",
|
||||
"reference": "54cacc9b81758b14e3ce750f205a393d52339e97",
|
||||
"url": "https://api.github.com/repos/doctrine/annotations/zipball/5beebb01b025c94e93686b7a0ed3edae81fe3e7f",
|
||||
"reference": "5beebb01b025c94e93686b7a0ed3edae81fe3e7f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/lexer": "1.*",
|
||||
"php": "^5.6 || ^7.0"
|
||||
"php": "^7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/cache": "1.*",
|
||||
@@ -175,7 +175,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.4.x-dev"
|
||||
"dev-master": "1.5.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -216,37 +216,41 @@
|
||||
"docblock",
|
||||
"parser"
|
||||
],
|
||||
"time": "2017-02-24T16:22:25+00:00"
|
||||
"time": "2017-07-22T10:58:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
"version": "v1.6.2",
|
||||
"version": "v1.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/cache.git",
|
||||
"reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b"
|
||||
"reference": "53d9518ffeb019c51d542ff60cb578f076d3ff16"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b",
|
||||
"reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b",
|
||||
"url": "https://api.github.com/repos/doctrine/cache/zipball/53d9518ffeb019c51d542ff60cb578f076d3ff16",
|
||||
"reference": "53d9518ffeb019c51d542ff60cb578f076d3ff16",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~5.5|~7.0"
|
||||
"php": "~7.1"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": ">2.2,<2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8|~5.0",
|
||||
"predis/predis": "~1.0",
|
||||
"satooshi/php-coveralls": "~0.6"
|
||||
"alcaeus/mongo-php-adapter": "^1.1",
|
||||
"mongodb/mongodb": "^1.1",
|
||||
"phpunit/phpunit": "^5.7",
|
||||
"predis/predis": "~1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.6.x-dev"
|
||||
"dev-master": "1.7.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -286,24 +290,24 @@
|
||||
"cache",
|
||||
"caching"
|
||||
],
|
||||
"time": "2017-07-22T12:49:21+00:00"
|
||||
"time": "2017-07-22T13:00:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/collections",
|
||||
"version": "v1.4.0",
|
||||
"version": "v1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/collections.git",
|
||||
"reference": "1a4fb7e902202c33cce8c55989b945612943c2ba"
|
||||
"reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba",
|
||||
"reference": "1a4fb7e902202c33cce8c55989b945612943c2ba",
|
||||
"url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
|
||||
"reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0"
|
||||
"php": "^7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "~0.1@dev",
|
||||
@@ -353,20 +357,20 @@
|
||||
"collections",
|
||||
"iterator"
|
||||
],
|
||||
"time": "2017-01-03T10:49:41+00:00"
|
||||
"time": "2017-07-22T10:37:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/common",
|
||||
"version": "v2.7.3",
|
||||
"version": "v2.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/common.git",
|
||||
"reference": "4acb8f89626baafede6ee5475bc5844096eba8a9"
|
||||
"reference": "ed349f953d443963c590b008b37b864b8a3c4b21"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/common/zipball/4acb8f89626baafede6ee5475bc5844096eba8a9",
|
||||
"reference": "4acb8f89626baafede6ee5475bc5844096eba8a9",
|
||||
"url": "https://api.github.com/repos/doctrine/common/zipball/ed349f953d443963c590b008b37b864b8a3c4b21",
|
||||
"reference": "ed349f953d443963c590b008b37b864b8a3c4b21",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -375,15 +379,15 @@
|
||||
"doctrine/collections": "1.*",
|
||||
"doctrine/inflector": "1.*",
|
||||
"doctrine/lexer": "1.*",
|
||||
"php": "~5.6|~7.0"
|
||||
"php": "~7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.4.6"
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7.x-dev"
|
||||
"dev-master": "2.8.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -426,28 +430,30 @@
|
||||
"persistence",
|
||||
"spl"
|
||||
],
|
||||
"time": "2017-07-22T08:35:12+00:00"
|
||||
"time": "2017-07-22T09:01:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "v2.5.13",
|
||||
"version": "v2.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "729340d8d1eec8f01bff708e12e449a3415af873"
|
||||
"reference": "1a086f853425b1f5349775ce57e45a772d2d2ba5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/729340d8d1eec8f01bff708e12e449a3415af873",
|
||||
"reference": "729340d8d1eec8f01bff708e12e449a3415af873",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/1a086f853425b1f5349775ce57e45a772d2d2ba5",
|
||||
"reference": "1a086f853425b1f5349775ce57e45a772d2d2ba5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/common": ">=2.4,<2.8-dev",
|
||||
"php": ">=5.3.2"
|
||||
"doctrine/common": "^2.7.1",
|
||||
"ext-pdo": "*",
|
||||
"php": "^7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*",
|
||||
"phpunit/phpunit": "^5.4.6",
|
||||
"phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5",
|
||||
"symfony/console": "2.*||^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
@@ -459,7 +465,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.5.x-dev"
|
||||
"dev-master": "2.6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -497,37 +503,37 @@
|
||||
"persistence",
|
||||
"queryobject"
|
||||
],
|
||||
"time": "2017-07-22T20:44:48+00:00"
|
||||
"time": "2017-07-28T10:40:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/inflector",
|
||||
"version": "v1.2.0",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/inflector.git",
|
||||
"reference": "e11d84c6e018beedd929cff5220969a3c6d1d462"
|
||||
"reference": "90b2128806bfde671b6952ab8bea493942c1fdae"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/inflector/zipball/e11d84c6e018beedd929cff5220969a3c6d1d462",
|
||||
"reference": "e11d84c6e018beedd929cff5220969a3c6d1d462",
|
||||
"url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae",
|
||||
"reference": "90b2128806bfde671b6952ab8bea493942c1fdae",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0"
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.2"
|
||||
"phpunit/phpunit": "4.*"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2.x-dev"
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector"
|
||||
"psr-0": {
|
||||
"Doctrine\\Common\\Inflector\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@@ -564,7 +570,7 @@
|
||||
"singularize",
|
||||
"string"
|
||||
],
|
||||
"time": "2017-07-22T12:18:28+00:00"
|
||||
"time": "2015-11-06T14:35:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
@@ -664,20 +670,20 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v5.4.30",
|
||||
"version": "v5.4.32",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "b9a64955f4278f45ac348a6e000b5ecc85da167a"
|
||||
"reference": "b8300578d159199b1195413b67318c79068cd24d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/b9a64955f4278f45ac348a6e000b5ecc85da167a",
|
||||
"reference": "b9a64955f4278f45ac348a6e000b5ecc85da167a",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/b8300578d159199b1195413b67318c79068cd24d",
|
||||
"reference": "b8300578d159199b1195413b67318c79068cd24d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/inflector": "~1.0",
|
||||
"doctrine/inflector": "~1.1.0",
|
||||
"erusev/parsedown": "~1.6",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
@@ -789,7 +795,7 @@
|
||||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2017-07-19T19:26:19+00:00"
|
||||
"time": "2017-08-03T12:59:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravelcollective/html",
|
||||
@@ -847,16 +853,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "0.15.4",
|
||||
"version": "0.15.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c"
|
||||
"reference": "91742543c25fecedc84a4883d2919213e04a73b7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
|
||||
"reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91742543c25fecedc84a4883d2919213e04a73b7",
|
||||
"reference": "91742543c25fecedc84a4883d2919213e04a73b7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -869,7 +875,7 @@
|
||||
"require-dev": {
|
||||
"cebe/markdown": "~1.0",
|
||||
"erusev/parsedown": "~1.0",
|
||||
"jgm/commonmark": "0.27",
|
||||
"jgm/commonmark": "0.28",
|
||||
"michelf/php-markdown": "~1.4",
|
||||
"mikehaertl/php-shellcommand": "~1.2.0",
|
||||
"phpunit/phpunit": "~4.3|~5.0",
|
||||
@@ -912,7 +918,7 @@
|
||||
"markdown",
|
||||
"parser"
|
||||
],
|
||||
"time": "2017-05-09T12:47:53+00:00"
|
||||
"time": "2017-08-08T11:47:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
@@ -973,16 +979,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "1.0.40",
|
||||
"version": "1.0.41",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61"
|
||||
"reference": "f400aa98912c561ba625ea4065031b7a41e5a155"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3828f0b24e2c1918bb362d57a53205d6dc8fde61",
|
||||
"reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f400aa98912c561ba625ea4065031b7a41e5a155",
|
||||
"reference": "f400aa98912c561ba625ea4065031b7a41e5a155",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1003,13 +1009,13 @@
|
||||
"league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
|
||||
"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-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",
|
||||
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage"
|
||||
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
|
||||
"srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -1052,7 +1058,7 @@
|
||||
"sftp",
|
||||
"storage"
|
||||
],
|
||||
"time": "2017-04-28T10:15:08+00:00"
|
||||
"time": "2017-08-06T17:41:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
@@ -1387,16 +1393,16 @@
|
||||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
"version": "3.6.1",
|
||||
"version": "3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/uuid.git",
|
||||
"reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e"
|
||||
"reference": "0ef23d1b10cf1bc576e9d865a7e9c47982c5715e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
|
||||
"reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/0ef23d1b10cf1bc576e9d865a7e9c47982c5715e",
|
||||
"reference": "0ef23d1b10cf1bc576e9d865a7e9c47982c5715e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1465,7 +1471,7 @@
|
||||
"identifier",
|
||||
"uuid"
|
||||
],
|
||||
"time": "2017-03-26T20:37:53+00:00"
|
||||
"time": "2017-08-04T13:39:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rcrowe/twigbridge",
|
||||
@@ -1636,16 +1642,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "a97e45d98c59510f085fa05225a1acb74dfe0546"
|
||||
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/a97e45d98c59510f085fa05225a1acb74dfe0546",
|
||||
"reference": "a97e45d98c59510f085fa05225a1acb74dfe0546",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201",
|
||||
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1701,7 +1707,7 @@
|
||||
],
|
||||
"description": "Symfony Console Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-07-03T13:19:36+00:00"
|
||||
"time": "2017-07-29T21:27:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
@@ -1758,16 +1764,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "63b85a968486d95ff9542228dc2e4247f16f9743"
|
||||
"reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/63b85a968486d95ff9542228dc2e4247f16f9743",
|
||||
"reference": "63b85a968486d95ff9542228dc2e4247f16f9743",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
|
||||
"reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1810,11 +1816,11 @@
|
||||
],
|
||||
"description": "Symfony Debug Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-07-05T13:02:37+00:00"
|
||||
"time": "2017-07-28T15:27:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v2.8.25",
|
||||
"version": "v2.8.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
@@ -1874,7 +1880,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
@@ -1923,16 +1929,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "e307abe4b79ccbbfdced9b91c132fd128f456bc5"
|
||||
"reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/e307abe4b79ccbbfdced9b91c132fd128f456bc5",
|
||||
"reference": "e307abe4b79ccbbfdced9b91c132fd128f456bc5",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/49e8cd2d59a7aa9bfab19e46de680c76e500a031",
|
||||
"reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1972,7 +1978,7 @@
|
||||
],
|
||||
"description": "Symfony HttpFoundation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-07-17T14:07:10+00:00"
|
||||
"time": "2017-07-21T11:04:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
@@ -2228,7 +2234,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
@@ -2277,16 +2283,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
"reference": "dc70bbd0ca7b19259f63cdacc8af370bc32a4728"
|
||||
"reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/dc70bbd0ca7b19259f63cdacc8af370bc32a4728",
|
||||
"reference": "dc70bbd0ca7b19259f63cdacc8af370bc32a4728",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26",
|
||||
"reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2351,11 +2357,11 @@
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"time": "2017-06-24T09:29:48+00:00"
|
||||
"time": "2017-07-21T17:43:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
@@ -2420,16 +2426,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "0f32b62d21991700250fed5109b092949007c5b3"
|
||||
"reference": "b2623bccb969ad595c2090f9be498b74670d0663"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/0f32b62d21991700250fed5109b092949007c5b3",
|
||||
"reference": "0f32b62d21991700250fed5109b092949007c5b3",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/b2623bccb969ad595c2090f9be498b74670d0663",
|
||||
"reference": "b2623bccb969ad595c2090f9be498b74670d0663",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2484,7 +2490,7 @@
|
||||
"debug",
|
||||
"dump"
|
||||
],
|
||||
"time": "2017-07-10T14:18:27+00:00"
|
||||
"time": "2017-07-28T06:06:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
@@ -2869,32 +2875,32 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/instantiator.git",
|
||||
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
|
||||
"reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
|
||||
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
|
||||
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
|
||||
"reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3,<8.0-DEV"
|
||||
"php": "^7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"athletic/athletic": "~0.1.8",
|
||||
"ext-pdo": "*",
|
||||
"ext-phar": "*",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"squizlabs/php_codesniffer": "~2.0"
|
||||
"phpunit/phpunit": "^6.2.3",
|
||||
"squizlabs/php_codesniffer": "^3.0.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "1.2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -2919,7 +2925,7 @@
|
||||
"constructor",
|
||||
"instantiate"
|
||||
],
|
||||
"time": "2015-06-14T21:17:01+00:00"
|
||||
"time": "2017-07-22T11:58:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fzaninotto/faker",
|
||||
@@ -3210,7 +3216,7 @@
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Padraic Brady",
|
||||
"name": "Pádraic Brady",
|
||||
"email": "padraic.brady@gmail.com",
|
||||
"homepage": "http://blog.astrumfutura.com"
|
||||
},
|
||||
@@ -3334,22 +3340,22 @@
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-docblock",
|
||||
"version": "3.2.0",
|
||||
"version": "3.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
||||
"reference": "46f7e8bb075036c92695b15a1ddb6971c751e585"
|
||||
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/46f7e8bb075036c92695b15a1ddb6971c751e585",
|
||||
"reference": "46f7e8bb075036c92695b15a1ddb6971c751e585",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157",
|
||||
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"phpdocumentor/reflection-common": "^1.0@dev",
|
||||
"phpdocumentor/type-resolver": "^0.4.0",
|
||||
"phpdocumentor/type-resolver": "^0.3.0",
|
||||
"webmozart/assert": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -3375,20 +3381,20 @@
|
||||
}
|
||||
],
|
||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
||||
"time": "2017-07-15T11:38:20+00:00"
|
||||
"time": "2017-08-08T06:39:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/type-resolver",
|
||||
"version": "0.4.0",
|
||||
"version": "0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
||||
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
|
||||
"reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
|
||||
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fb3933512008d8162b3cdf9e18dba9309b7c3773",
|
||||
"reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3422,7 +3428,7 @@
|
||||
"email": "me@mikevanriel.com"
|
||||
}
|
||||
],
|
||||
"time": "2017-07-14T14:27:02+00:00"
|
||||
"time": "2017-06-03T08:32:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
@@ -3689,29 +3695,29 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-token-stream",
|
||||
"version": "1.4.11",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
|
||||
"reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
|
||||
"reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
|
||||
"reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ecb0b2cdaa0add708fe6f329ef65ae0c5225130b",
|
||||
"reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-tokenizer": "*",
|
||||
"php": ">=5.3.3"
|
||||
"php": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.2"
|
||||
"phpunit/phpunit": "^6.2.4"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.4-dev"
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -3734,7 +3740,7 @@
|
||||
"keywords": [
|
||||
"tokenizer"
|
||||
],
|
||||
"time": "2017-02-27T10:12:30+00:00"
|
||||
"time": "2017-08-03T14:17:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
@@ -4450,7 +4456,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/class-loader",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/class-loader.git",
|
||||
@@ -4506,16 +4512,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/config.git",
|
||||
"reference": "a094618deb9a3fe1c3cf500a796e167d0495a274"
|
||||
"reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/a094618deb9a3fe1c3cf500a796e167d0495a274",
|
||||
"reference": "a094618deb9a3fe1c3cf500a796e167d0495a274",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/54ee12b0dd60f294132cabae6f5da9573d2e5297",
|
||||
"reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4564,7 +4570,7 @@
|
||||
],
|
||||
"description": "Symfony Config Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-06-16T12:40:34+00:00"
|
||||
"time": "2017-07-19T07:37:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dom-crawler",
|
||||
@@ -4624,7 +4630,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
@@ -4673,7 +4679,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/stopwatch",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/stopwatch.git",
|
||||
@@ -4722,16 +4728,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "1f93a8d19b8241617f5074a123e282575b821df8"
|
||||
"reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/1f93a8d19b8241617f5074a123e282575b821df8",
|
||||
"reference": "1f93a8d19b8241617f5074a123e282575b821df8",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
|
||||
"reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4773,7 +4779,7 @@
|
||||
],
|
||||
"description": "Symfony Yaml Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-06-15T12:58:50+00:00"
|
||||
"time": "2017-07-23T12:43:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
@@ -4832,7 +4838,7 @@
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.0.0",
|
||||
"php": ">=7.1.0",
|
||||
"ext-intl": "*",
|
||||
"ext-bcmath": "*",
|
||||
"ext-mbstring": "*",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* auth.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* broadcasting.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* cache.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* compile.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -191,7 +191,7 @@ return [
|
||||
'mapper' => 'Categories',
|
||||
],
|
||||
'tags-comma' => [
|
||||
'mappable' => true,
|
||||
'mappable' => false,
|
||||
'pre-process-map' => true,
|
||||
'pre-process-mapper' => 'TagsComma',
|
||||
'field' => 'tags',
|
||||
@@ -199,7 +199,7 @@ return [
|
||||
'mapper' => 'Tags',
|
||||
],
|
||||
'tags-space' => [
|
||||
'mappable' => true,
|
||||
'mappable' => false,
|
||||
'pre-process-map' => true,
|
||||
'pre-process-mapper' => 'TagsSpace',
|
||||
'field' => 'tags',
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* database.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* filesystems.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'is_demo_site' => false,
|
||||
],
|
||||
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
|
||||
'version' => '4.6.3.1',
|
||||
'version' => '4.6.4',
|
||||
'maxUploadSize' => 15242880,
|
||||
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
|
||||
'list_length' => 10,
|
||||
@@ -39,6 +39,9 @@ return [
|
||||
'import_processors' => [
|
||||
'csv' => 'FireflyIII\Import\FileProcessor\CsvProcessor',
|
||||
],
|
||||
'import_pre' => [
|
||||
'bunq' => 'FireflyIII\Support\Import\Prerequisites\BunqPrerequisites',
|
||||
],
|
||||
'default_export_format' => 'csv',
|
||||
'default_import_format' => 'csv',
|
||||
'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
|
||||
|
||||
@@ -99,12 +99,11 @@ return [
|
||||
'accountStatus' => ['element' => '#accountStatus', 'position' => 'top'],
|
||||
],
|
||||
'piggy-banks_create' => [
|
||||
'name' => ['element' => '#ffInput_name'],
|
||||
'date' => ['element' => '#ffInput_targetdate'],
|
||||
'name' => ['element' => '#ffInput_name'],
|
||||
'date' => ['element' => '#ffInput_targetdate'],
|
||||
|
||||
],
|
||||
'piggy-banks_show' => [
|
||||
'intro' => [],
|
||||
'piggyChart' => ['element' => '#piggyChart'],
|
||||
'piggyDetails' => ['element' => '#piggyDetails'],
|
||||
'piggyEvents' => ['element' => '#piggyEvents'],
|
||||
@@ -146,7 +145,7 @@ return [
|
||||
],
|
||||
// preferences: index
|
||||
'preferences_index' => [
|
||||
'tabs' => ['element' => '.nav-tabs'],
|
||||
'tabs' => ['element' => '.nav-tabs'],
|
||||
],
|
||||
// currencies: index, create
|
||||
'currencies_index' => [
|
||||
@@ -154,6 +153,6 @@ return [
|
||||
'default' => ['element' => '.defaultCurrency'],
|
||||
],
|
||||
'currencies_create' => [
|
||||
'code' => ['element' => '#ffInput_code',],
|
||||
'code' => ['element' => '#ffInput_code',],
|
||||
],
|
||||
];
|
||||
];
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* mail.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* queue.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* services.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* This file is part of the TwigBridge package.
|
||||
|
||||
@@ -17,11 +17,13 @@ return [
|
||||
[
|
||||
'4.3' => 'Make sure you run the migrations and clear your cache. If you need more help, please check Github or the Firefly III website.',
|
||||
'4.6.3' => 'This will be the last version to require PHP7.0. Future versions will require PHP7.1 minimum.',
|
||||
'4.6.4' => 'This version of Firefly III requires PHP7.1.'
|
||||
],
|
||||
'install' =>
|
||||
[
|
||||
'4.3' => 'Welcome to Firefly! Make sure you follow the installation guide. If you need more help, please check Github or the Firefly III website. The installation guide has a FAQ which you should check out as well.',
|
||||
'4.6.3' => 'This will be the last version to require PHP7.0. Future versions will require PHP7.1 minimum.',
|
||||
'4.6.4' => 'This version of Firefly III requires PHP7.1.'
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 2016_10_09_150037_expand_transactions_table.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 2016_10_22_075804_changes_for_v410.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 2016_11_24_210552_changes_for_v420.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 2016_12_28_203205_changes_for_v431.php
|
||||
* Copyright (c) 2016 thegrumpydictator@gmail.com
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@@ -23,6 +25,7 @@ class ChangesForV440 extends Migration
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ShortMethodName)
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@@ -20,6 +22,7 @@ class ChangesForV450 extends Migration
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ShortMethodName)
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ browserconfig.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.
|
||||
-->
|
||||
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
|
||||
10
public/css/daterangepicker.css
vendored
10
public/css/daterangepicker.css
vendored
@@ -1,7 +1,7 @@
|
||||
.daterangepicker {
|
||||
position: absolute;
|
||||
color: inherit;
|
||||
background: #fff;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
width: 278px;
|
||||
padding: 4px;
|
||||
@@ -115,7 +115,7 @@
|
||||
border: 1px solid #fff;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.daterangepicker table {
|
||||
@@ -274,7 +274,7 @@
|
||||
|
||||
.ranges li {
|
||||
font-size: 13px;
|
||||
background: #f5f5f5;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #f5f5f5;
|
||||
border-radius: 4px;
|
||||
color: #08c;
|
||||
@@ -284,13 +284,13 @@
|
||||
}
|
||||
|
||||
.ranges li:hover {
|
||||
background: #08c;
|
||||
background-color: #08c;
|
||||
border: 1px solid #08c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ranges li.active {
|
||||
background: #08c;
|
||||
background-color: #08c;
|
||||
border: 1px solid #08c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
BIN
public/images/logos/bunq.png
Normal file
BIN
public/images/logos/bunq.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
/** global: Chart, defaultChartOptions, accounting, defaultPieOptions, noDataForChart, noDataForChart */
|
||||
/** global: Chart, defaultChartOptions, accounting, defaultPieOptions, noDataForChart */
|
||||
var allCharts = {};
|
||||
|
||||
/*
|
||||
|
||||
@@ -78,8 +78,7 @@ function showDownload() {
|
||||
|
||||
function showError(text) {
|
||||
"use strict";
|
||||
$('#export-error').show();
|
||||
$('#export-error').find('p').text(text);
|
||||
$('#export-error').show().find('p').text(text);
|
||||
}
|
||||
|
||||
function callExport() {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
/** global: moment, accountingConfig, dateRangeConfig, accounting, currencySymbol, mon_decimal_point, frac_digits, showFullList, showOnlyTop, mon_thousands_sep */
|
||||
/** global: moment, dateRangeMeta,dateRangeConfig, accountingConfig, accounting, currencySymbol, mon_decimal_point, frac_digits, showFullList, showOnlyTop, mon_thousands_sep */
|
||||
|
||||
|
||||
$(function () {
|
||||
@@ -16,8 +16,8 @@ $(function () {
|
||||
configAccounting(currencySymbol);
|
||||
|
||||
// on submit of form, disable any button in form:
|
||||
$('form.form-horizontal').on('submit',function() {
|
||||
$('button[type="submit"]').prop('disabled',true);
|
||||
$('form.form-horizontal').on('submit', function () {
|
||||
$('button[type="submit"]').prop('disabled', true);
|
||||
});
|
||||
|
||||
$.ajaxSetup({
|
||||
@@ -29,27 +29,18 @@ $(function () {
|
||||
// when you click on a currency, this happens:
|
||||
$('.currency-option').on('click', currencySelect);
|
||||
|
||||
var ranges = {};
|
||||
ranges[dateRangeConfig.currentPeriod] = [moment(dateRangeConfig.ranges.current[0]), moment(dateRangeConfig.ranges.current[1])];
|
||||
ranges[dateRangeConfig.previousPeriod] = [moment(dateRangeConfig.ranges.previous[0]), moment(dateRangeConfig.ranges.previous[1])];
|
||||
ranges[dateRangeConfig.nextPeriod] = [moment(dateRangeConfig.ranges.next[0]), moment(dateRangeConfig.ranges.next[1])];
|
||||
|
||||
// range for everything:
|
||||
ranges[dateRangeConfig.everything] = [dateRangeConfig.firstDate, moment()];
|
||||
|
||||
|
||||
// build the data range:
|
||||
$('#daterange').text(dateRangeConfig.linkTitle).daterangepicker(
|
||||
$('#daterange').text(dateRangeMeta.title).daterangepicker(
|
||||
{
|
||||
ranges: ranges,
|
||||
ranges: dateRangeConfig.ranges,
|
||||
opens: 'left',
|
||||
locale: {
|
||||
applyLabel: dateRangeConfig.applyLabel,
|
||||
cancelLabel: dateRangeConfig.cancelLabel,
|
||||
fromLabel: dateRangeConfig.fromLabel,
|
||||
toLabel: dateRangeConfig.toLabel,
|
||||
applyLabel: dateRangeMeta.labels.apply,
|
||||
cancelLabel: dateRangeMeta.labels.cancel,
|
||||
fromLabel: dateRangeMeta.labels.from,
|
||||
toLabel: dateRangeMeta.labels.to,
|
||||
weekLabel: 'W',
|
||||
customRangeLabel: dateRangeConfig.customRangeLabel,
|
||||
customRangeLabel: dateRangeMeta.labels.customRange,
|
||||
daysOfWeek: moment.weekdaysMin(),
|
||||
monthNames: moment.monthsShort(),
|
||||
firstDay: moment.localeData()._week.dow
|
||||
@@ -61,7 +52,7 @@ $(function () {
|
||||
function (start, end, label) {
|
||||
|
||||
// send post.
|
||||
$.post(dateRangeConfig.URL, {
|
||||
$.post(dateRangeMeta.uri, {
|
||||
start: start.format('YYYY-MM-DD'),
|
||||
end: end.format('YYYY-MM-DD'),
|
||||
label: label
|
||||
|
||||
@@ -60,8 +60,6 @@ function failedJobImport(jqxhr, textStatus, error) {
|
||||
function reportOnJobImport(data) {
|
||||
|
||||
switch (data.status) {
|
||||
default:
|
||||
break;
|
||||
case "configured":
|
||||
// job is ready. Do not check again, just show the start-box. Hide the rest.
|
||||
$('.statusbox').hide();
|
||||
@@ -94,9 +92,10 @@ function reportOnJobImport(data) {
|
||||
$('.status_finished').show();
|
||||
// show text:
|
||||
$('#import-status-more-info').html(data.finishedText);
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
if(!forceDemoOff) {
|
||||
if (!forceDemoOff) {
|
||||
$.getJSON(routeStepsUri).done(setupIntro)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -69,6 +69,7 @@ function removeMoney(e) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function stopSorting() {
|
||||
"use strict";
|
||||
$('.loadSpin').addClass('fa fa-refresh fa-spin');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user