mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Merge branch 'release/4.1.0'
This commit is contained in:
commit
7edd1bff40
21
CHANGELOG.md
21
CHANGELOG.md
@ -2,8 +2,29 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [4.1] - 2016-10-22
|
||||
### Added
|
||||
- Option to show deposit accounts on the front page.
|
||||
- Script to upgrade split transactions
|
||||
- Can now save notes on piggy banks.
|
||||
- Extend user admin options.
|
||||
- Run import jobs from the command line
|
||||
|
||||
|
||||
### Changed
|
||||
- New preferences screen layout.
|
||||
|
||||
### Deprecated
|
||||
- ``firefly:import`` is now ``firefly:start-import``
|
||||
|
||||
### Removed
|
||||
- Lots of old code
|
||||
|
||||
### Fixed
|
||||
- #357, where non utf-8 files would break Firefly.
|
||||
- Tab delimiter is not properly loaded from import configuration (@roberthorlings)
|
||||
- System response to yearly bills
|
||||
|
||||
## [4.0.2] - 2016-10-14
|
||||
### Added
|
||||
- Added ``intl`` dependency to composer file to ease installation (thanks @telyn)
|
||||
|
152
app/Console/Commands/CreateImport.php
Normal file
152
app/Console/Commands/CreateImport.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* CreateImport.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Artisan;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class CreateImport
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class CreateImport extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Use this command to create a new import. Your user ID can be found on the /profile page.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:create-import {file} {configuration} {--user=1} {--type=csv} {--start}';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// find the file
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
$file = $this->argument('file');
|
||||
$configuration = $this->argument('configuration');
|
||||
$user = $userRepository->find(intval($this->option('user')));
|
||||
$cwd = getcwd();
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
if (!$this->validArguments()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// try to parse configuration data:
|
||||
$configurationData = json_decode(file_get_contents($configuration));
|
||||
if (is_null($configurationData)) {
|
||||
$this->error(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info(sprintf('Going to create a job to import file: %s', $file));
|
||||
$this->info(sprintf('Using configuration file: %s', $configuration));
|
||||
$this->info(sprintf('Import into user: #%d (%s)', $user->id, $user->email));
|
||||
$this->info(sprintf('Type of import: %s', $type));
|
||||
|
||||
/** @var ImportJobRepositoryInterface $jobRepository */
|
||||
$jobRepository = app(ImportJobRepositoryInterface::class, [$user]);
|
||||
|
||||
$job = $jobRepository->create($type);
|
||||
$this->line(sprintf('Created job "%s"...', $job->key));
|
||||
|
||||
// put the file in the proper place:
|
||||
Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]);
|
||||
$this->line('Stored import data...');
|
||||
|
||||
// store the configuration in the job:
|
||||
$job->configuration = $configurationData;
|
||||
$job->status = 'settings_complete';
|
||||
$job->save();
|
||||
$this->line('Stored configuration...');
|
||||
|
||||
// if user wants to run it, do!
|
||||
if ($this->option('start') === true) {
|
||||
$this->line('The import will start in a moment. This process is not visible...');
|
||||
Log::debug('Go for import!');
|
||||
Artisan::call('firefly:start-import', ['key' => $job->key]);
|
||||
$this->line('Done!');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function validArguments(): bool
|
||||
{
|
||||
// find the file
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
$file = $this->argument('file');
|
||||
$configuration = $this->argument('configuration');
|
||||
$user = $userRepository->find(intval($this->option('user')));
|
||||
$cwd = getcwd();
|
||||
$validTypes = array_keys(config('firefly.import_formats'));
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
if (is_null($user->id)) {
|
||||
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!in_array($type, $validTypes)) {
|
||||
$this->error(sprintf('Cannot import file of type "%s"', $type));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_exists($file)) {
|
||||
$this->error(sprintf('Firefly III cannot find file "%s" (working directory: "%s").', $file, $cwd));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_exists($configuration)) {
|
||||
$this->error(sprintf('Firefly III cannot find configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -32,14 +32,14 @@ class Import extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Import stuff into Firefly III.';
|
||||
protected $description = 'This will start a new import.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:import {key}';
|
||||
protected $signature = 'firefly:start-import {key}';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
@ -57,9 +57,12 @@ class Import extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Log::debug('Start start-import command');
|
||||
$jobKey = $this->argument('key');
|
||||
$job = ImportJob::whereKey($jobKey)->first();
|
||||
if (!$this->isValid($job)) {
|
||||
Log::error('Job is not valid for some reason. Exit.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
121
app/Console/Commands/UpgradeDatabase.php
Normal file
121
app/Console/Commands/UpgradeDatabase.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* UpgradeDatabase.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
|
||||
use DB;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class UpgradeDatabase
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class UpgradeDatabase extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Will run various commands to update database records.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:upgrade-database';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->setTransactionIdentifier();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
|
||||
*/
|
||||
private function setTransactionIdentifier()
|
||||
{
|
||||
$subQuery = TransactionJournal
|
||||
::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->groupBy(['transaction_journals.id'])
|
||||
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
|
||||
|
||||
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
|
||||
->mergeBindings($subQuery->getQuery())
|
||||
->where('t_count', '>', 2)
|
||||
->select(['id', 't_count']);
|
||||
$journalIds = array_unique($result->pluck('id')->toArray());
|
||||
|
||||
foreach ($journalIds as $journalId) {
|
||||
// grab all positive transactiosn from this journal that are not deleted.
|
||||
// for each one, grab the negative opposing one which has 0 as an identifier and give it the same identifier.
|
||||
$identifier = 0;
|
||||
$processed = [];
|
||||
$transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get();
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
// find opposing:
|
||||
$amount = bcmul(strval($transaction->amount), '-1');
|
||||
|
||||
try {
|
||||
/** @var Transaction $opposing */
|
||||
$opposing = Transaction
|
||||
::where('transaction_journal_id', $journalId)
|
||||
->where('amount', $amount)->where('identifier', '=', 0)
|
||||
->whereNotIn('id', $processed)
|
||||
->first();
|
||||
} catch (QueryException $e) {
|
||||
Log::error($e->getMessage());
|
||||
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
|
||||
$this->error('This field is required for Firefly III version ' . config('firefly.version') . ' to run.');
|
||||
$this->error('Please run "php artisan migrate" to add this field to the table.');
|
||||
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
|
||||
break 2;
|
||||
}
|
||||
if (!is_null($opposing)) {
|
||||
// give both a new identifier:
|
||||
$transaction->identifier = $identifier;
|
||||
$transaction->save();
|
||||
$opposing->identifier = $identifier;
|
||||
$opposing->save();
|
||||
$processed[] = $transaction->id;
|
||||
$processed[] = $opposing->id;
|
||||
$this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id));
|
||||
}
|
||||
$identifier++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,9 +13,11 @@ declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Console;
|
||||
|
||||
use FireflyIII\Console\Commands\CreateImport;
|
||||
use FireflyIII\Console\Commands\EncryptFile;
|
||||
use FireflyIII\Console\Commands\Import;
|
||||
use FireflyIII\Console\Commands\ScanAttachments;
|
||||
use FireflyIII\Console\Commands\UpgradeDatabase;
|
||||
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
|
||||
use FireflyIII\Console\Commands\VerifyDatabase;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
@ -57,8 +59,10 @@ class Kernel extends ConsoleKernel
|
||||
UpgradeFireflyInstructions::class,
|
||||
VerifyDatabase::class,
|
||||
Import::class,
|
||||
CreateImport::class,
|
||||
EncryptFile::class,
|
||||
ScanAttachments::class,
|
||||
UpgradeDatabase::class,
|
||||
|
||||
];
|
||||
|
||||
|
@ -1,221 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Journal.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Crud\Split;
|
||||
|
||||
use FireflyIII\Events\TransactionStored;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class Journal
|
||||
*
|
||||
* @package FireflyIII\Crud\Split
|
||||
*/
|
||||
class Journal implements JournalInterface
|
||||
{
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* AttachmentRepository constructor.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function markAsComplete(TransactionJournal $journal)
|
||||
{
|
||||
$journal->completed = 1;
|
||||
$journal->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $transaction
|
||||
* @param int $identifier
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function storeTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection
|
||||
{
|
||||
// store accounts (depends on type)
|
||||
list($sourceAccount, $destinationAccount) = $this->storeAccounts($journal->transactionType->type, $transaction);
|
||||
|
||||
// store transaction one way:
|
||||
/** @var Transaction $one */
|
||||
$one = Transaction::create(
|
||||
['account_id' => $sourceAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'] * -1,
|
||||
'description' => $transaction['description'], 'identifier' => $identifier]
|
||||
);
|
||||
$two = Transaction::create(
|
||||
['account_id' => $destinationAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'],
|
||||
'description' => $transaction['description'], 'identifier' => $identifier]
|
||||
);
|
||||
|
||||
if (strlen($transaction['category']) > 0) {
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $transaction['category'], 'user_id' => $journal->user_id]);
|
||||
$one->categories()->save($category);
|
||||
$two->categories()->save($category);
|
||||
}
|
||||
if (intval($transaction['budget_id']) > 0) {
|
||||
$budget = Budget::find($transaction['budget_id']);
|
||||
$one->budgets()->save($budget);
|
||||
$two->budgets()->save($budget);
|
||||
}
|
||||
|
||||
if ($transaction['piggy_bank_id'] > 0) {
|
||||
$transaction['date'] = $journal->date->format('Y-m-d');
|
||||
event(new TransactionStored($transaction));
|
||||
}
|
||||
|
||||
return new Collection([$one, $two]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateJournal(TransactionJournal $journal, array $data): TransactionJournal
|
||||
{
|
||||
$journal->description = $data['journal_description'];
|
||||
$journal->transaction_currency_id = $data['journal_currency_id'];
|
||||
$journal->date = $data['date'];
|
||||
$journal->interest_date = $data['interest_date'];
|
||||
$journal->book_date = $data['book_date'];
|
||||
$journal->process_date = $data['process_date'];
|
||||
$journal->save();
|
||||
|
||||
// delete original transactions, and recreate them.
|
||||
$journal->transactions()->delete();
|
||||
|
||||
$identifier = 0;
|
||||
foreach ($data['transactions'] as $transaction) {
|
||||
$this->storeTransaction($journal, $transaction, $identifier);
|
||||
$identifier++;
|
||||
}
|
||||
|
||||
$journal->completed = true;
|
||||
$journal->save();
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param array $transaction
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function storeAccounts(string $type, array $transaction): array
|
||||
{
|
||||
$sourceAccount = null;
|
||||
$destinationAccount = null;
|
||||
switch ($type) {
|
||||
case TransactionType::WITHDRAWAL:
|
||||
list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($transaction);
|
||||
break;
|
||||
case TransactionType::DEPOSIT:
|
||||
list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction);
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['source_account_id'])->first();
|
||||
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['destination_account_id'])->first();
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('Cannot handle ' . e($type));
|
||||
}
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function storeDepositAccounts(array $data): array
|
||||
{
|
||||
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
|
||||
|
||||
|
||||
if (isset($data['source_account_name']) && strlen($data['source_account_name']) > 0) {
|
||||
$sourceType = AccountType::where('type', 'Revenue account')->first();
|
||||
$sourceAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
}
|
||||
|
||||
$sourceType = AccountType::where('type', 'Cash account')->first();
|
||||
$sourceAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function storeWithdrawalAccounts(array $data): array
|
||||
{
|
||||
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
|
||||
|
||||
if (strlen($data['destination_account_name']) > 0) {
|
||||
$destinationType = AccountType::where('type', 'Expense account')->first();
|
||||
$destinationAccount = Account::firstOrCreateEncrypted(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'account_type_id' => $destinationType->id,
|
||||
'name' => $data['destination_account_name'],
|
||||
'active' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
}
|
||||
$destinationType = AccountType::where('type', 'Cash account')->first();
|
||||
$destinationAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* JournalInterface.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Crud\Split;
|
||||
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface JournalInterface
|
||||
*
|
||||
* @package FireflyIII\Crud\Split
|
||||
*/
|
||||
interface JournalInterface
|
||||
{
|
||||
/**
|
||||
* @param $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function markAsComplete(TransactionJournal $journal);
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $transaction
|
||||
* @param int $identifier
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function storeTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateJournal(TransactionJournal $journal, array $data): TransactionJournal;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* UserIsDeleted.php
|
||||
* ConfirmedUser.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -17,11 +17,11 @@ use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class UserIsDeleted
|
||||
* Class ConfirmedUser
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class UserIsDeleted extends Event
|
||||
class ConfirmedUser extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* UserIsConfirmed.php
|
||||
* RegisteredUser.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -17,11 +17,11 @@ use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class UserIsConfirmed
|
||||
* Class RegisteredUser
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class UserIsConfirmed extends Event
|
||||
class RegisteredUser extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* ResendConfirmation.php
|
||||
* ResentConfirmation.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -17,11 +17,11 @@ use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class ResendConfirmation
|
||||
* Class ResentConfirmation
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class ResendConfirmation extends Event
|
||||
class ResentConfirmation extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* BudgetLimitStored.php
|
||||
* StoredBudgetLimit.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -18,11 +18,11 @@ use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class BudgetLimitStored
|
||||
* Class StoredBudgetLimit
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class BudgetLimitStored extends Event
|
||||
class StoredBudgetLimit extends Event
|
||||
{
|
||||
|
||||
use SerializesModels;
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* TransactionJournalStored.php
|
||||
* StoredTransactionJournal.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -17,11 +17,11 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class TransactionJournalStored
|
||||
* Class StoredTransactionJournal
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class TransactionJournalStored extends Event
|
||||
class StoredTransactionJournal extends Event
|
||||
{
|
||||
|
||||
use SerializesModels;
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* TransactionStored.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class TransactionJournalStored
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class TransactionStored extends Event
|
||||
{
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $transaction = [];
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param array $transaction
|
||||
*/
|
||||
public function __construct(array $transaction)
|
||||
{
|
||||
//
|
||||
$this->transaction = $transaction;
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* BudgetLimitUpdated.php
|
||||
* UpdatedBudgetLimit.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -18,11 +18,11 @@ use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class BudgetLimitUpdated
|
||||
* Class UpdatedBudgetLimit
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class BudgetLimitUpdated extends Event
|
||||
class UpdatedBudgetLimit extends Event
|
||||
{
|
||||
|
||||
use SerializesModels;
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* TransactionJournalUpdated.php
|
||||
* UpdatedTransactionJournal.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -17,11 +17,11 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class TransactionJournalUpdated
|
||||
* Class UpdatedTransactionJournal
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class TransactionJournalUpdated extends Event
|
||||
class UpdatedTransactionJournal extends Event
|
||||
{
|
||||
|
||||
use SerializesModels;
|
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* UserRegistration.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class UserRegistration
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class UserRegistration extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $ipAddress;
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param User $user
|
||||
* @param string $ipAddress
|
||||
*/
|
||||
public function __construct(User $user, string $ipAddress)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->ipAddress = $ipAddress;
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ use FireflyIII\Export\Collector\UploadCollector;
|
||||
use FireflyIII\Export\Entry\Entry;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Collection;
|
||||
use Storage;
|
||||
@ -95,9 +95,9 @@ class Processor
|
||||
*/
|
||||
public function collectJournals(): bool
|
||||
{
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
$this->journals = $repository->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
|
||||
/** @var JournalTaskerInterface $tasker */
|
||||
$tasker = app(JournalTaskerInterface::class);
|
||||
$this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
interface AccountChartGeneratorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
@ -43,6 +42,15 @@ interface AccountChartGeneratorInterface
|
||||
*/
|
||||
public function frontpage(Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param array $labels
|
||||
|
@ -83,6 +83,30 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$data = [
|
||||
'count' => 1,
|
||||
'labels' => [], 'datasets' => [[
|
||||
'label' => trans('firefly.earned'),
|
||||
'data' => []]]];
|
||||
foreach ($accounts as $account) {
|
||||
if ($account->difference > 0) {
|
||||
$data['labels'][] = $account->name;
|
||||
$data['datasets'][0]['data'][] = $account->difference;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param array $labels
|
||||
@ -105,5 +129,4 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* AttachUserRole.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
|
||||
use FireflyIII\Events\UserRegistration;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Class AttachUserRole
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class AttachUserRole
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param UserRegistration $event
|
||||
*/
|
||||
public function handle(UserRegistration $event)
|
||||
{
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
// first user ever?
|
||||
if ($repository->count() == 1) {
|
||||
$repository->attachRole($event->user, 'owner');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* BudgetLimitEventHandler.php
|
||||
* BudgetEventHandler.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
@ -13,36 +13,30 @@ declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\BudgetLimitStored;
|
||||
use FireflyIII\Events\BudgetLimitUpdated;
|
||||
|
||||
use FireflyIII\Events\StoredBudgetLimit;
|
||||
use FireflyIII\Events\UpdatedBudgetLimit;
|
||||
use FireflyIII\Models\LimitRepetition;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class BudgetLimitEventHandler
|
||||
* Handles budget related events.
|
||||
*
|
||||
* Class BudgetEventHandler
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class BudgetLimitEventHandler
|
||||
class BudgetEventHandler
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
* This method creates a new budget limit repetition when a new budget limit has been created.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In a perfect world, the store() routine should be different from the update()
|
||||
* routine. It would not have to check count() == 0 because there could be NO
|
||||
* limit repetitions at this point. However, the database can be wrong so we check.
|
||||
* @param StoredBudgetLimit $event
|
||||
*
|
||||
* @param BudgetLimitStored $event
|
||||
* @return bool
|
||||
*/
|
||||
public function store(BudgetLimitStored $event)
|
||||
public function storeRepetition(StoredBudgetLimit $event):bool
|
||||
{
|
||||
$budgetLimit = $event->budgetLimit;
|
||||
$end = $event->end;
|
||||
@ -71,12 +65,18 @@ class BudgetLimitEventHandler
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param BudgetLimitUpdated $event
|
||||
* Updates, if present the budget limit repetition part of a budget limit.
|
||||
*
|
||||
* @param UpdatedBudgetLimit $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update(BudgetLimitUpdated $event)
|
||||
public function update(UpdatedBudgetLimit $event): bool
|
||||
{
|
||||
$budgetLimit = $event->budgetLimit;
|
||||
$end = $event->end;
|
||||
@ -104,6 +104,7 @@ class BudgetLimitEventHandler
|
||||
$repetition->save();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ConnectJournalToPiggyBank.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\TransactionJournalStored;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
|
||||
/**
|
||||
* Class ConnectJournalToPiggyBank
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class ConnectJournalToPiggyBank
|
||||
{
|
||||
|
||||
/**
|
||||
* Connect a new transaction journal to any related piggy banks.
|
||||
*
|
||||
* @param TransactionJournalStored $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(TransactionJournalStored $event): bool
|
||||
{
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->journal;
|
||||
$piggyBankId = $event->piggyBankId;
|
||||
|
||||
/** @var PiggyBank $piggyBank */
|
||||
$piggyBank = auth()->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
||||
|
||||
if (is_null($piggyBank)) {
|
||||
return true;
|
||||
}
|
||||
// update piggy bank rep for date of transaction journal.
|
||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||
if (is_null($repetition)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$amount = TransactionJournal::amountPositive($journal);
|
||||
// if piggy account matches source account, the amount is positive
|
||||
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
|
||||
if (in_array($piggyBank->account_id, $sources)) {
|
||||
$amount = bcmul($amount, '-1');
|
||||
}
|
||||
|
||||
|
||||
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
|
||||
$repetition->save();
|
||||
|
||||
PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ConnectTransactionToPiggyBank.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\TransactionStored;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Class ConnectTransactionToPiggyBank
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class ConnectTransactionToPiggyBank
|
||||
{
|
||||
|
||||
/**
|
||||
* Connect a new transaction journal to any related piggy banks.
|
||||
*
|
||||
* @param TransactionStored $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(TransactionStored $event): bool
|
||||
{
|
||||
|
||||
/** @var PiggyBankRepositoryInterface $repository */
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$transaction = $event->transaction;
|
||||
|
||||
$piggyBank = $repository->find($transaction['piggy_bank_id']);
|
||||
|
||||
// valid piggy:
|
||||
if (is_null($piggyBank->id)) {
|
||||
return true;
|
||||
}
|
||||
$amount = strval($transaction['amount']);
|
||||
// piggy bank account something with amount:
|
||||
if ($transaction['source_account_id'] == $piggyBank->account_id) {
|
||||
// if the source of this transaction is the same as the piggy bank,
|
||||
// the money is being removed from the piggy bank. So the
|
||||
// amount must be negative:
|
||||
$amount = bcmul($amount, '-1');
|
||||
}
|
||||
|
||||
$repetition = $piggyBank->currentRelevantRep();
|
||||
// add or remove the money from the piggy bank:
|
||||
$newAmount = bcadd(strval($repetition->currentamount), $amount);
|
||||
$repetition->currentamount = $newAmount;
|
||||
$repetition->save();
|
||||
|
||||
// now generate a piggy bank event:
|
||||
PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'date' => $transaction['date'], 'amount' => $newAmount]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* FireRulesForStore.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
|
||||
use FireflyIII\Events\TransactionJournalStored;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\User;
|
||||
|
||||
/**
|
||||
* Class FireRulesForStore
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class FireRulesForStore
|
||||
{
|
||||
|
||||
/**
|
||||
* Connect a new transaction journal to any related piggy banks.
|
||||
*
|
||||
* @param TransactionJournalStored $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(TransactionJournalStored $event): bool
|
||||
{
|
||||
// get all the user's rule groups, with the rules, order by 'order'.
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$groups = $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||
//
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
$rules = $group->rules()
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', 1)
|
||||
->get(['rules.*']);
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($event->journal);
|
||||
|
||||
if ($rule->stop_processing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* FireRulesForUpdate.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\TransactionJournalUpdated;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\User;
|
||||
|
||||
/**
|
||||
* Class FireRulesForUpdate
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class FireRulesForUpdate
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param TransactionJournalUpdated $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(TransactionJournalUpdated $event): bool
|
||||
{
|
||||
// get all the user's rule groups, with the rules, order by 'order'.
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$groups = $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||
//
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
$rules = $group->rules()
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'update-journal')
|
||||
->where('rules.active', 1)
|
||||
->get(['rules.*']);
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($event->journal);
|
||||
|
||||
if ($rule->stop_processing) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ScanForBillsAfterStore.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\TransactionJournalStored;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
|
||||
/**
|
||||
* Class RescanJournal
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class ScanForBillsAfterStore
|
||||
{
|
||||
|
||||
/**
|
||||
* Scan a transaction journal for possible links to bills, right after storing.
|
||||
*
|
||||
* @param TransactionJournalStored $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(TransactionJournalStored $event): bool
|
||||
{
|
||||
$journal = $event->journal;
|
||||
BillScanner::scan($journal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ScanForBillsAfterUpdate.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\TransactionJournalUpdated;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
|
||||
/**
|
||||
* Class RescanJournal
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class ScanForBillsAfterUpdate
|
||||
{
|
||||
/**
|
||||
* Scan a transaction journal for possibly related bills after it has been updated.
|
||||
*
|
||||
* @param TransactionJournalUpdated $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(TransactionJournalUpdated $event): bool
|
||||
{
|
||||
$journal = $event->journal;
|
||||
BillScanner::scan($journal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* SendRegistrationMail.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
|
||||
use FireflyIII\Events\UserRegistration;
|
||||
use Illuminate\Mail\Message;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
* Class SendRegistrationMail
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class SendRegistrationMail
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param UserRegistration $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(UserRegistration $event): bool
|
||||
{
|
||||
$sendMail = env('SEND_REGISTRATION_MAIL', true);
|
||||
if (!$sendMail) {
|
||||
return true;
|
||||
}
|
||||
// get the email address
|
||||
$email = $event->user->email;
|
||||
$address = route('index');
|
||||
$ipAddress = $event->ipAddress;
|
||||
// send email.
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
||||
$message->to($email, $email)->subject('Welcome to Firefly III! ');
|
||||
}
|
||||
);
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
124
app/Handlers/Events/StoredJournalEventHandler.php
Normal file
124
app/Handlers/Events/StoredJournalEventHandler.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* StoredJournalEventHandler.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\StoredTransactionJournal;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
|
||||
/**
|
||||
* Class StoredJournalEventHandler
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class StoredJournalEventHandler
|
||||
{
|
||||
/**
|
||||
* This method connects a new transfer to a piggy bank.
|
||||
*
|
||||
* @param StoredTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connectToPiggyBank(StoredTransactionJournal $event): bool
|
||||
{
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->journal;
|
||||
$piggyBankId = $event->piggyBankId;
|
||||
|
||||
/** @var PiggyBank $piggyBank */
|
||||
$piggyBank = $journal->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
||||
|
||||
if (is_null($piggyBank)) {
|
||||
return true;
|
||||
}
|
||||
// update piggy bank rep for date of transaction journal.
|
||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||
if (is_null($repetition)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$amount = TransactionJournal::amountPositive($journal);
|
||||
// if piggy account matches source account, the amount is positive
|
||||
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
|
||||
if (in_array($piggyBank->account_id, $sources)) {
|
||||
$amount = bcmul($amount, '-1');
|
||||
}
|
||||
|
||||
|
||||
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
|
||||
$repetition->save();
|
||||
|
||||
PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method grabs all the users rules and processes them.
|
||||
*
|
||||
* @param StoredTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function processRules(StoredTransactionJournal $event): bool
|
||||
{
|
||||
// get all the user's rule groups, with the rules, order by 'order'.
|
||||
$journal = $event->journal;
|
||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||
//
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
$rules = $group->rules()
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', 1)
|
||||
->get(['rules.*']);
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($journal);
|
||||
|
||||
if ($rule->stop_processing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calls a special bill scanner that will check if the stored journal is part of a bill.
|
||||
*
|
||||
* @param StoredTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function scanBills(StoredTransactionJournal $event): bool
|
||||
{
|
||||
$journal = $event->journal;
|
||||
BillScanner::scan($journal);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* UpdateJournalConnection.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\TransactionJournalUpdated;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
|
||||
/**
|
||||
* Class UpdateJournalConnection
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UpdateJournalConnection
|
||||
{
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param TransactionJournalUpdated $event
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(TransactionJournalUpdated $event):bool
|
||||
{
|
||||
$journal = $event->journal;
|
||||
|
||||
if (!$journal->isTransfer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the event connected to this journal:
|
||||
/** @var PiggyBankEvent $event */
|
||||
$event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first();
|
||||
if (is_null($event)) {
|
||||
return false;
|
||||
}
|
||||
$piggyBank = $event->piggyBank()->first();
|
||||
$repetition = null;
|
||||
if (!is_null($piggyBank)) {
|
||||
/** @var PiggyBankRepetition $repetition */
|
||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||
}
|
||||
|
||||
if (is_null($repetition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$amount = TransactionJournal::amount($journal);
|
||||
$diff = bcsub($amount, $event->amount); // update current repetition
|
||||
|
||||
$repetition->currentamount = bcadd($repetition->currentamount, $diff);
|
||||
$repetition->save();
|
||||
|
||||
|
||||
$event->amount = $amount;
|
||||
$event->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
128
app/Handlers/Events/UpdatedJournalEventHandler.php
Normal file
128
app/Handlers/Events/UpdatedJournalEventHandler.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* UpdatedJournalEventHandler.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
|
||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
|
||||
/**
|
||||
* Class UpdatedJournalEventHandler
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UpdatedJournalEventHandler
|
||||
{
|
||||
/**
|
||||
* This method will try to reconnect a journal to a piggy bank, updating the piggy bank repetition.
|
||||
*
|
||||
* @param UpdatedTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connectToPiggyBank(UpdatedTransactionJournal $event): bool
|
||||
{
|
||||
$journal = $event->journal;
|
||||
|
||||
if (!$journal->isTransfer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the event connected to this journal:
|
||||
/** @var PiggyBankEvent $event */
|
||||
$event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first();
|
||||
if (is_null($event)) {
|
||||
return false;
|
||||
}
|
||||
$piggyBank = $event->piggyBank()->first();
|
||||
$repetition = null;
|
||||
if (!is_null($piggyBank)) {
|
||||
/** @var PiggyBankRepetition $repetition */
|
||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||
}
|
||||
|
||||
if (is_null($repetition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$amount = TransactionJournal::amount($journal);
|
||||
$diff = bcsub($amount, $event->amount); // update current repetition
|
||||
|
||||
$repetition->currentamount = bcadd($repetition->currentamount, $diff);
|
||||
$repetition->save();
|
||||
|
||||
|
||||
$event->amount = $amount;
|
||||
$event->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will check all the rules when a journal is updated.
|
||||
*
|
||||
* @param UpdatedTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function processRules(UpdatedTransactionJournal $event):bool
|
||||
{
|
||||
// get all the user's rule groups, with the rules, order by 'order'.
|
||||
$journal = $event->journal;
|
||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||
//
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
$rules = $group->rules()
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'update-journal')
|
||||
->where('rules.active', 1)
|
||||
->get(['rules.*']);
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
$processor = Processor::make($rule);
|
||||
$processor->handleTransactionJournal($journal);
|
||||
|
||||
if ($rule->stop_processing) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calls a special bill scanner that will check if the updated journal is part of a bill.
|
||||
*
|
||||
* @param UpdatedTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function scanBills(UpdatedTransactionJournal $event): bool
|
||||
{
|
||||
$journal = $event->journal;
|
||||
BillScanner::scan($journal);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* UserConfirmation.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\ResendConfirmation;
|
||||
use FireflyIII\Events\UserRegistration;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Mail\Message;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Preferences;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
* Class UserConfirmation
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UserConfirmation
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResendConfirmation $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function resendConfirmation(ResendConfirmation $event): bool
|
||||
{
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$this->doConfirm($user, $ipAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param UserRegistration $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendConfirmation(UserRegistration $event): bool
|
||||
{
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$this->doConfirm($user, $ipAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $ipAddress
|
||||
*/
|
||||
private function doConfirm(User $user, string $ipAddress)
|
||||
{
|
||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
||||
if ($confirmAccount === false) {
|
||||
Preferences::setForUser($user, 'user_confirmed', true);
|
||||
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
||||
Preferences::mark();
|
||||
|
||||
return;
|
||||
}
|
||||
$email = $user->email;
|
||||
$code = str_random(16);
|
||||
$route = route('do_confirm_account', [$code]);
|
||||
Preferences::setForUser($user, 'user_confirmed', false);
|
||||
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
|
||||
Preferences::setForUser($user, 'user_confirmed_code', $code);
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
|
||||
function (Message $message) use ($email) {
|
||||
$message->to($email, $email)->subject('Please confirm your Firefly III account');
|
||||
}
|
||||
);
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
225
app/Handlers/Events/UserEventHandler.php
Normal file
225
app/Handlers/Events/UserEventHandler.php
Normal file
@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* UserEventHandler.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\ConfirmedUser;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Events\ResentConfirmation;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Mail\Message;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
* Class UserEventHandler
|
||||
*
|
||||
* This class responds to any events that have anything to do with the User object.
|
||||
*
|
||||
* The method name reflects what is being done. This is in the present tense.
|
||||
*
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UserEventHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* This method will bestow upon a user the "owner" role if he is the first user in the system.
|
||||
*
|
||||
* @param RegisteredUser $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function attachUserRole(RegisteredUser $event): bool
|
||||
{
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
// first user ever?
|
||||
if ($repository->count() === 1) {
|
||||
$repository->attachRole($event->user, 'owner');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle user logout events.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onUserLogout(): bool
|
||||
{
|
||||
// dump stuff from the session:
|
||||
Session::forget('twofactor-authenticated');
|
||||
Session::forget('twofactor-authenticated-date');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will send a newly registered user a confirmation message, urging him or her to activate their account.
|
||||
*
|
||||
* @param RegisteredUser $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendConfirmationMessage(RegisteredUser $event): bool
|
||||
{
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
||||
if ($confirmAccount === false) {
|
||||
Preferences::setForUser($user, 'user_confirmed', true);
|
||||
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
||||
Preferences::mark();
|
||||
|
||||
return true;
|
||||
}
|
||||
$email = $user->email;
|
||||
$code = str_random(16);
|
||||
$route = route('do_confirm_account', [$code]);
|
||||
Preferences::setForUser($user, 'user_confirmed', false);
|
||||
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
|
||||
Preferences::setForUser($user, 'user_confirmed_code', $code);
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
|
||||
function (Message $message) use ($email) {
|
||||
$message->to($email, $email)->subject('Please confirm your Firefly III account');
|
||||
}
|
||||
);
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user has somehow lost his or her confirmation message, this event will send it to the user again.
|
||||
*
|
||||
* At the moment, this method is exactly the same as the ::sendConfirmationMessage method, but that will change.
|
||||
*
|
||||
* @param ResentConfirmation $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function sendConfirmationMessageAgain(ResentConfirmation $event): bool
|
||||
{
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
||||
if ($confirmAccount === false) {
|
||||
Preferences::setForUser($user, 'user_confirmed', true);
|
||||
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
||||
Preferences::mark();
|
||||
|
||||
return true;
|
||||
}
|
||||
$email = $user->email;
|
||||
$code = str_random(16);
|
||||
$route = route('do_confirm_account', [$code]);
|
||||
Preferences::setForUser($user, 'user_confirmed', false);
|
||||
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
|
||||
Preferences::setForUser($user, 'user_confirmed_code', $code);
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
|
||||
function (Message $message) use ($email) {
|
||||
$message->to($email, $email)->subject('Please confirm your Firefly III account');
|
||||
}
|
||||
);
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will send the user a registration mail, welcoming him or her to Firefly III.
|
||||
* This message is only sent when the configuration of Firefly III says so.
|
||||
*
|
||||
* @param RegisteredUser $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendRegistrationMail(RegisteredUser $event)
|
||||
{
|
||||
|
||||
$sendMail = env('SEND_REGISTRATION_MAIL', true);
|
||||
if (!$sendMail) {
|
||||
return true;
|
||||
}
|
||||
// get the email address
|
||||
$email = $event->user->email;
|
||||
$address = route('index');
|
||||
$ipAddress = $event->ipAddress;
|
||||
// send email.
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
||||
$message->to($email, $email)->subject('Welcome to Firefly III! ');
|
||||
}
|
||||
);
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user is confirmed, this method stores the IP address of the user
|
||||
* as a preference. Since this preference cannot be edited, it is effectively hidden
|
||||
* from the user yet stored conveniently.
|
||||
*
|
||||
* @param ConfirmedUser $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function storeConfirmationIpAddress(ConfirmedUser $event): bool
|
||||
{
|
||||
Preferences::setForUser($event->user, 'confirmation_ip_address', $event->ipAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This message stores the users IP address on registration, in much the same
|
||||
* fashion as the previous method.
|
||||
*
|
||||
* @param RegisteredUser $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function storeRegistrationIpAddress(RegisteredUser $event): bool
|
||||
{
|
||||
Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* UserEventListener.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class UserEventListener
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UserEventListener
|
||||
{
|
||||
/**
|
||||
* Handle user logout events.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onUserLogout(): bool
|
||||
{
|
||||
// dump stuff from the session:
|
||||
Session::forget('twofactor-authenticated');
|
||||
Session::forget('twofactor-authenticated-date');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* UserSaveIpAddress.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\UserIsConfirmed;
|
||||
use FireflyIII\Events\UserRegistration;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
* Class UserSaveIpAddress
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class UserSaveIpAddress
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param UserIsConfirmed $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveFromConfirmation(UserIsConfirmed $event): bool
|
||||
{
|
||||
Preferences::setForUser($event->user, 'confirmation_ip_address', $event->ipAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param UserRegistration $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveFromRegistration(UserRegistration $event): bool
|
||||
{
|
||||
Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -114,12 +114,11 @@ class AccountController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ARI $repository
|
||||
* @param Account $account
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(ARI $repository, Account $account)
|
||||
public function edit(Account $account)
|
||||
{
|
||||
|
||||
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
|
||||
|
@ -78,5 +78,52 @@ class UserController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserRepositoryInterface $repository
|
||||
* @param User $user
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function show(UserRepositoryInterface $repository, User $user)
|
||||
{
|
||||
$title = strval(trans('firefly.administration'));
|
||||
$mainTitleIcon = 'fa-hand-spock-o';
|
||||
$subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email]));
|
||||
$subTitleIcon = 'fa-user';
|
||||
|
||||
// get IP info:
|
||||
$defaultIp = '0.0.0.0';
|
||||
$regPref = Preferences::getForUser($user, 'registration_ip_address');
|
||||
$registration = $defaultIp;
|
||||
$conPref = Preferences::getForUser($user, 'confirmation_ip_address');
|
||||
$confirmation = $defaultIp;
|
||||
if (!is_null($regPref)) {
|
||||
$registration = $regPref->data;
|
||||
}
|
||||
if (!is_null($conPref)) {
|
||||
$confirmation = $conPref->data;
|
||||
}
|
||||
|
||||
$registrationHost = '';
|
||||
$confirmationHost = '';
|
||||
|
||||
if ($registration != $defaultIp) {
|
||||
$registrationHost = gethostbyaddr($registration);
|
||||
}
|
||||
if ($confirmation != $defaultIp) {
|
||||
$confirmationHost = gethostbyaddr($confirmation);
|
||||
}
|
||||
|
||||
$information = $repository->getUserData($user);
|
||||
|
||||
return view(
|
||||
'admin.users.show',
|
||||
compact(
|
||||
'title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'information',
|
||||
'user', 'registration', 'confirmation', 'registrationHost', 'confirmationHost'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyIII\Events\ResendConfirmation;
|
||||
use FireflyIII\Events\UserIsConfirmed;
|
||||
use FireflyIII\Events\ConfirmedUser;
|
||||
use FireflyIII\Events\ResentConfirmation;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
@ -56,7 +56,7 @@ class ConfirmationController extends Controller
|
||||
if ($database === $code && ($now - $time <= $maxDiff)) {
|
||||
|
||||
// trigger user registration event:
|
||||
event(new UserIsConfirmed(auth()->user(), $request->ip()));
|
||||
event(new ConfirmedUser(auth()->user(), $request->ip()));
|
||||
|
||||
Preferences::setForUser(auth()->user(), 'user_confirmed', true);
|
||||
Preferences::setForUser(auth()->user(), 'user_confirmed_confirmed', time());
|
||||
@ -80,7 +80,7 @@ class ConfirmationController extends Controller
|
||||
$owner = env('SITE_OWNER', 'mail@example.com');
|
||||
$view = 'auth.confirmation.no-resent';
|
||||
if ($now - $time > $maxDiff) {
|
||||
event(new ResendConfirmation(auth()->user(), $request->ip()));
|
||||
event(new ResentConfirmation(auth()->user(), $request->ip()));
|
||||
$view = 'auth.confirmation.resent';
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use Auth;
|
||||
use Config;
|
||||
use FireflyIII\Events\UserRegistration;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\User;
|
||||
@ -102,7 +102,7 @@ class RegisterController extends Controller
|
||||
$user = $this->create($request->all());
|
||||
|
||||
// trigger user registration event:
|
||||
event(new UserRegistration($user, $request->ip()));
|
||||
event(new RegisteredUser($user, $request->ip()));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
|
@ -41,6 +41,8 @@ class ResetPasswordController extends Controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware('guest');
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ class BillController extends Controller
|
||||
$bills = $repository->getBills();
|
||||
$bills->each(
|
||||
function (Bill $bill) use ($repository, $start, $end) {
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
|
||||
$bill->lastFoundMatch = $repository->lastFoundMatch($bill);
|
||||
$journals = $repository->getJournalsInRange($bill, $start, $end);
|
||||
// loop journals, find average:
|
||||
@ -204,7 +204,7 @@ class BillController extends Controller
|
||||
$yearAverage = $repository->getYearAverage($bill, $date);
|
||||
$overallAverage = $repository->getOverallAverage($bill);
|
||||
$journals->setPath('/bills/show/' . $bill->id);
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
|
||||
$hideBill = true;
|
||||
$subTitle = e($bill->name);
|
||||
|
||||
|
@ -190,6 +190,56 @@ class AccountController extends Controller
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the balances for all the user's revenue accounts.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function revenueAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$start = clone session('start', Carbon::now()->startOfMonth());
|
||||
$end = clone session('end', Carbon::now()->endOfMonth());
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('revenueAccounts');
|
||||
$cache->addProperty('accounts');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get());
|
||||
}
|
||||
$accounts = $repository->getAccountsByType([AccountType::REVENUE]);
|
||||
|
||||
$start->subDay();
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
$startBalances = Steam::balancesById($ids, $start);
|
||||
$endBalances = Steam::balancesById($ids, $end);
|
||||
|
||||
$accounts->each(
|
||||
function (Account $account) use ($startBalances, $endBalances) {
|
||||
$id = $account->id;
|
||||
$startBalance = $startBalances[$id] ?? '0';
|
||||
$endBalance = $endBalances[$id] ?? '0';
|
||||
$diff = bcsub($endBalance, $startBalance);
|
||||
$diff = bcmul($diff, '-1');
|
||||
$account->difference = round($diff, 2);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
$accounts = $accounts->sortByDesc(
|
||||
function (Account $account) {
|
||||
return $account->difference;
|
||||
}
|
||||
);
|
||||
|
||||
$data = $this->generator->revenueAccounts($accounts, $start, $end);
|
||||
$cache->store($data);
|
||||
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an account's balance for a single month.
|
||||
*
|
||||
|
@ -138,9 +138,10 @@ class HomeController extends Controller
|
||||
/** @var Carbon $start */
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
/** @var Carbon $end */
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$showTour = Preferences::get('tour', true)->data;
|
||||
$accounts = $repository->getAccountsById($frontPage->data);
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$showTour = Preferences::get('tour', true)->data;
|
||||
$accounts = $repository->getAccountsById($frontPage->data);
|
||||
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
|
||||
@ -152,7 +153,7 @@ class HomeController extends Controller
|
||||
}
|
||||
|
||||
return view(
|
||||
'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions')
|
||||
'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage')
|
||||
);
|
||||
}
|
||||
|
||||
@ -195,6 +196,18 @@ class HomeController extends Controller
|
||||
return '<hr>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function testFlash()
|
||||
{
|
||||
Session::flash('success', 'This is a success message.');
|
||||
Session::flash('info', 'This is an info message.');
|
||||
Session::flash('warning', 'This is a warning.');
|
||||
Session::flash('error', 'This is an error!');
|
||||
|
||||
return redirect(route('home'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
|
@ -20,7 +20,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Input;
|
||||
@ -270,17 +270,17 @@ class JsonController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param JournalTaskerInterface $tasker
|
||||
* @param $what
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function transactionJournals(JournalRepositoryInterface $repository, $what)
|
||||
public function transactionJournals(JournalTaskerInterface $tasker, $what)
|
||||
{
|
||||
$descriptions = [];
|
||||
$type = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$types = [$type];
|
||||
$journals = $repository->getJournals($types, 1, 50);
|
||||
$journals = $tasker->getJournals($types, 1, 50);
|
||||
foreach ($journals as $j) {
|
||||
$descriptions[] = $j->description;
|
||||
}
|
||||
|
@ -168,6 +168,7 @@ class PiggyBankController extends Controller
|
||||
'account_id' => $piggyBank->account_id,
|
||||
'targetamount' => $piggyBank->targetamount,
|
||||
'targetdate' => $targetDate,
|
||||
'note' => $piggyBank->notes()->first()->text,
|
||||
];
|
||||
Session::flash('preFilled', $preFilled);
|
||||
Session::flash('gaEventCategory', 'piggy-banks');
|
||||
@ -346,10 +347,11 @@ class PiggyBankController extends Controller
|
||||
*/
|
||||
public function show(PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
|
||||
{
|
||||
$note = $piggyBank->notes()->first();
|
||||
$events = $repository->getEvents($piggyBank);
|
||||
$subTitle = e($piggyBank->name);
|
||||
|
||||
return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle'));
|
||||
return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'note'));
|
||||
|
||||
}
|
||||
|
||||
@ -369,6 +371,7 @@ class PiggyBankController extends Controller
|
||||
'targetamount' => round($request->get('targetamount'), 2),
|
||||
'order' => $repository->getMaxOrder() + 1,
|
||||
'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null,
|
||||
'note' => $request->get('note'),
|
||||
];
|
||||
|
||||
$piggyBank = $repository->store($piggyBankData);
|
||||
@ -402,6 +405,7 @@ class PiggyBankController extends Controller
|
||||
'account_id' => intval($request->get('account_id')),
|
||||
'targetamount' => round($request->get('targetamount'), 2),
|
||||
'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null,
|
||||
'note' => $request->get('note'),
|
||||
];
|
||||
|
||||
$piggyBank = $repository->update($piggyBank, $piggyBankData);
|
||||
|
@ -76,26 +76,27 @@ class PreferencesController extends Controller
|
||||
*/
|
||||
public function index(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$viewRangePref = Preferences::get('viewRange', '1M');
|
||||
$viewRange = $viewRangePref->data;
|
||||
$frontPageAccounts = Preferences::get('frontPageAccounts', []);
|
||||
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
|
||||
$transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
|
||||
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
|
||||
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
|
||||
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
|
||||
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; // twoFactorAuthEnabled
|
||||
$has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); // hasTwoFactorAuthSecret
|
||||
$showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$viewRangePref = Preferences::get('viewRange', '1M');
|
||||
$viewRange = $viewRangePref->data;
|
||||
$frontPageAccounts = Preferences::get('frontPageAccounts', []);
|
||||
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
|
||||
$transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
|
||||
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
|
||||
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
|
||||
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
|
||||
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
|
||||
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; // twoFactorAuthEnabled
|
||||
$has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); // hasTwoFactorAuthSecret
|
||||
$showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
|
||||
|
||||
return view(
|
||||
'preferences.index',
|
||||
compact(
|
||||
'language', 'accounts', 'frontPageAccounts', 'tjOptionalFields',
|
||||
'viewRange', 'customFiscalYear', 'transactionPageSize', 'fiscalYearStart', 'is2faEnabled',
|
||||
'has2faSecret', 'showIncomplete'
|
||||
'has2faSecret', 'showIncomplete', 'showDepositsFrontpage'
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -145,6 +146,10 @@ class PreferencesController extends Controller
|
||||
Preferences::set('customFiscalYear', $customFiscalYear);
|
||||
Preferences::set('fiscalYearStart', $fiscalYearStart);
|
||||
|
||||
// show deposits frontpage:
|
||||
$showDepositsFrontpage = intval($request->get('showDepositsFrontpage')) === 1;
|
||||
Preferences::set('showDepositsFrontpage', $showDepositsFrontpage);
|
||||
|
||||
// save page size:
|
||||
$transactionPageSize = intval($request->get('transactionPageSize'));
|
||||
if ($transactionPageSize > 0 && $transactionPageSize < 1337) {
|
||||
|
@ -13,7 +13,7 @@ declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use FireflyIII\Events\UserIsDeleted;
|
||||
use FireflyIII\Events\DeletedUser;
|
||||
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
|
||||
use FireflyIII\Http\Requests\ProfileFormRequest;
|
||||
use FireflyIII\User;
|
||||
@ -35,6 +35,9 @@ class ProfileController extends Controller
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
View::share('title', trans('firefly.profile'));
|
||||
View::share('mainTitleIcon', 'fa-user');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,7 +66,10 @@ class ProfileController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('profile.index')->with('title', trans('firefly.profile'))->with('subTitle', auth()->user()->email)->with('mainTitleIcon', 'fa-user');
|
||||
$subTitle = auth()->user()->email;
|
||||
$userId = auth()->user()->id;
|
||||
|
||||
return view('profile.index', compact('subTitle', 'userId'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,9 +116,6 @@ class ProfileController extends Controller
|
||||
return redirect(route('profile.delete-account'));
|
||||
}
|
||||
|
||||
// respond to deletion:
|
||||
event(new UserIsDeleted(auth()->user(), $request->ip()));
|
||||
|
||||
// store some stuff for the future:
|
||||
$registration = Preferences::get('registration_ip_address')->data;
|
||||
$confirmation = Preferences::get('confirmation_ip_address')->data;
|
||||
|
@ -153,8 +153,6 @@ class ReportController extends Controller
|
||||
*/
|
||||
private function auditReport(Carbon $start, Carbon $end, Collection $accounts)
|
||||
{
|
||||
/** @var ARI $repos */
|
||||
$repos = app(ARI::class);
|
||||
/** @var AccountTaskerInterface $tasker */
|
||||
$tasker = app(AccountTaskerInterface::class);
|
||||
$auditData = [];
|
||||
|
331
app/Http/Controllers/Transaction/SingleController.php
Normal file
331
app/Http/Controllers/Transaction/SingleController.php
Normal file
@ -0,0 +1,331 @@
|
||||
<?php
|
||||
/**
|
||||
* SingleController.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Events\StoredTransactionJournal;
|
||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\JournalFormRequest;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use Steam;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class SingleController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Transaction
|
||||
*/
|
||||
class SingleController extends Controller
|
||||
{
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accounts;
|
||||
|
||||
/** @var AttachmentHelperInterface */
|
||||
private $attachments;
|
||||
|
||||
/** @var BudgetRepositoryInterface */
|
||||
private $budgets;
|
||||
|
||||
/** @var PiggyBankRepositoryInterface */
|
||||
private $piggyBanks;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
View::share('title', trans('firefly.transactions'));
|
||||
View::share('mainTitleIcon', 'fa-repeat');
|
||||
|
||||
$maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
|
||||
$maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
|
||||
$uploadSize = min($maxFileSize, $maxPostSize);
|
||||
View::share('uploadSize', $uploadSize);
|
||||
|
||||
// some useful repositories:
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->accounts = app(AccountRepositoryInterface::class);
|
||||
$this->budgets = app(BudgetRepositoryInterface::class);
|
||||
$this->piggyBanks = app(PiggyBankRepositoryInterface::class);
|
||||
$this->attachments = app(AttachmentHelperInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $what
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function create(string $what = TransactionType::DEPOSIT)
|
||||
{
|
||||
$what = strtolower($what);
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount();
|
||||
$piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
|
||||
$preFilled = Session::has('preFilled') ? session('preFilled') : [];
|
||||
$subTitle = trans('form.add_new_' . $what);
|
||||
$subTitleIcon = 'fa-plus';
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
|
||||
Session::put('preFilled', $preFilled);
|
||||
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('transactions.create.fromStore') !== true) {
|
||||
$url = URL::previous();
|
||||
Session::put('transactions.create.url', $url);
|
||||
}
|
||||
Session::forget('transactions.create.fromStore');
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'create-' . $what);
|
||||
|
||||
asort($piggies);
|
||||
|
||||
return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the form that allows a user to delete a transaction journal.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function delete(TransactionJournal $journal)
|
||||
{
|
||||
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
|
||||
$subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]);
|
||||
|
||||
// put previous url in session
|
||||
Session::put('transactions.delete.url', URL::previous());
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'delete-' . $what);
|
||||
|
||||
return view('transactions.delete', compact('journal', 'subTitle', 'what'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $transactionJournal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal)
|
||||
{
|
||||
$type = TransactionJournal::transactionTypeStr($transactionJournal);
|
||||
Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)])));
|
||||
|
||||
$repository->delete($transactionJournal);
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
// redirect to previous URL:
|
||||
return redirect(session('transactions.delete.url'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function edit(TransactionJournal $journal)
|
||||
{
|
||||
$count = $journal->transactions()->count();
|
||||
if ($count > 2) {
|
||||
return redirect(route('transactions.edit-split', [$journal->id]));
|
||||
}
|
||||
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$piggyBankList = ExpandedForm::makeSelectListWithEmpty($this->piggyBanks->getPiggyBanks());
|
||||
|
||||
// view related code
|
||||
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
|
||||
$what = strtolower(TransactionJournal::transactionTypeStr($journal));
|
||||
|
||||
// journal related code
|
||||
$sourceAccounts = TransactionJournal::sourceAccountList($journal);
|
||||
$destinationAccounts = TransactionJournal::destinationAccountList($journal);
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$preFilled = [
|
||||
'date' => TransactionJournal::dateAsString($journal),
|
||||
'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'),
|
||||
'book_date' => TransactionJournal::dateAsString($journal, 'book_date'),
|
||||
'process_date' => TransactionJournal::dateAsString($journal, 'process_date'),
|
||||
'category' => TransactionJournal::categoryAsString($journal),
|
||||
'budget_id' => TransactionJournal::budgetId($journal),
|
||||
'piggy_bank_id' => TransactionJournal::piggyBankId($journal),
|
||||
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
|
||||
'source_account_id' => $sourceAccounts->first()->id,
|
||||
'source_account_name' => $sourceAccounts->first()->name,
|
||||
'destination_account_id' => $destinationAccounts->first()->id,
|
||||
'destination_account_name' => $destinationAccounts->first()->name,
|
||||
'amount' => TransactionJournal::amountPositive($journal),
|
||||
|
||||
// new custom fields:
|
||||
'due_date' => TransactionJournal::dateAsString($journal, 'due_date'),
|
||||
'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'),
|
||||
'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'),
|
||||
'interal_reference' => $journal->getMeta('internal_reference'),
|
||||
'notes' => $journal->getMeta('notes'),
|
||||
];
|
||||
|
||||
if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
|
||||
$preFilled['destination_account_name'] = '';
|
||||
}
|
||||
|
||||
if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) {
|
||||
$preFilled['source_account_name'] = '';
|
||||
}
|
||||
|
||||
|
||||
Session::flash('preFilled', $preFilled);
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'edit-' . $what);
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('transactions.edit.fromUpdate') !== true) {
|
||||
Session::put('transactions.edit.url', URL::previous());
|
||||
}
|
||||
Session::forget('transactions.edit.fromUpdate');
|
||||
|
||||
return view(
|
||||
'transactions.edit',
|
||||
compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle')
|
||||
)->with('data', $preFilled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalFormRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
|
||||
{
|
||||
$doSplit = intval($request->get('split_journal')) === 1;
|
||||
$createAnother = intval($request->get('create_another')) === 1;
|
||||
$data = $request->getJournalData();
|
||||
$journal = $repository->store($data);
|
||||
if (is_null($journal->id)) {
|
||||
// error!
|
||||
Log::error('Could not store transaction journal: ', $journal->getErrors()->toArray());
|
||||
Session::flash('error', $journal->getErrors()->first());
|
||||
|
||||
return redirect(route('transactions.create', [$request->input('what')]))->withInput();
|
||||
}
|
||||
|
||||
$this->attachments->saveAttachmentsForModel($journal);
|
||||
|
||||
// store the journal only, flash the rest.
|
||||
if (count($this->attachments->getErrors()->get('attachments')) > 0) {
|
||||
Session::flash('error', $this->attachments->getErrors()->get('attachments'));
|
||||
}
|
||||
// flash messages
|
||||
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
|
||||
Session::flash('info', $this->attachments->getMessages()->get('attachments'));
|
||||
}
|
||||
|
||||
event(new StoredTransactionJournal($journal, $data['piggy_bank_id']));
|
||||
|
||||
Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
|
||||
Preferences::mark();
|
||||
|
||||
if ($createAnother === true) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
Session::put('transactions.create.fromStore', true);
|
||||
|
||||
return redirect(route('transactions.create', [$request->input('what')]))->withInput();
|
||||
}
|
||||
|
||||
if ($doSplit === true) {
|
||||
// redirect to edit screen:
|
||||
return redirect(route('transactions.edit-split', [$journal->id]));
|
||||
}
|
||||
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('transactions.create.url'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalFormRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
|
||||
{
|
||||
$data = $request->getJournalData();
|
||||
$journal = $repository->update($journal, $data);
|
||||
$this->attachments->saveAttachmentsForModel($journal);
|
||||
|
||||
// flash errors
|
||||
if (count($this->attachments->getErrors()->get('attachments')) > 0) {
|
||||
Session::flash('error', $this->attachments->getErrors()->get('attachments'));
|
||||
}
|
||||
// flash messages
|
||||
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
|
||||
Session::flash('info', $this->attachments->getMessages()->get('attachments'));
|
||||
}
|
||||
|
||||
event(new UpdatedTransactionJournal($journal));
|
||||
// update, get events by date and sort DESC
|
||||
|
||||
$type = strtolower(TransactionJournal::transactionTypeStr($journal));
|
||||
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])])));
|
||||
Preferences::mark();
|
||||
|
||||
// if wishes to split:
|
||||
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
Session::put('transactions.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('transactions.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('transactions.edit.url'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -15,19 +15,17 @@ namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Crud\Split\JournalInterface;
|
||||
use FireflyIII\Events\TransactionJournalUpdated;
|
||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\SplitJournalFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use Steam;
|
||||
@ -42,6 +40,22 @@ use View;
|
||||
*/
|
||||
class SplitController extends Controller
|
||||
{
|
||||
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accounts;
|
||||
|
||||
/** @var AttachmentHelperInterface */
|
||||
private $attachments;
|
||||
|
||||
/** @var BudgetRepositoryInterface */
|
||||
private $budgets;
|
||||
|
||||
/** @var CurrencyRepositoryInterface */
|
||||
private $currencies;
|
||||
|
||||
/** @var JournalTaskerInterface */
|
||||
private $tasker;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -50,47 +64,18 @@ class SplitController extends Controller
|
||||
parent::__construct();
|
||||
View::share('mainTitleIcon', 'fa-share-alt');
|
||||
View::share('title', trans('firefly.split-transactions'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function create(TransactionJournal $journal)
|
||||
{
|
||||
$currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface');
|
||||
$budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
|
||||
$piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
|
||||
// some useful repositories:
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->accounts = app(AccountRepositoryInterface::class);
|
||||
$this->budgets = app(BudgetRepositoryInterface::class);
|
||||
$this->tasker = app(JournalTaskerInterface::class);
|
||||
$this->attachments = app(AttachmentHelperInterface::class);
|
||||
$this->currencies = app(CurrencyRepositoryInterface::class);
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
|
||||
$assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account']));
|
||||
$sessionData = session('journal-data', []);
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$currencies = ExpandedForm::makeSelectList($currencyRepository->get());
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
|
||||
$piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanksWithAmount());
|
||||
$subTitle = trans('form.add_new_' . $sessionData['what']);
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$subTitleIcon = 'fa-plus';
|
||||
$preFilled = [
|
||||
'what' => $sessionData['what'] ?? 'withdrawal',
|
||||
'journal_amount' => $sessionData['amount'] ?? 0,
|
||||
'journal_source_account_id' => $sessionData['source_account_id'] ?? 0,
|
||||
'journal_source_account_name' => $sessionData['source_account_name'] ?? '',
|
||||
'description' => [$journal->description],
|
||||
'destination_account_name' => [$sessionData['destination_account_name']],
|
||||
'destination_account_id' => [$sessionData['destination_account_id']],
|
||||
'amount' => [$sessionData['amount']],
|
||||
'budget_id' => [$sessionData['budget_id']],
|
||||
'category' => [$sessionData['category']],
|
||||
];
|
||||
|
||||
return view(
|
||||
'split.journals.create',
|
||||
compact('journal', 'piggyBanks', 'subTitle', 'optionalFields', 'subTitleIcon', 'preFilled', 'assetAccounts', 'currencies', 'budgets', 'uploadSize')
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -102,17 +87,11 @@ class SplitController extends Controller
|
||||
*/
|
||||
public function edit(Request $request, TransactionJournal $journal)
|
||||
{
|
||||
$currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface');
|
||||
$budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$currencies = ExpandedForm::makeSelectList($currencyRepository->get());
|
||||
$assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account']));
|
||||
$currencies = ExpandedForm::makeSelectList($this->currencies->get());
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$preFilled = $this->arrayFromJournal($request, $journal);
|
||||
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
|
||||
$subTitleIcon = 'fa-pencil';
|
||||
@ -127,7 +106,7 @@ class SplitController extends Controller
|
||||
Session::forget('transactions.edit-split.fromUpdate');
|
||||
|
||||
return view(
|
||||
'split.journals.edit',
|
||||
'transactions.edit-split',
|
||||
compact(
|
||||
'subTitleIcon', 'currencies', 'optionalFields',
|
||||
'preFilled', 'subTitle', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts',
|
||||
@ -136,63 +115,31 @@ class SplitController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalInterface $repository
|
||||
* @param SplitJournalFormRequest $request
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function store(JournalInterface $repository, SplitJournalFormRequest $request, TransactionJournal $journal)
|
||||
{
|
||||
$data = $request->getSplitData();
|
||||
foreach ($data['transactions'] as $transaction) {
|
||||
$repository->storeTransaction($journal, $transaction);
|
||||
}
|
||||
|
||||
$repository->markAsComplete($journal);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('create_another')) === 1) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
Session::put('transactions.create.fromStore', true);
|
||||
|
||||
return redirect(route('transactions.create', [$request->input('what')]))->withInput();
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('transactions.create.url'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param SplitJournalFormRequest $request
|
||||
* @param JournalInterface $repository
|
||||
* @param AttachmentHelperInterface $att
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(TransactionJournal $journal, SplitJournalFormRequest $request, JournalInterface $repository, AttachmentHelperInterface $att)
|
||||
public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
|
||||
{
|
||||
|
||||
$data = $request->getSplitData();
|
||||
$journal = $repository->updateJournal($journal, $data);
|
||||
$data = $this->arrayFromInput($request);
|
||||
$journal = $repository->updateSplitJournal($journal, $data);
|
||||
|
||||
// save attachments:
|
||||
$att->saveAttachmentsForModel($journal);
|
||||
$this->attachments->saveAttachmentsForModel($journal);
|
||||
|
||||
event(new TransactionJournalUpdated($journal));
|
||||
event(new UpdatedTransactionJournal($journal));
|
||||
// update, get events by date and sort DESC
|
||||
|
||||
// flash messages
|
||||
if (count($att->getMessages()->get('attachments')) > 0) {
|
||||
Session::flash('info', $att->getMessages()->get('attachments'));
|
||||
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
|
||||
Session::flash('info', $this->attachments->getMessages()->get('attachments'));
|
||||
}
|
||||
|
||||
|
||||
$type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal));
|
||||
$type = strtolower(TransactionJournal::transactionTypeStr($journal));
|
||||
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])])));
|
||||
Preferences::mark();
|
||||
|
||||
@ -200,7 +147,7 @@ class SplitController extends Controller
|
||||
// set value so edit routine will not overwrite URL:
|
||||
Session::put('transactions.edit-split.fromUpdate', true);
|
||||
|
||||
return redirect(route('split.journal.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
|
||||
return redirect(route('transactions.edit-split', [$journal->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
@ -208,6 +155,39 @@ class SplitController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function arrayFromInput(Request $request): array
|
||||
{
|
||||
$array = [
|
||||
'journal_description' => $request->get('journal_description'),
|
||||
'journal_source_account_id' => $request->get('journal_source_account_id'),
|
||||
'journal_source_account_name' => $request->get('journal_source_account_name'),
|
||||
'journal_destination_account_id' => $request->get('journal_destination_account_id'),
|
||||
'currency_id' => $request->get('currency_id'),
|
||||
'what' => $request->get('what'),
|
||||
'date' => $request->get('date'),
|
||||
// all custom fields:
|
||||
'interest_date' => $request->get('interest_date'),
|
||||
'book_date' => $request->get('book_date'),
|
||||
'process_date' => $request->get('process_date'),
|
||||
'due_date' => $request->get('due_date'),
|
||||
'payment_date' => $request->get('payment_date'),
|
||||
'invoice_date' => $request->get('invoice_date'),
|
||||
'internal_reference' => $request->get('internal_reference'),
|
||||
'notes' => $request->get('notes'),
|
||||
'tags' => explode(',', $request->get('tags')),
|
||||
|
||||
// transactions.
|
||||
'transactions' => $this->getTransactionDataFromRequest($request),
|
||||
];
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param TransactionJournal $journal
|
||||
@ -222,149 +202,79 @@ class SplitController extends Controller
|
||||
'journal_description' => $request->old('journal_description', $journal->description),
|
||||
'journal_amount' => TransactionJournal::amountPositive($journal),
|
||||
'sourceAccounts' => $sourceAccounts,
|
||||
'journal_source_account_id' => $sourceAccounts->first()->id,
|
||||
'journal_source_account_name' => $sourceAccounts->first()->name,
|
||||
'journal_destination_account_id' => $destinationAccounts->first()->id,
|
||||
'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id),
|
||||
'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id),
|
||||
'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name),
|
||||
'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id),
|
||||
'currency_id' => $request->old('currency_id', $journal->transaction_currency_id),
|
||||
'destinationAccounts' => $destinationAccounts,
|
||||
'what' => strtolower(TransactionJournal::transactionTypeStr($journal)),
|
||||
'date' => $request->old('date', $journal->date),
|
||||
'interest_date' => $request->old('interest_date', $journal->interest_date),
|
||||
'book_date' => $request->old('book_date', $journal->book_date),
|
||||
'process_date' => $request->old('process_date', $journal->process_date),
|
||||
'description' => [],
|
||||
'source_account_id' => [],
|
||||
'source_account_name' => [],
|
||||
'destination_account_id' => [],
|
||||
'destination_account_name' => [],
|
||||
'amount' => [],
|
||||
'budget_id' => [],
|
||||
'category' => [],
|
||||
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
|
||||
|
||||
// all custom fields:
|
||||
'interest_date' => $request->old('interest_date', $journal->getMeta('interest_date')),
|
||||
'book_date' => $request->old('book_date', $journal->getMeta('book_date')),
|
||||
'process_date' => $request->old('process_date', $journal->getMeta('process_date')),
|
||||
'due_date' => $request->old('due_date', $journal->getMeta('due_date')),
|
||||
'payment_date' => $request->old('payment_date', $journal->getMeta('payment_date')),
|
||||
'invoice_date' => $request->old('invoice_date', $journal->getMeta('invoice_date')),
|
||||
'internal_reference' => $request->old('internal_reference', $journal->getMeta('internal_reference')),
|
||||
'notes' => $request->old('notes', $journal->getMeta('notes')),
|
||||
|
||||
// transactions.
|
||||
'transactions' => $this->getTransactionDataFromJournal($journal),
|
||||
];
|
||||
|
||||
// number of transactions present in old input:
|
||||
$previousCount = count($request->old('description'));
|
||||
|
||||
if ($previousCount === 0) {
|
||||
// build from scratch
|
||||
$transactions = $this->transactionsFromJournal($request, $journal);
|
||||
$array['description'] = $transactions['description'];
|
||||
$array['source_account_id'] = $transactions['source_account_id'];
|
||||
$array['source_account_name'] = $transactions['source_account_name'];
|
||||
$array['destination_account_id'] = $transactions['destination_account_id'];
|
||||
$array['destination_account_name'] = $transactions['destination_account_name'];
|
||||
$array['amount'] = $transactions['amount'];
|
||||
$array['budget_id'] = $transactions['budget_id'];
|
||||
$array['category'] = $transactions['category'];
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
while ($index < $previousCount) {
|
||||
$description = $request->old('description')[$index] ?? '';
|
||||
$destinationId = $request->old('destination_account_id')[$index] ?? 0;
|
||||
$destinationName = $request->old('destination_account_name')[$index] ?? '';
|
||||
$sourceId = $request->old('source_account_id')[$index] ?? 0;
|
||||
$sourceName = $request->old('source_account_name')[$index] ?? '';
|
||||
$amount = $request->old('amount')[$index] ?? '';
|
||||
$budgetId = $request->old('budget_id')[$index] ?? 0;
|
||||
$categoryName = $request->old('category')[$index] ?? '';
|
||||
|
||||
|
||||
// any transfer not from the source:
|
||||
$array['description'][] = $description;
|
||||
$array['source_account_id'][] = $sourceId;
|
||||
$array['source_account_name'][] = $sourceName;
|
||||
$array['destination_account_id'][] = $destinationId;
|
||||
$array['destination_account_name'][] = $destinationName;
|
||||
$array['amount'][] = $amount;
|
||||
$array['budget_id'][] = intval($budgetId);
|
||||
$array['category'][] = $categoryName;
|
||||
$index++;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function transactionsFromJournal(Request $request, TransactionJournal $journal): array
|
||||
private function getTransactionDataFromJournal(TransactionJournal $journal): array
|
||||
{
|
||||
/** @var Collection $transactions */
|
||||
$transactions = $journal->transactions()->get();
|
||||
|
||||
/*
|
||||
* Splitted journals always have ONE source OR ONE destination.
|
||||
* Withdrawals have ONE source (asset account)
|
||||
* Deposits have ONE destination (asset account)
|
||||
* Transfers have ONE of both (asset account)
|
||||
*/
|
||||
/** @var Account $singular */
|
||||
$singular = TransactionJournal::sourceAccountList($journal)->first();
|
||||
if ($journal->transactionType->type == TransactionType::DEPOSIT) {
|
||||
/** @var Account $singular */
|
||||
$singular = TransactionJournal::destinationAccountList($journal)->first();
|
||||
$transactions = $this->tasker->getTransactionsOverview($journal);
|
||||
$return = [];
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = [
|
||||
'description' => $transaction['description'],
|
||||
'source_account_id' => $transaction['source_account_id'],
|
||||
'source_account_name' => $transaction['source_account_name'],
|
||||
'destination_account_id' => $transaction['destination_account_id'],
|
||||
'destination_account_name' => $transaction['destination_account_name'],
|
||||
'amount' => round($transaction['destination_amount'], 2),
|
||||
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
|
||||
'category' => $transaction['category'],
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Loop all transactions. Collect info ONLY from the transaction that is NOT related to
|
||||
* the singular account.
|
||||
*/
|
||||
$index = 0;
|
||||
$return = [
|
||||
'description' => [],
|
||||
'source_account_id' => [],
|
||||
'source_account_name' => [],
|
||||
'destination_account_id' => [],
|
||||
'destination_account_name' => [],
|
||||
'amount' => [],
|
||||
'budget_id' => [],
|
||||
'category' => [],
|
||||
];
|
||||
return $return;
|
||||
}
|
||||
|
||||
Log::debug('now at transactionsFromJournal');
|
||||
|
||||
/**
|
||||
* @var int $current
|
||||
* @var Transaction $transaction
|
||||
*/
|
||||
foreach ($transactions as $current => $transaction) {
|
||||
$budget = $transaction->budgets()->first();
|
||||
$category = $transaction->categories()->first();
|
||||
$budgetId = 0;
|
||||
$categoryName = '';
|
||||
if (!is_null($budget)) {
|
||||
$budgetId = $budget->id;
|
||||
}
|
||||
|
||||
if (!is_null($category)) {
|
||||
$categoryName = $category->name;
|
||||
}
|
||||
|
||||
$budgetId = $request->old('budget_id')[$index] ?? $budgetId;
|
||||
$categoryName = $request->old('category')[$index] ?? $categoryName;
|
||||
$amount = $request->old('amount')[$index] ?? $transaction->amount;
|
||||
$description = $request->old('description')[$index] ?? $transaction->description;
|
||||
$destinationName = $request->old('destination_account_name')[$index] ?? $transaction->account->name;
|
||||
$sourceName = $request->old('source_account_name')[$index] ?? $transaction->account->name;
|
||||
$amount = bccomp($amount, '0') === -1 ? bcmul($amount, '-1') : $amount;
|
||||
|
||||
if ($transaction->account_id !== $singular->id) {
|
||||
$return['description'][] = $description;
|
||||
$return['destination_account_id'][] = $transaction->account_id;
|
||||
$return['destination_account_name'][] = $destinationName;
|
||||
$return['source_account_name'][] = $sourceName;
|
||||
$return['amount'][] = $amount;
|
||||
$return['budget_id'][] = intval($budgetId);
|
||||
$return['category'][] = $categoryName;
|
||||
// only add one when "valid" transaction
|
||||
$index++;
|
||||
}
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTransactionDataFromRequest(Request $request): array
|
||||
{
|
||||
$return = [];
|
||||
$transactions = $request->get('transactions');
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = [
|
||||
'description' => $transaction['description'],
|
||||
'source_account_id' => $transaction['source_account_id'] ?? 0,
|
||||
'source_account_name' => $transaction['source_account_name'] ?? '',
|
||||
'destination_account_id' => $transaction['destination_account_id'] ?? 0,
|
||||
'destination_account_name' => $transaction['destination_account_name'] ?? '',
|
||||
'amount' => round($transaction['amount'] ?? 0, 2),
|
||||
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
|
||||
'category' => $transaction['category'] ?? '',
|
||||
'user' => auth()->user()->id, // needed for accounts.
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
@ -14,22 +14,12 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Events\TransactionJournalStored;
|
||||
use FireflyIII\Events\TransactionJournalUpdated;
|
||||
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Requests\JournalFormRequest;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Session;
|
||||
use Steam;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
@ -40,7 +30,7 @@ use View;
|
||||
class TransactionController extends Controller
|
||||
{
|
||||
/**
|
||||
*
|
||||
* TransactionController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@ -48,187 +38,23 @@ class TransactionController extends Controller
|
||||
View::share('title', trans('firefly.transactions'));
|
||||
View::share('mainTitleIcon', 'fa-repeat');
|
||||
|
||||
$maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
|
||||
$maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
|
||||
$uploadSize = min($maxFileSize, $maxPostSize);
|
||||
|
||||
View::share('uploadSize', $uploadSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $what
|
||||
* @param Request $request
|
||||
* @param JournalTaskerInterface $tasker
|
||||
* @param string $what
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function create(string $what = TransactionType::DEPOSIT)
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
|
||||
$piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
|
||||
$what = strtolower($what);
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$assetAccounts = ExpandedForm::makeSelectList($accountRepository->getActiveAccountsByType(['Default account', 'Asset account']));
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
|
||||
$piggyBanks = $piggyRepository->getPiggyBanksWithAmount();
|
||||
$piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
|
||||
$preFilled = Session::has('preFilled') ? session('preFilled') : [];
|
||||
$subTitle = trans('form.add_new_' . $what);
|
||||
$subTitleIcon = 'fa-plus';
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
|
||||
Session::put('preFilled', $preFilled);
|
||||
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('transactions.create.fromStore') !== true) {
|
||||
$url = URL::previous();
|
||||
Session::put('transactions.create.url', $url);
|
||||
}
|
||||
Session::forget('transactions.create.fromStore');
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'create-' . $what);
|
||||
|
||||
asort($piggies);
|
||||
|
||||
|
||||
return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the form that allows a user to delete a transaction journal.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function delete(TransactionJournal $journal)
|
||||
{
|
||||
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
|
||||
$subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]);
|
||||
|
||||
// put previous url in session
|
||||
Session::put('transactions.delete.url', URL::previous());
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'delete-' . $what);
|
||||
|
||||
return view('transactions.delete', compact('journal', 'subTitle', 'what'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $transactionJournal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal)
|
||||
{
|
||||
$type = TransactionJournal::transactionTypeStr($transactionJournal);
|
||||
Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)])));
|
||||
|
||||
$repository->delete($transactionJournal);
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
// redirect to previous URL:
|
||||
return redirect(session('transactions.delete.url'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function edit(TransactionJournal $journal)
|
||||
{
|
||||
$count = $journal->transactions()->count();
|
||||
if ($count > 2) {
|
||||
return redirect(route('split.journal.edit', [$journal->id]));
|
||||
}
|
||||
|
||||
// code to get list data:
|
||||
$budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
|
||||
$piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
|
||||
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
|
||||
$assetAccounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account']));
|
||||
$budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
|
||||
$piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks());
|
||||
|
||||
// view related code
|
||||
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
|
||||
$what = strtolower(TransactionJournal::transactionTypeStr($journal));
|
||||
|
||||
// journal related code
|
||||
$sourceAccounts = TransactionJournal::sourceAccountList($journal);
|
||||
$destinationAccounts = TransactionJournal::destinationAccountList($journal);
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$preFilled = [
|
||||
'date' => TransactionJournal::dateAsString($journal),
|
||||
'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'),
|
||||
'book_date' => TransactionJournal::dateAsString($journal, 'book_date'),
|
||||
'process_date' => TransactionJournal::dateAsString($journal, 'process_date'),
|
||||
'category' => TransactionJournal::categoryAsString($journal),
|
||||
'budget_id' => TransactionJournal::budgetId($journal),
|
||||
'piggy_bank_id' => TransactionJournal::piggyBankId($journal),
|
||||
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
|
||||
'source_account_id' => $sourceAccounts->first()->id,
|
||||
'source_account_name' => $sourceAccounts->first()->name,
|
||||
'destination_account_id' => $destinationAccounts->first()->id,
|
||||
'destination_account_name' => $destinationAccounts->first()->name,
|
||||
'amount' => TransactionJournal::amountPositive($journal),
|
||||
|
||||
// new custom fields:
|
||||
'due_date' => TransactionJournal::dateAsString($journal, 'due_date'),
|
||||
'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'),
|
||||
'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'),
|
||||
'interal_reference' => $journal->getMeta('internal_reference'),
|
||||
'notes' => $journal->getMeta('notes'),
|
||||
];
|
||||
|
||||
if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
|
||||
$preFilled['destination_account_name'] = '';
|
||||
}
|
||||
|
||||
if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) {
|
||||
$preFilled['source_account_name'] = '';
|
||||
}
|
||||
|
||||
|
||||
Session::flash('preFilled', $preFilled);
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'edit-' . $what);
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('transactions.edit.fromUpdate') !== true) {
|
||||
Session::put('transactions.edit.url', URL::previous());
|
||||
}
|
||||
Session::forget('transactions.edit.fromUpdate');
|
||||
|
||||
return view(
|
||||
'transactions.edit',
|
||||
compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle')
|
||||
)->with('data', $preFilled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param string $what
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(Request $request, JournalRepositoryInterface $repository, string $what)
|
||||
public function index(Request $request, JournalTaskerInterface $tasker, string $what)
|
||||
{
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
|
||||
$types = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$subTitle = trans('firefly.title_' . $what);
|
||||
$page = intval($request->get('page'));
|
||||
$journals = $repository->getJournals($types, $page, $pageSize);
|
||||
$journals = $tasker->getJournals($types, $page, $pageSize);
|
||||
|
||||
$journals->setPath('transactions/' . $what);
|
||||
|
||||
@ -265,139 +91,20 @@ class TransactionController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $journal
|
||||
* @param JournalTaskerInterface $tasker
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function show(TransactionJournal $journal, JournalRepositoryInterface $repository)
|
||||
public function show(TransactionJournal $journal, JournalTaskerInterface $tasker)
|
||||
{
|
||||
$events = $repository->getPiggyBankEvents($journal);
|
||||
$transactions = $repository->getTransactions($journal);
|
||||
$events = $tasker->getPiggyBankEvents($journal);
|
||||
$transactions = $tasker->getTransactionsOverview($journal);
|
||||
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
|
||||
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
|
||||
|
||||
if ($transactions->count() > 2) {
|
||||
return view('split.journals.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
|
||||
}
|
||||
|
||||
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalFormRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
|
||||
{
|
||||
$att = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface');
|
||||
$doSplit = intval($request->get('split_journal')) === 1;
|
||||
$journalData = $request->getJournalData();
|
||||
|
||||
// store the journal only, flash the rest.
|
||||
if ($doSplit) {
|
||||
$journal = $repository->storeJournal($journalData);
|
||||
$journal->completed = false;
|
||||
$journal->save();
|
||||
|
||||
// store attachments:
|
||||
$att->saveAttachmentsForModel($journal);
|
||||
|
||||
// flash errors
|
||||
if (count($att->getErrors()->get('attachments')) > 0) {
|
||||
Session::flash('error', $att->getErrors()->get('attachments'));
|
||||
}
|
||||
// flash messages
|
||||
if (count($att->getMessages()->get('attachments')) > 0) {
|
||||
Session::flash('info', $att->getMessages()->get('attachments'));
|
||||
}
|
||||
|
||||
Session::put('journal-data', $journalData);
|
||||
|
||||
return redirect(route('split.journal.create', [$journal->id]));
|
||||
}
|
||||
|
||||
|
||||
// if not withdrawal, unset budgetid.
|
||||
if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) {
|
||||
$journalData['budget_id'] = 0;
|
||||
}
|
||||
|
||||
$journal = $repository->store($journalData);
|
||||
$att->saveAttachmentsForModel($journal);
|
||||
|
||||
// flash errors
|
||||
if (count($att->getErrors()->get('attachments')) > 0) {
|
||||
Session::flash('error', $att->getErrors()->get('attachments'));
|
||||
}
|
||||
// flash messages
|
||||
if (count($att->getMessages()->get('attachments')) > 0) {
|
||||
Session::flash('info', $att->getMessages()->get('attachments'));
|
||||
}
|
||||
|
||||
event(new TransactionJournalStored($journal, intval($journalData['piggy_bank_id'])));
|
||||
|
||||
Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('create_another')) === 1) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
Session::put('transactions.create.fromStore', true);
|
||||
|
||||
return redirect(route('transactions.create', [$request->input('what')]))->withInput();
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('transactions.create.url'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param JournalFormRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param AttachmentHelperInterface $att
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal)
|
||||
{
|
||||
$journalData = $request->getJournalData();
|
||||
$repository->update($journal, $journalData);
|
||||
|
||||
// save attachments:
|
||||
$att->saveAttachmentsForModel($journal);
|
||||
|
||||
// flash errors
|
||||
if (count($att->getErrors()->get('attachments')) > 0) {
|
||||
Session::flash('error', $att->getErrors()->get('attachments'));
|
||||
}
|
||||
// flash messages
|
||||
if (count($att->getMessages()->get('attachments')) > 0) {
|
||||
Session::flash('info', $att->getMessages()->get('attachments'));
|
||||
}
|
||||
|
||||
event(new TransactionJournalUpdated($journal));
|
||||
// update, get events by date and sort DESC
|
||||
|
||||
$type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal));
|
||||
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($journalData['description'])])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
Session::put('transactions.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('transactions.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect(session('transactions.edit.url'));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use Input;
|
||||
@ -37,86 +36,110 @@ class JournalFormRequest extends Request
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and validates the data required to store a new journal. Can handle both single transaction journals and split journals.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJournalData()
|
||||
{
|
||||
$tags = $this->getFieldOrEmptyString('tags');
|
||||
$data = [
|
||||
'what' => $this->get('what'), // type. can be 'deposit', 'withdrawal' or 'transfer'
|
||||
'user' => auth()->user()->id,
|
||||
'date' => new Carbon($this->get('date')),
|
||||
'tags' => explode(',', $this->getFieldOrEmptyString('tags')),
|
||||
'currency_id' => intval($this->get('amount_currency_id_amount')),
|
||||
|
||||
return [
|
||||
'what' => $this->get('what'),
|
||||
'description' => trim($this->get('description')),
|
||||
'source_account_id' => intval($this->get('source_account_id')),
|
||||
'source_account_name' => trim($this->getFieldOrEmptyString('source_account_name')),
|
||||
'destination_account_id' => intval($this->get('destination_account_id')),
|
||||
'destination_account_name' => trim($this->getFieldOrEmptyString('destination_account_name')),
|
||||
'amount' => round($this->get('amount'), 2),
|
||||
'user' => auth()->user()->id,
|
||||
'amount_currency_id_amount' => intval($this->get('amount_currency_id_amount')),
|
||||
'date' => new Carbon($this->get('date')),
|
||||
'interest_date' => $this->getDateOrNull('interest_date'),
|
||||
'book_date' => $this->getDateOrNull('book_date'),
|
||||
'process_date' => $this->getDateOrNull('process_date'),
|
||||
'budget_id' => intval($this->get('budget_id')),
|
||||
'category' => trim($this->getFieldOrEmptyString('category')),
|
||||
'tags' => explode(',', $tags),
|
||||
'piggy_bank_id' => intval($this->get('piggy_bank_id')),
|
||||
// all custom fields:
|
||||
'interest_date' => $this->getDateOrNull('interest_date'),
|
||||
'book_date' => $this->getDateOrNull('book_date'),
|
||||
'process_date' => $this->getDateOrNull('process_date'),
|
||||
'due_date' => $this->getDateOrNull('due_date'),
|
||||
'payment_date' => $this->getDateOrNull('payment_date'),
|
||||
'invoice_date' => $this->getDateOrNull('invoice_date'),
|
||||
'internal_reference' => trim(strval($this->get('internal_reference'))),
|
||||
'notes' => trim(strval($this->get('notes'))),
|
||||
|
||||
// new custom fields here:
|
||||
'due_date' => $this->getDateOrNull('due_date'),
|
||||
'payment_date' => $this->getDateOrNull('payment_date'),
|
||||
'invoice_date' => $this->getDateOrNull('invoice_date'),
|
||||
'internal_reference' => trim(strval($this->get('internal_reference'))),
|
||||
'notes' => trim(strval($this->get('notes'))),
|
||||
// transaction / journal data:
|
||||
'description' => $this->getFieldOrEmptyString('description'),
|
||||
'amount' => round($this->get('amount'), 2),
|
||||
'budget_id' => intval($this->get('budget_id')),
|
||||
'category' => $this->getFieldOrEmptyString('category'),
|
||||
'source_account_id' => intval($this->get('source_account_id')),
|
||||
'source_account_name' => $this->getFieldOrEmptyString('source_account_name'),
|
||||
'destination_account_id' => $this->getFieldOrEmptyString('destination_account_id'),
|
||||
'destination_account_name' => $this->getFieldOrEmptyString('destination_account_name'),
|
||||
'piggy_bank_id' => intval($this->get('piggy_bank_id')),
|
||||
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$what = Input::get('what');
|
||||
$rules = [
|
||||
'description' => 'required|min:1,max:255',
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'amount' => 'numeric|required|min:0.01',
|
||||
'date' => 'required|date',
|
||||
'process_date' => 'date',
|
||||
'book_date' => 'date',
|
||||
'interest_date' => 'date',
|
||||
'category' => 'between:1,255',
|
||||
'amount_currency_id_amount' => 'required|exists:transaction_currencies,id',
|
||||
'piggy_bank_id' => 'numeric',
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'date' => 'required|date',
|
||||
|
||||
// new custom fields here:
|
||||
'due_date' => 'date',
|
||||
'payment_date' => 'date',
|
||||
'internal_reference' => 'min:1,max:255',
|
||||
'notes' => 'min:1,max:65536',
|
||||
// then, custom fields:
|
||||
'interest_date' => 'date',
|
||||
'book_date' => 'date',
|
||||
'process_date' => 'date',
|
||||
'due_date' => 'date',
|
||||
'payment_date' => 'date',
|
||||
'invoice_date' => 'date',
|
||||
'internal_reference' => 'min:1,max:255',
|
||||
'notes' => 'min:1,max:50000',
|
||||
// and then transaction rules:
|
||||
'description' => 'required|between:1,255',
|
||||
'amount' => 'numeric|required|min:0.01',
|
||||
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id',
|
||||
'category' => 'between:1,255',
|
||||
'source_account_id' => 'numeric|belongsToUser:accounts,id',
|
||||
'source_account_name' => 'between:1,255',
|
||||
'destination_account_id' => 'numeric|belongsToUser:accounts,id',
|
||||
'destination_account_name' => 'between:1,255',
|
||||
'piggy_bank_id' => 'between:1,255',
|
||||
];
|
||||
|
||||
// some rules get an upgrade depending on the type of data:
|
||||
$rules = $this->enhanceRules($what, $rules);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspired by https://www.youtube.com/watch?v=WwnI0RS6J5A
|
||||
*
|
||||
* @param string $what
|
||||
* @param array $rules
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function enhanceRules(string $what, array $rules): array
|
||||
{
|
||||
switch ($what) {
|
||||
case strtolower(TransactionType::WITHDRAWAL):
|
||||
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
|
||||
$rules['destination_account_name'] = 'between:1,255';
|
||||
if (intval(Input::get('budget_id')) != 0) {
|
||||
$rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets';
|
||||
}
|
||||
break;
|
||||
case strtolower(TransactionType::DEPOSIT):
|
||||
$rules['source_account_name'] = 'between:1,255';
|
||||
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
|
||||
break;
|
||||
case strtolower(TransactionType::TRANSFER):
|
||||
// this may not work:
|
||||
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id';
|
||||
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id';
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('Cannot handle transaction type of type ' . e($what) . '.');
|
||||
throw new FireflyException('Cannot handle transaction type of type ' . e($what) . ' . ');
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
@ -25,6 +25,7 @@ use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\User;
|
||||
|
||||
/**
|
||||
* HOME
|
||||
@ -115,6 +116,13 @@ Breadcrumbs::register(
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'admin.users.show', function (BreadCrumbGenerator $breadcrumbs, User $user) {
|
||||
$breadcrumbs->parent('admin.users');
|
||||
$breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', [$user->id]));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'admin.users.domains', function (BreadCrumbGenerator $breadcrumbs) {
|
||||
$breadcrumbs->parent('admin.index');
|
||||
@ -598,9 +606,9 @@ Breadcrumbs::register(
|
||||
* SPLIT
|
||||
*/
|
||||
Breadcrumbs::register(
|
||||
'split.journal.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
|
||||
'transactions.edit-split', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
|
||||
$breadcrumbs->parent('transactions.show', $journal);
|
||||
$breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('split.journal.edit', [$journal->id]));
|
||||
$breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit-split', [$journal->id]));
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -13,6 +13,7 @@ declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Import\Converter;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use Log;
|
||||
@ -69,11 +70,20 @@ class OpposingAccountIban extends BasicConverter implements ConverterInterface
|
||||
return $account;
|
||||
}
|
||||
|
||||
$account = $repository->store(
|
||||
['name' => $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true,
|
||||
'openingBalance' => 0]
|
||||
);
|
||||
$this->setCertainty(100);
|
||||
// the IBAN given may not be a valid IBAN. If not, we cannot store by
|
||||
// iban and we have no opposing account. There should be some kind of fall back
|
||||
// routine.
|
||||
try {
|
||||
$account = $repository->store(
|
||||
['name' => $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true,
|
||||
'openingBalance' => 0]
|
||||
);
|
||||
$this->setCertainty(100);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e);
|
||||
|
||||
$account = new Account;
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
@ -111,9 +111,14 @@ class CsvImporter implements ImporterInterface
|
||||
// set some vars:
|
||||
$object->setUser($this->job->user);
|
||||
$config = $this->job->configuration;
|
||||
$json = json_encode($row);
|
||||
|
||||
if ($json === false) {
|
||||
throw new FireflyException(sprintf('Could not process row #%d. Are you sure the uploaded file is encoded as "UTF-8"?', $index));
|
||||
}
|
||||
|
||||
// hash the row:
|
||||
$hash = hash('sha256', json_encode($row));
|
||||
$hash = hash('sha256', $json);
|
||||
$object->importValue('hash', 100, $hash);
|
||||
|
||||
// and this is the point where the specifix go to work.
|
||||
|
@ -15,7 +15,7 @@ namespace FireflyIII\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -155,10 +155,10 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
|
||||
*/
|
||||
protected function collectJournals()
|
||||
{
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
/** @var JournalTaskerInterface $tasker */
|
||||
$tasker = app(JournalTaskerInterface::class);
|
||||
|
||||
return $repository->getJournalsInRange($this->accounts, $this->startDate, $this->endDate);
|
||||
return $tasker->getJournalsInRange($this->accounts, $this->startDate, $this->endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
54
app/Models/Note.php
Normal file
54
app/Models/Note.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Note.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\Note
|
||||
*
|
||||
* @property integer $id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property string $deleted_at
|
||||
* @property integer $noteable_id
|
||||
* @property string $noteable_type
|
||||
* @property string $title
|
||||
* @property string $text
|
||||
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $noteable
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereNoteableId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereNoteableType($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereTitle($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note whereText($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Note extends Model
|
||||
{
|
||||
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
|
||||
protected $fillable = ['title', 'text'];
|
||||
|
||||
/**
|
||||
* Get all of the owning noteable models. Currently only piggy bank
|
||||
*/
|
||||
public function noteable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
}
|
@ -56,6 +56,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @mixin \Eloquent
|
||||
* @property boolean $active
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\PiggyBank whereActive($value)
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes
|
||||
*/
|
||||
class PiggyBank extends Model
|
||||
{
|
||||
@ -146,6 +147,14 @@ class PiggyBank extends Model
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the piggy bank's notes.
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->morphMany('FireflyIII\Models\Note', 'noteable');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
|
@ -55,7 +55,7 @@ class Transaction extends Model
|
||||
{
|
||||
|
||||
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
|
||||
protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount'];
|
||||
protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier'];
|
||||
protected $hidden = ['encrypted'];
|
||||
protected $rules
|
||||
= [
|
||||
|
@ -434,6 +434,9 @@ class TransactionJournal extends TransactionJournalSupport
|
||||
|
||||
return new TransactionJournalMeta();
|
||||
}
|
||||
if (is_string($value) && strlen($value) === 0) {
|
||||
return new TransactionJournalMeta();
|
||||
}
|
||||
|
||||
if ($value instanceof Carbon) {
|
||||
$value = $value->toW3cString();
|
||||
|
@ -35,42 +35,50 @@ class EventServiceProvider extends ServiceProvider
|
||||
*/
|
||||
protected $listen
|
||||
= [
|
||||
'FireflyIII\Events\TransactionJournalUpdated' => [
|
||||
'FireflyIII\Handlers\Events\ScanForBillsAfterUpdate',
|
||||
'FireflyIII\Handlers\Events\UpdateJournalConnection',
|
||||
'FireflyIII\Handlers\Events\FireRulesForUpdate',
|
||||
// new event handlers:
|
||||
'FireflyIII\Events\ConfirmedUser' => // is a User related event.
|
||||
[
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@storeConfirmationIpAddress',
|
||||
],
|
||||
'FireflyIII\Events\RegisteredUser' => // is a User related event.
|
||||
[
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessage',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@storeRegistrationIpAddress',
|
||||
],
|
||||
'FireflyIII\Events\ResentConfirmation' => // is a User related event.
|
||||
[
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessageAgain',
|
||||
],
|
||||
'FireflyIII\Events\StoredBudgetLimit' => // is a Budget related event.
|
||||
[
|
||||
'FireflyIII\Handlers\Events\BudgetEventHandler@storeRepetition',
|
||||
],
|
||||
|
||||
],
|
||||
'FireflyIII\Events\UpdatedBudgetLimit' => // is a Budget related event.
|
||||
[
|
||||
'FireflyIII\Handlers\Events\BudgetEventHandler@updateRepetition',
|
||||
],
|
||||
|
||||
'FireflyIII\Events\BudgetLimitStored' => [
|
||||
'FireflyIII\Handlers\Events\BudgetLimitEventHandler@store',
|
||||
],
|
||||
'FireflyIII\Events\BudgetLimitUpdated' => [
|
||||
'FireflyIII\Handlers\Events\BudgetLimitEventHandler@update',
|
||||
],
|
||||
'FireflyIII\Events\TransactionStored' => [
|
||||
'FireflyIII\Handlers\Events\ConnectTransactionToPiggyBank',
|
||||
],
|
||||
'FireflyIII\Events\TransactionJournalStored' => [
|
||||
'FireflyIII\Handlers\Events\ScanForBillsAfterStore',
|
||||
'FireflyIII\Handlers\Events\ConnectJournalToPiggyBank',
|
||||
'FireflyIII\Handlers\Events\FireRulesForStore',
|
||||
],
|
||||
'Illuminate\Auth\Events\Logout' => [
|
||||
'FireflyIII\Handlers\Events\UserEventListener@onUserLogout',
|
||||
],
|
||||
'FireflyIII\Events\UserRegistration' => [
|
||||
'FireflyIII\Handlers\Events\SendRegistrationMail',
|
||||
'FireflyIII\Handlers\Events\AttachUserRole',
|
||||
'FireflyIII\Handlers\Events\UserConfirmation@sendConfirmation',
|
||||
'FireflyIII\Handlers\Events\UserSaveIpAddress@saveFromRegistration',
|
||||
],
|
||||
'FireflyIII\Events\UserIsConfirmed' => [
|
||||
'FireflyIII\Handlers\Events\UserSaveIpAddress@saveFromConfirmation',
|
||||
],
|
||||
'FireflyIII\Events\ResendConfirmation' => [
|
||||
'FireflyIII\Handlers\Events\UserConfirmation@resendConfirmation',
|
||||
],
|
||||
'FireflyIII\Events\StoredTransactionJournal' => // is a Transaction Journal related event.
|
||||
[
|
||||
'FireflyIII\Handlers\Events\StoredJournalEventHandler@scanBills',
|
||||
'FireflyIII\Handlers\Events\StoredJournalEventHandler@connectToPiggyBank',
|
||||
'FireflyIII\Handlers\Events\StoredJournalEventHandler@processRules',
|
||||
],
|
||||
'FireflyIII\Events\UpdatedTransactionJournal' => // is a Transaction Journal related event.
|
||||
[
|
||||
'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@scanBills',
|
||||
'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@connectToPiggyBank',
|
||||
'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@processRules',
|
||||
],
|
||||
|
||||
// LARAVEL EVENTS:
|
||||
'Illuminate\Auth\Events\Logout' =>
|
||||
[
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@logoutUser',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
@ -83,9 +91,6 @@ class EventServiceProvider extends ServiceProvider
|
||||
parent::boot();
|
||||
$this->registerDeleteEvents();
|
||||
$this->registerCreateEvents();
|
||||
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,6 +41,12 @@ class JournalServiceProvider extends ServiceProvider
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->registerRepository();
|
||||
$this->registerTasker();
|
||||
}
|
||||
|
||||
private function registerRepository()
|
||||
{
|
||||
$this->app->bind(
|
||||
'FireflyIII\Repositories\Journal\JournalRepositoryInterface',
|
||||
@ -56,4 +62,21 @@ class JournalServiceProvider extends ServiceProvider
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function registerTasker()
|
||||
{
|
||||
$this->app->bind(
|
||||
'FireflyIII\Repositories\Journal\JournalTaskerInterface',
|
||||
function (Application $app, array $arguments) {
|
||||
if (!isset($arguments[0]) && $app->auth->check()) {
|
||||
return app('FireflyIII\Repositories\Journal\JournalTasker', [auth()->user()]);
|
||||
}
|
||||
if (!isset($arguments[0]) && !$app->auth->check()) {
|
||||
throw new FireflyException('There is no user present.');
|
||||
}
|
||||
|
||||
return app('FireflyIII\Repositories\Journal\JournalTasker', $arguments);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,15 @@ namespace FireflyIII\Repositories\Bill;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Navigation;
|
||||
|
||||
/**
|
||||
@ -222,7 +225,8 @@ class BillRepository implements BillRepositoryInterface
|
||||
|
||||
/**
|
||||
* Get the total amount of money paid for the users active bills in the date range given.
|
||||
* This amount will be negative (they're expenses).
|
||||
* This amount will be negative (they're expenses). This method is equal to
|
||||
* getBillsUnpaidInRange. So the debug comments are gone.
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
@ -231,29 +235,22 @@ class BillRepository implements BillRepositoryInterface
|
||||
*/
|
||||
public function getBillsPaidInRange(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$amount = '0';
|
||||
$bills = $this->getActiveBills();
|
||||
|
||||
$bills = $this->getActiveBills();
|
||||
$sum = '0';
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
$ranges = $this->getRanges($bill, $start, $end);
|
||||
|
||||
foreach ($ranges as $range) {
|
||||
$paid = $bill->transactionJournals()
|
||||
->before($range['end'])
|
||||
->after($range['start'])
|
||||
->leftJoin(
|
||||
'transactions', function (JoinClause $join) {
|
||||
$join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0);
|
||||
}
|
||||
)
|
||||
->first([DB::raw('SUM(transactions.amount) AS sum_amount')]);
|
||||
$sumAmount = $paid->sum_amount ?? '0';
|
||||
$amount = bcadd($amount, $sumAmount);
|
||||
/** @var Collection $set */
|
||||
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
|
||||
if ($set->count() > 0) {
|
||||
$journalIds = $set->pluck('id')->toArray();
|
||||
$amount = strval(Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'));
|
||||
$sum = bcadd($sum, $amount);
|
||||
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
|
||||
}
|
||||
Log::debug('---');
|
||||
}
|
||||
|
||||
return $amount;
|
||||
return $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -266,32 +263,28 @@ class BillRepository implements BillRepositoryInterface
|
||||
*/
|
||||
public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$amount = '0';
|
||||
$bills = $this->getActiveBills();
|
||||
|
||||
$bills = $this->getActiveBills();
|
||||
$sum = '0';
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
$ranges = $this->getRanges($bill, $start, $end);
|
||||
$paidBill = '0';
|
||||
foreach ($ranges as $range) {
|
||||
$paid = $bill->transactionJournals()
|
||||
->before($range['end'])
|
||||
->after($range['start'])
|
||||
->leftJoin(
|
||||
'transactions', function (JoinClause $join) {
|
||||
$join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0);
|
||||
}
|
||||
)
|
||||
->first([DB::raw('SUM(transactions.amount) AS sum_amount')]);
|
||||
$sumAmount = $paid->sum_amount ?? '0';
|
||||
$paidBill = bcadd($sumAmount, $paidBill);
|
||||
}
|
||||
if ($paidBill == 0) {
|
||||
$amount = bcadd($amount, $bill->expectedAmount);
|
||||
Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
|
||||
$dates = $this->getPayDatesInRange($bill, $start, $end);
|
||||
$count = $bill->transactionJournals()->after($start)->before($end)->count();
|
||||
$total = $dates->count() - $count;
|
||||
|
||||
Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
|
||||
|
||||
if ($total > 0) {
|
||||
|
||||
$average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
|
||||
$multi = bcmul($average, strval($total));
|
||||
$sum = bcadd($sum, $multi);
|
||||
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
|
||||
}
|
||||
Log::debug('---');
|
||||
}
|
||||
|
||||
return $amount;
|
||||
return $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,6 +347,61 @@ class BillRepository implements BillRepositoryInterface
|
||||
return $avg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Between start and end, tells you on which date(s) the bill is expected to hit.
|
||||
*
|
||||
* @param Bill $bill
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
$set = new Collection;
|
||||
Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq));
|
||||
|
||||
/*
|
||||
* Start at 2016-10-01, see when we expect the bill to hit:
|
||||
*/
|
||||
$currentStart = clone $start;
|
||||
Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d')));
|
||||
|
||||
while ($currentStart <= $end) {
|
||||
Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
|
||||
$nextExpectedMatch = $this->nextDateMatch($bill, $currentStart);
|
||||
Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
|
||||
/*
|
||||
* If nextExpectedMatch is after end, we continue:
|
||||
*/
|
||||
if ($nextExpectedMatch > $end) {
|
||||
Log::debug(
|
||||
sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch->format('Y-m-d'), $end->format('Y-m-d'))
|
||||
);
|
||||
break;
|
||||
}
|
||||
// add to set
|
||||
$set->push(clone $nextExpectedMatch);
|
||||
Log::debug(sprintf('Now %d dates in set.', $set->count()));
|
||||
|
||||
// add day if necessary.
|
||||
$nextExpectedMatch->addDay();
|
||||
|
||||
Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
|
||||
|
||||
$currentStart = clone $nextExpectedMatch;
|
||||
}
|
||||
$simple = $set->each(
|
||||
function (Carbon $date) {
|
||||
return $date->format('Y-m-d');
|
||||
}
|
||||
);
|
||||
Log::debug(sprintf('Found dates between %s and %s:', $start->format('Y-m-d'), $end->format('Y-m-d')), $simple->toArray());
|
||||
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
*
|
||||
@ -377,48 +425,6 @@ class BillRepository implements BillRepositoryInterface
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself)
|
||||
* and returns date ranges that fall within the given range; those ranges are the bills expected. When a bill is due on the 14th of the month and
|
||||
* you give 1st and the 31st of that month as argument, you'll get one response, matching the range of your bill.
|
||||
*
|
||||
* @param Bill $bill
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRanges(Bill $bill, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$startOfBill = Navigation::startOfPeriod($start, $bill->repeat_freq);
|
||||
|
||||
|
||||
// all periods of this bill up until the current period:
|
||||
$billStarts = [];
|
||||
while ($startOfBill < $end) {
|
||||
|
||||
$endOfBill = Navigation::endOfPeriod($startOfBill, $bill->repeat_freq);
|
||||
|
||||
$billStarts[] = [
|
||||
'start' => clone $startOfBill,
|
||||
'end' => clone $endOfBill,
|
||||
];
|
||||
// actually the next one:
|
||||
$startOfBill = Navigation::addPeriod($startOfBill, $bill->repeat_freq, $bill->skip);
|
||||
|
||||
}
|
||||
// for each
|
||||
$validRanges = [];
|
||||
foreach ($billStarts as $dateEntry) {
|
||||
if ($dateEntry['end'] > $start && $dateEntry['start'] < $end) {
|
||||
// count transactions for bill in this range (not relevant yet!):
|
||||
$validRanges[] = $dateEntry;
|
||||
}
|
||||
}
|
||||
|
||||
return $validRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* @param Carbon $date
|
||||
@ -461,52 +467,87 @@ class BillRepository implements BillRepositoryInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* Given a bill and a date, this method will tell you at which moment this bill expects its next
|
||||
* transaction. Whether or not it is there already, is not relevant.
|
||||
*
|
||||
* @param Bill $bill
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return \Carbon\Carbon
|
||||
*/
|
||||
public function nextExpectedMatch(Bill $bill): Carbon
|
||||
public function nextDateMatch(Bill $bill, Carbon $date): Carbon
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($bill->id);
|
||||
$cache->addProperty('nextDateMatch');
|
||||
$cache->addProperty($date);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
// find the most recent date for this bill NOT in the future. Cache this date:
|
||||
$start = clone $bill->date;
|
||||
Log::debug('nextDateMatch: Start is ' . $start->format('Y-m-d'));
|
||||
|
||||
$finalDate = Carbon::now();
|
||||
$finalDate->year = 1900;
|
||||
if ($bill->active == 0) {
|
||||
return $finalDate;
|
||||
while ($start < $date) {
|
||||
Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
|
||||
$start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
|
||||
Log::debug('Start is now ' . $start->format('Y-m-d'));
|
||||
}
|
||||
|
||||
/*
|
||||
* $today is the start of the next period, to make sure FF3 won't miss anything
|
||||
* when the current period has a transaction journal.
|
||||
*/
|
||||
/** @var \Carbon\Carbon $obj */
|
||||
$obj = new Carbon;
|
||||
$today = Navigation::addPeriod($obj, $bill->repeat_freq, 0);
|
||||
$end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
|
||||
|
||||
$skip = $bill->skip + 1;
|
||||
$start = Navigation::startOfPeriod($obj, $bill->repeat_freq);
|
||||
/*
|
||||
* go back exactly one month/week/etc because FF3 does not care about 'next'
|
||||
* bills if they're too far into the past.
|
||||
*/
|
||||
Log::debug('nextDateMatch: Final start is ' . $start->format('Y-m-d'));
|
||||
Log::debug('nextDateMatch: Matching end is ' . $end->format('Y-m-d'));
|
||||
|
||||
$counter = 0;
|
||||
while ($start <= $today) {
|
||||
if (($counter % $skip) == 0) {
|
||||
// do something.
|
||||
$end = Navigation::endOfPeriod(clone $start, $bill->repeat_freq);
|
||||
$journalCount = $bill->transactionJournals()->before($end)->after($start)->count();
|
||||
if ($journalCount == 0) {
|
||||
$finalDate = new Carbon($start->format('Y-m-d'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
$cache->store($start);
|
||||
|
||||
// add period for next round!
|
||||
$start = Navigation::addPeriod($start, $bill->repeat_freq, 0);
|
||||
$counter++;
|
||||
return $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the date in $date, this method will return a moment in the future where the bill is expected to be paid.
|
||||
*
|
||||
* @param Bill $bill
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($bill->id);
|
||||
$cache->addProperty('nextExpectedMatch');
|
||||
$cache->addProperty($date);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
// find the most recent date for this bill NOT in the future. Cache this date:
|
||||
$start = clone $bill->date;
|
||||
Log::debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d'));
|
||||
|
||||
while ($start < $date) {
|
||||
Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
|
||||
$start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
|
||||
Log::debug('Start is now ' . $start->format('Y-m-d'));
|
||||
}
|
||||
|
||||
return $finalDate;
|
||||
$end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
|
||||
|
||||
// see if the bill was paid in this period.
|
||||
$journalCount = $bill->transactionJournals()->before($end)->after($start)->count();
|
||||
|
||||
if ($journalCount > 0) {
|
||||
// this period had in fact a bill. The new start is the current end, and we create a new end.
|
||||
Log::debug(sprintf('Journal count is %d, so start becomes %s', $journalCount, $end->format('Y-m-d')));
|
||||
$start = clone $end;
|
||||
$end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip);
|
||||
}
|
||||
Log::debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d'));
|
||||
Log::debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d'));
|
||||
|
||||
$cache->store($start);
|
||||
|
||||
return $start;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,24 +132,22 @@ interface BillRepositoryInterface
|
||||
public function getOverallAverage($bill): string;
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPossiblyRelatedJournals(Bill $bill): Collection;
|
||||
|
||||
/**
|
||||
* Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself)
|
||||
* and returns date ranges that fall within the given range; those ranges are the bills expected. When a bill is due on the 14th of the month and
|
||||
* you give 1st and the 31st of that month as argument, you'll get one response, matching the range of your bill (from the 14th to the 31th).
|
||||
* Between start and end, tells you on which date(s) the bill is expected to hit.
|
||||
*
|
||||
* @param Bill $bill
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return array
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRanges(Bill $bill, Carbon $start, Carbon $end): array;
|
||||
public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPossiblyRelatedJournals(Bill $bill): Collection;
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
@ -168,11 +166,23 @@ interface BillRepositoryInterface
|
||||
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* Given a bill and a date, this method will tell you at which moment this bill expects its next
|
||||
* transaction. Whether or not it is there already, is not relevant.
|
||||
*
|
||||
* @param Bill $bill
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return \Carbon\Carbon
|
||||
*/
|
||||
public function nextExpectedMatch(Bill $bill): Carbon;
|
||||
public function nextDateMatch(Bill $bill, Carbon $date): Carbon;
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return \Carbon\Carbon
|
||||
*/
|
||||
public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon;
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
|
@ -14,8 +14,8 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\BudgetLimitStored;
|
||||
use FireflyIII\Events\BudgetLimitUpdated;
|
||||
use FireflyIII\Events\StoredBudgetLimit;
|
||||
use FireflyIII\Events\UpdatedBudgetLimit;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\LimitRepetition;
|
||||
@ -555,7 +555,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
$limit->save();
|
||||
|
||||
// fire event to create or update LimitRepetition.
|
||||
event(new BudgetLimitUpdated($limit, $end));
|
||||
event(new UpdatedBudgetLimit($limit, $end));
|
||||
|
||||
return $limit;
|
||||
}
|
||||
@ -568,7 +568,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
$limit->repeat_freq = $repeatFreq;
|
||||
$limit->repeats = 0;
|
||||
$limit->save();
|
||||
event(new BudgetLimitStored($limit, $end));
|
||||
event(new StoredBudgetLimit($limit, $end));
|
||||
|
||||
|
||||
// likewise, there should be a limit repetition to match the end date
|
||||
|
@ -13,23 +13,18 @@ declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Repositories\Journal;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
@ -56,42 +51,6 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount in the account before the specified transaction took place.
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function balanceBeforeTransaction(Transaction $transaction): string
|
||||
{
|
||||
// some dates from journal
|
||||
$journal = $transaction->transactionJournal;
|
||||
$query = Transaction::
|
||||
leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $transaction->account_id)
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
->where(
|
||||
function (Builder $q) use ($journal) {
|
||||
$q->where('transaction_journals.date', '<', $journal->date->format('Y-m-d'));
|
||||
$q->orWhere(
|
||||
function (Builder $qq) use ($journal) {
|
||||
$qq->where('transaction_journals.date', '=', $journal->date->format('Y-m-d'));
|
||||
$qq->where('transaction_journals.order', '>', $journal->order);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
)
|
||||
->where('transactions.id', '!=', $transaction->id)
|
||||
->whereNull('transactions.deleted_at')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->groupBy('transaction_journals.id');
|
||||
$sum = $query->sum('transactions.amount');
|
||||
|
||||
return strval($sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
@ -136,146 +95,6 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $types
|
||||
* @param int $page
|
||||
* @param int $pageSize
|
||||
*
|
||||
* @return LengthAwarePaginator
|
||||
*/
|
||||
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
|
||||
{
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
|
||||
$query->where('transaction_journals.completed', 1);
|
||||
if (count($types) > 0) {
|
||||
$query->transactionTypes($types);
|
||||
}
|
||||
$count = $this->user->transactionJournals()->transactionTypes($types)->count();
|
||||
$set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
|
||||
$journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
|
||||
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of ALL journals, given a specific account and a date range.
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
|
||||
$query->where('transaction_journals.completed', 1);
|
||||
$query->before($end);
|
||||
$query->after($start);
|
||||
|
||||
if ($accounts->count() > 0) {
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
// join source and destination:
|
||||
$query->leftJoin(
|
||||
'transactions as source', function (JoinClause $join) {
|
||||
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
|
||||
}
|
||||
);
|
||||
$query->leftJoin(
|
||||
'transactions as destination', function (JoinClause $join) {
|
||||
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
|
||||
}
|
||||
);
|
||||
|
||||
$query->where(
|
||||
function (Builder $q) use ($ids) {
|
||||
$q->whereIn('destination.account_id', $ids);
|
||||
$q->orWhereIn('source.account_id', $ids);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$set = $query->get(TransactionJournal::queryFields());
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPiggyBankEvents(TransactionJournal $journal): Collection
|
||||
{
|
||||
/** @var Collection $set */
|
||||
$events = $journal->piggyBankEvents()->get();
|
||||
$events->each(
|
||||
function (PiggyBankEvent $event) {
|
||||
$event->piggyBank = $event->piggyBank()->withTrashed()->first();
|
||||
}
|
||||
);
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getTransactions(TransactionJournal $journal): Collection
|
||||
{
|
||||
$transactions = new Collection;
|
||||
$fields = ['transactions.id', 'transactions.created_at', 'transactions.updated_at', 'transactions.deleted_at', 'transactions.account_id',
|
||||
'transactions.transaction_journal_id', 'transactions.description', 'transactions.amount',
|
||||
DB::raw('SUM(transactions.amount) AS sum')];
|
||||
$groupBy = ['transactions.id', 'transactions.created_at', 'transactions.updated_at', 'transactions.deleted_at', 'transactions.account_id',
|
||||
'transactions.transaction_journal_id', 'transactions.description', 'transactions.amount'];
|
||||
switch ($journal->transactionType->type) {
|
||||
case TransactionType::DEPOSIT:
|
||||
/** @var Collection $transactions */
|
||||
$transactions = $journal->transactions()
|
||||
->groupBy('transactions.account_id')
|
||||
->where('amount', '<', 0)
|
||||
->groupBy($groupBy)
|
||||
->orderBy('amount', 'ASC')->get($fields);
|
||||
$final = $journal->transactions()
|
||||
->groupBy($groupBy)
|
||||
->where('amount', '>', 0)
|
||||
->orderBy('amount', 'ASC')->first($fields);
|
||||
$transactions->push($final);
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
|
||||
/** @var Collection $transactions */
|
||||
$transactions = $journal->transactions()
|
||||
->groupBy($groupBy)
|
||||
->orderBy('transactions.id')->get($fields);
|
||||
break;
|
||||
case TransactionType::WITHDRAWAL:
|
||||
|
||||
/** @var Collection $transactions */
|
||||
$transactions = $journal->transactions()
|
||||
->where('amount', '>', 0)
|
||||
->groupBy($groupBy)
|
||||
->orderBy('amount', 'ASC')->get($fields);
|
||||
$final = $journal->transactions()
|
||||
->where('amount', '<', 0)
|
||||
->groupBy($groupBy)
|
||||
->orderBy('amount', 'ASC')->first($fields);
|
||||
$transactions->push($final);
|
||||
break;
|
||||
}
|
||||
// foreach do balance thing
|
||||
$transactions->each(
|
||||
function (Transaction $t) {
|
||||
$t->before = $this->balanceBeforeTransaction($t);
|
||||
}
|
||||
);
|
||||
|
||||
return $transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
@ -286,56 +105,47 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
{
|
||||
// find transaction type.
|
||||
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
|
||||
|
||||
// store actual journal.
|
||||
$journal = new TransactionJournal(
|
||||
$journal = new TransactionJournal(
|
||||
[
|
||||
'user_id' => $data['user'],
|
||||
'transaction_type_id' => $transactionType->id,
|
||||
'transaction_currency_id' => $data['amount_currency_id_amount'],
|
||||
'transaction_currency_id' => $data['currency_id'],
|
||||
'description' => $data['description'],
|
||||
'completed' => 0,
|
||||
'date' => $data['date'],
|
||||
'interest_date' => $data['interest_date'],
|
||||
'book_date' => $data['book_date'],
|
||||
'process_date' => $data['process_date'],
|
||||
]
|
||||
);
|
||||
$journal->save();
|
||||
|
||||
// store or get category
|
||||
if (strlen($data['category']) > 0) {
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
|
||||
$journal->categories()->save($category);
|
||||
}
|
||||
// store stuff:
|
||||
$this->storeCategoryWithJournal($journal, $data['category']);
|
||||
$this->storeBudgetWithJournal($journal, $data['budget_id']);
|
||||
$accounts = $this->storeAccounts($transactionType, $data);
|
||||
|
||||
// store or get budget
|
||||
if (intval($data['budget_id']) > 0 && $transactionType->type !== TransactionType::TRANSFER) {
|
||||
/** @var \FireflyIII\Models\Budget $budget */
|
||||
$budget = Budget::find($data['budget_id']);
|
||||
$journal->budgets()->save($budget);
|
||||
}
|
||||
// store two transactions:
|
||||
$one = [
|
||||
'journal' => $journal,
|
||||
'account' => $accounts['source'],
|
||||
'amount' => bcmul(strval($data['amount']), '-1'),
|
||||
'description' => null,
|
||||
'category' => null,
|
||||
'budget' => null,
|
||||
'identifier' => 0,
|
||||
];
|
||||
$this->storeTransaction($one);
|
||||
|
||||
// store accounts (depends on type)
|
||||
list($sourceAccount, $destinationAccount) = $this->storeAccounts($transactionType, $data);
|
||||
$two = [
|
||||
'journal' => $journal,
|
||||
'account' => $accounts['destination'],
|
||||
'amount' => $data['amount'],
|
||||
'description' => null,
|
||||
'category' => null,
|
||||
'budget' => null,
|
||||
'identifier' => 0,
|
||||
];
|
||||
|
||||
$this->storeTransaction($two);
|
||||
|
||||
// store accompanying transactions.
|
||||
Transaction::create( // first transaction.
|
||||
[
|
||||
'account_id' => $sourceAccount->id,
|
||||
'transaction_journal_id' => $journal->id,
|
||||
'amount' => $data['amount'] * -1,
|
||||
]
|
||||
);
|
||||
Transaction::create( // second transaction.
|
||||
[
|
||||
'account_id' => $destinationAccount->id,
|
||||
'transaction_journal_id' => $journal->id,
|
||||
'amount' => $data['amount'],
|
||||
]
|
||||
);
|
||||
$journal->completed = 1;
|
||||
$journal->save();
|
||||
|
||||
// store tags
|
||||
if (isset($data['tags']) && is_array($data['tags'])) {
|
||||
@ -350,6 +160,9 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
|
||||
}
|
||||
|
||||
$journal->completed = 1;
|
||||
$journal->save();
|
||||
|
||||
return $journal;
|
||||
|
||||
}
|
||||
@ -396,45 +209,24 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
*/
|
||||
public function update(TransactionJournal $journal, array $data): TransactionJournal
|
||||
{
|
||||
// update actual journal.
|
||||
$journal->transaction_currency_id = $data['amount_currency_id_amount'];
|
||||
// update actual journal:
|
||||
$journal->transaction_currency_id = $data['currency_id'];
|
||||
$journal->description = $data['description'];
|
||||
$journal->date = $data['date'];
|
||||
|
||||
// unlink all categories, recreate them:
|
||||
$journal->categories()->detach();
|
||||
if (strlen($data['category']) > 0) {
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
|
||||
$journal->categories()->save($category);
|
||||
}
|
||||
|
||||
// unlink all budgets and recreate them:
|
||||
$journal->budgets()->detach();
|
||||
if (intval($data['budget_id']) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) {
|
||||
/** @var \FireflyIII\Models\Budget $budget */
|
||||
$budget = Budget::where('user_id', $this->user->id)->where('id', $data['budget_id'])->first();
|
||||
$journal->budgets()->save($budget);
|
||||
}
|
||||
|
||||
// store accounts (depends on type)
|
||||
list($fromAccount, $toAccount) = $this->storeAccounts($journal->transactionType, $data);
|
||||
$this->storeCategoryWithJournal($journal, $data['category']);
|
||||
$this->storeBudgetWithJournal($journal, $data['budget_id']);
|
||||
$accounts = $this->storeAccounts($journal->transactionType, $data);
|
||||
|
||||
// update the from and to transaction.
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions()->get() as $transaction) {
|
||||
if ($transaction->amount < 0) {
|
||||
// this is the from transaction, negative amount:
|
||||
$transaction->amount = $data['amount'] * -1;
|
||||
$transaction->account_id = $fromAccount->id;
|
||||
$transaction->save();
|
||||
}
|
||||
if ($transaction->amount > 0) {
|
||||
$transaction->amount = $data['amount'];
|
||||
$transaction->account_id = $toAccount->id;
|
||||
$transaction->save();
|
||||
}
|
||||
}
|
||||
$sourceAmount = bcmul(strval($data['amount']), '-1');
|
||||
$this->updateSourceTransaction($journal, $accounts['source'], $sourceAmount); // negative because source loses money.
|
||||
|
||||
$amount = strval($data['amount']);
|
||||
$this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money.
|
||||
|
||||
$journal->save();
|
||||
|
||||
@ -460,6 +252,96 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above but for transaction journal with multiple transactions.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal
|
||||
{
|
||||
// update actual journal:
|
||||
$journal->transaction_currency_id = $data['currency_id'];
|
||||
$journal->description = $data['journal_description'];
|
||||
$journal->date = $data['date'];
|
||||
$journal->save();
|
||||
|
||||
// unlink all categories:
|
||||
$journal->categories()->detach();
|
||||
$journal->budgets()->detach();
|
||||
|
||||
// update meta fields:
|
||||
$result = $journal->save();
|
||||
if ($result) {
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, $this->validMetaFields)) {
|
||||
$journal->setMeta($key, $value);
|
||||
continue;
|
||||
}
|
||||
Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
|
||||
}
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
|
||||
// update tags:
|
||||
if (isset($data['tags']) && is_array($data['tags'])) {
|
||||
$this->updateTags($journal, $data['tags']);
|
||||
}
|
||||
|
||||
// delete original transactions, and recreate them.
|
||||
$journal->transactions()->delete();
|
||||
|
||||
// store each transaction.
|
||||
$identifier = 0;
|
||||
foreach ($data['transactions'] as $transaction) {
|
||||
Log::debug(sprintf('Split journal update split transaction %d', $identifier));
|
||||
$transaction = $this->appendTransactionData($transaction, $data);
|
||||
$this->storeSplitTransaction($journal, $transaction, $identifier);
|
||||
$identifier++;
|
||||
}
|
||||
|
||||
$journal->save();
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user edits a split journal, each line is missing crucial data:
|
||||
*
|
||||
* - Withdrawal lines are missing the source account ID
|
||||
* - Deposit lines are missing the destination account ID
|
||||
* - Transfers are missing both.
|
||||
*
|
||||
* We need to append the array.
|
||||
*
|
||||
* @param array $transaction
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function appendTransactionData(array $transaction, array $data): array
|
||||
{
|
||||
switch ($data['what']) {
|
||||
case strtolower(TransactionType::TRANSFER):
|
||||
case strtolower(TransactionType::WITHDRAWAL):
|
||||
$transaction['source_account_id'] = intval($data['journal_source_account_id']);
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($data['what']) {
|
||||
case strtolower(TransactionType::TRANSFER):
|
||||
case strtolower(TransactionType::DEPOSIT):
|
||||
$transaction['destination_account_id'] = intval($data['journal_destination_account_id']);
|
||||
break;
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* * Remember: a balancingAct takes at most one expense and one transfer.
|
||||
@ -496,38 +378,92 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
*/
|
||||
private function storeAccounts(TransactionType $type, array $data): array
|
||||
{
|
||||
$sourceAccount = null;
|
||||
$destinationAccount = null;
|
||||
$accounts = [
|
||||
'source' => null,
|
||||
'destination' => null,
|
||||
];
|
||||
|
||||
Log::debug(sprintf('Going to store accounts for type %s', $type->type));
|
||||
switch ($type->type) {
|
||||
case TransactionType::WITHDRAWAL:
|
||||
list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data);
|
||||
$accounts = $this->storeWithdrawalAccounts($data);
|
||||
break;
|
||||
|
||||
case TransactionType::DEPOSIT:
|
||||
list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data);
|
||||
$accounts = $this->storeDepositAccounts($data);
|
||||
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
|
||||
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
|
||||
$accounts['source'] = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
|
||||
$accounts['destination'] = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('Did not recognise transaction type.');
|
||||
throw new FireflyException(sprintf('Did not recognise transaction type "%s".', $type->type));
|
||||
}
|
||||
|
||||
if (is_null($destinationAccount)) {
|
||||
Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]);
|
||||
throw new FireflyException('"destination"-account is null, so we cannot continue!');
|
||||
}
|
||||
|
||||
if (is_null($sourceAccount)) {
|
||||
if (is_null($accounts['source'])) {
|
||||
Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]);
|
||||
throw new FireflyException('"source"-account is null, so we cannot continue!');
|
||||
}
|
||||
|
||||
if (is_null($accounts['destination'])) {
|
||||
Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]);
|
||||
throw new FireflyException('"destination"-account is null, so we cannot continue!');
|
||||
|
||||
}
|
||||
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
return $accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param int $budgetId
|
||||
*/
|
||||
private function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId)
|
||||
{
|
||||
if (intval($budgetId) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) {
|
||||
/** @var \FireflyIII\Models\Budget $budget */
|
||||
$budget = Budget::find($budgetId);
|
||||
$journal->budgets()->save($budget);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Transaction $transaction
|
||||
* @param int $budgetId
|
||||
*/
|
||||
private function storeBudgetWithTransaction(Transaction $transaction, int $budgetId)
|
||||
{
|
||||
if (intval($budgetId) > 0 && $transaction->transactionJournal->transactionType->type !== TransactionType::TRANSFER) {
|
||||
/** @var \FireflyIII\Models\Budget $budget */
|
||||
$budget = Budget::find($budgetId);
|
||||
$transaction->budgets()->save($budget);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param string $category
|
||||
*/
|
||||
private function storeCategoryWithJournal(TransactionJournal $journal, string $category)
|
||||
{
|
||||
if (strlen($category) > 0) {
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]);
|
||||
$journal->categories()->save($category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Transaction $transaction
|
||||
* @param string $category
|
||||
*/
|
||||
private function storeCategoryWithTransaction(Transaction $transaction, string $category)
|
||||
{
|
||||
if (strlen($category) > 0) {
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $transaction->transactionJournal->user_id]);
|
||||
$transaction->categories()->save($category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -540,19 +476,102 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
|
||||
|
||||
if (strlen($data['source_account_name']) > 0) {
|
||||
$fromType = AccountType::where('type', 'Revenue account')->first();
|
||||
$fromAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
|
||||
$sourceType = AccountType::where('type', 'Revenue account')->first();
|
||||
$sourceAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
|
||||
);
|
||||
|
||||
return [$fromAccount, $destinationAccount];
|
||||
return [
|
||||
'source' => $sourceAccount,
|
||||
'destination' => $destinationAccount,
|
||||
];
|
||||
}
|
||||
$fromType = AccountType::where('type', 'Cash account')->first();
|
||||
$fromAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
$sourceType = AccountType::where('type', 'Cash account')->first();
|
||||
$sourceAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
);
|
||||
|
||||
return [$fromAccount, $destinationAccount];
|
||||
return [
|
||||
'source' => $sourceAccount,
|
||||
'destination' => $destinationAccount,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $transaction
|
||||
* @param int $identifier
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection
|
||||
{
|
||||
// store source and destination accounts (depends on type)
|
||||
$accounts = $this->storeAccounts($journal->transactionType, $transaction);
|
||||
|
||||
// store transaction one way:
|
||||
$one = $this->storeTransaction(
|
||||
[
|
||||
'journal' => $journal,
|
||||
'account' => $accounts['source'],
|
||||
'amount' => bcmul(strval($transaction['amount']), '-1'),
|
||||
'description' => $transaction['description'],
|
||||
'category' => null,
|
||||
'budget' => null,
|
||||
'identifier' => $identifier,
|
||||
]
|
||||
);
|
||||
$this->storeCategoryWithTransaction($one, $transaction['category']);
|
||||
$this->storeBudgetWithTransaction($one, $transaction['budget_id']);
|
||||
|
||||
// and the other way:
|
||||
$two = $this->storeTransaction(
|
||||
[
|
||||
'journal' => $journal,
|
||||
'account' => $accounts['destination'],
|
||||
'amount' => strval($transaction['amount']),
|
||||
'description' => $transaction['description'],
|
||||
'category' => null,
|
||||
'budget' => null,
|
||||
'identifier' => $identifier,
|
||||
]
|
||||
);
|
||||
$this->storeCategoryWithTransaction($two, $transaction['category']);
|
||||
$this->storeBudgetWithTransaction($two, $transaction['budget_id']);
|
||||
|
||||
return new Collection([$one, $two]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return Transaction
|
||||
*/
|
||||
private function storeTransaction(array $data): Transaction
|
||||
{
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = Transaction::create(
|
||||
[
|
||||
'transaction_journal_id' => $data['journal']->id,
|
||||
'account_id' => $data['account']->id,
|
||||
'amount' => $data['amount'],
|
||||
'description' => $data['description'],
|
||||
'identifier' => $data['identifier'],
|
||||
]
|
||||
);
|
||||
|
||||
Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id));
|
||||
|
||||
if (!is_null($data['category'])) {
|
||||
$transaction->categories()->save($data['category']);
|
||||
}
|
||||
|
||||
if (!is_null($data['budget'])) {
|
||||
$transaction->categories()->save($data['budget']);
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -565,7 +584,7 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
|
||||
|
||||
if (strlen($data['destination_account_name']) > 0) {
|
||||
$destinationType = AccountType::where('type', 'Expense account')->first();
|
||||
$destinationType = AccountType::where('type', AccountType::EXPENSE)->first();
|
||||
$destinationAccount = Account::firstOrCreateEncrypted(
|
||||
[
|
||||
'user_id' => $data['user'],
|
||||
@ -575,14 +594,69 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
return [
|
||||
'source' => $sourceAccount,
|
||||
'destination' => $destinationAccount,
|
||||
];
|
||||
}
|
||||
$destinationType = AccountType::where('type', 'Cash account')->first();
|
||||
$destinationAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $data['user'], 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
return [
|
||||
'source' => $sourceAccount,
|
||||
'destination' => $destinationAccount,
|
||||
];
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Account $account
|
||||
* @param string $amount
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function updateDestinationTransaction(TransactionJournal $journal, Account $account, string $amount)
|
||||
{
|
||||
// should be one:
|
||||
$set = $journal->transactions()->where('amount', '>', 0)->get();
|
||||
if ($set->count() != 1) {
|
||||
throw new FireflyException(
|
||||
sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount more than zero.', $journal->id, $set->count())
|
||||
);
|
||||
}
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $set->first();
|
||||
$transaction->amount = $amount;
|
||||
$transaction->account_id = $account->id;
|
||||
$transaction->save();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param Account $account
|
||||
* @param string $amount
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function updateSourceTransaction(TransactionJournal $journal, Account $account, string $amount)
|
||||
{
|
||||
// should be one:
|
||||
$set = $journal->transactions()->where('amount', '<', 0)->get();
|
||||
if ($set->count() != 1) {
|
||||
throw new FireflyException(
|
||||
sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount less than zero.', $journal->id, $set->count())
|
||||
);
|
||||
}
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $set->first();
|
||||
$transaction->amount = $amount;
|
||||
$transaction->account_id = $account->id;
|
||||
$transaction->save();
|
||||
|
||||
|
||||
}
|
||||
|
@ -13,11 +13,7 @@ declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Repositories\Journal;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface JournalRepositoryInterface
|
||||
@ -26,14 +22,6 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
interface JournalRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Returns the amount in the account before the specified transaction took place.
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function balanceBeforeTransaction(Transaction $transaction): string;
|
||||
|
||||
/**
|
||||
* Deletes a journal.
|
||||
@ -60,41 +48,6 @@ interface JournalRepositoryInterface
|
||||
*/
|
||||
public function first(): TransactionJournal;
|
||||
|
||||
/**
|
||||
* Returns a page of a specific type(s) of journal.
|
||||
*
|
||||
* @param array $types
|
||||
* @param int $page
|
||||
* @param int $pageSize
|
||||
*
|
||||
* @return LengthAwarePaginator
|
||||
*/
|
||||
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* Returns a collection of ALL journals, given a specific account and a date range.
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPiggyBankEvents(TransactionJournal $journal): Collection;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getTransactions(TransactionJournal $journal): Collection;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
@ -120,4 +73,12 @@ interface JournalRepositoryInterface
|
||||
*/
|
||||
public function update(TransactionJournal $journal, array $data): TransactionJournal;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal;
|
||||
|
||||
}
|
||||
|
293
app/Repositories/Journal/JournalTasker.php
Normal file
293
app/Repositories/Journal/JournalTasker.php
Normal file
@ -0,0 +1,293 @@
|
||||
<?php
|
||||
/**
|
||||
* JournalTasker.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Repositories\Journal;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crypt;
|
||||
use DB;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class JournalTasker
|
||||
*
|
||||
* @package FireflyIII\Repositories\Journal
|
||||
*/
|
||||
class JournalTasker implements JournalTaskerInterface
|
||||
{
|
||||
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* JournalRepository constructor.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a page of a specific type(s) of journal.
|
||||
*
|
||||
* @param array $types
|
||||
* @param int $page
|
||||
* @param int $pageSize
|
||||
*
|
||||
* @return LengthAwarePaginator
|
||||
*/
|
||||
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
|
||||
{
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
|
||||
$query->where('transaction_journals.completed', 1);
|
||||
if (count($types) > 0) {
|
||||
$query->transactionTypes($types);
|
||||
}
|
||||
$count = $this->user->transactionJournals()->transactionTypes($types)->count();
|
||||
$set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
|
||||
$journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
|
||||
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of ALL journals, given a specific account and a date range.
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
|
||||
$query->where('transaction_journals.completed', 1);
|
||||
$query->before($end);
|
||||
$query->after($start);
|
||||
|
||||
if ($accounts->count() > 0) {
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
// join source and destination:
|
||||
$query->leftJoin(
|
||||
'transactions as source', function (JoinClause $join) {
|
||||
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
|
||||
}
|
||||
);
|
||||
$query->leftJoin(
|
||||
'transactions as destination', function (JoinClause $join) {
|
||||
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
|
||||
}
|
||||
);
|
||||
|
||||
$query->where(
|
||||
function (Builder $q) use ($ids) {
|
||||
$q->whereIn('destination.account_id', $ids);
|
||||
$q->orWhereIn('source.account_id', $ids);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$set = $query->get(TransactionJournal::queryFields());
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPiggyBankEvents(TransactionJournal $journal): Collection
|
||||
{
|
||||
/** @var Collection $set */
|
||||
$events = $journal->piggyBankEvents()->get();
|
||||
$events->each(
|
||||
function (PiggyBankEvent $event) {
|
||||
$event->piggyBank = $event->piggyBank()->withTrashed()->first();
|
||||
}
|
||||
);
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an overview of the transactions of a journal, tailored to the view
|
||||
* that shows a transaction (transaction/show/xx).
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTransactionsOverview(TransactionJournal $journal): array
|
||||
{
|
||||
// get all transaction data + the opposite site in one list.
|
||||
/**
|
||||
* select
|
||||
*
|
||||
* source.id,
|
||||
* source.account_id,
|
||||
* source_accounts.name as account_name,
|
||||
* source_accounts.encrypted as account_encrypted,
|
||||
* source.amount,
|
||||
* source.description,
|
||||
*
|
||||
* destination.id as destination_id,
|
||||
* destination.account_id as destination_account_id,
|
||||
* destination_accounts.name as destination_account_name,
|
||||
* destination_accounts.encrypted as destination_account_encrypted
|
||||
*
|
||||
*
|
||||
* from transactions as source
|
||||
*
|
||||
* left join transactions as destination ON source.transaction_journal_id =
|
||||
* destination.transaction_journal_id AND source.amount = destination.amount * -1 AND source.identifier = destination.identifier
|
||||
* -- left join source account name:
|
||||
* left join accounts as source_accounts ON source.account_id = source_accounts.id
|
||||
* left join accounts as destination_accounts ON destination.account_id = destination_accounts.id
|
||||
*
|
||||
* where source.transaction_journal_id = 6600
|
||||
* and source.amount < 0
|
||||
* and source.deleted_at is null
|
||||
*/
|
||||
$set = $journal
|
||||
->transactions()// "source"
|
||||
->leftJoin(
|
||||
'transactions as destination', function (JoinClause $join) {
|
||||
$join
|
||||
->on('transactions.transaction_journal_id', '=', 'destination.transaction_journal_id')
|
||||
->where('transactions.amount', '=', DB::raw('destination.amount * -1'))
|
||||
->where('transactions.identifier', '=', DB::raw('destination.identifier'))
|
||||
->whereNull('destination.deleted_at');
|
||||
}
|
||||
)
|
||||
->with(['budgets', 'categories'])
|
||||
->leftJoin('accounts as source_accounts', 'transactions.account_id', '=', 'source_accounts.id')
|
||||
->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id')
|
||||
->where('transactions.amount', '<', 0)
|
||||
->whereNull('transactions.deleted_at')
|
||||
->get(
|
||||
[
|
||||
'transactions.id',
|
||||
'transactions.account_id',
|
||||
'source_accounts.name as account_name',
|
||||
'source_accounts.encrypted as account_encrypted',
|
||||
'transactions.amount',
|
||||
'transactions.description',
|
||||
'destination.id as destination_id',
|
||||
'destination.account_id as destination_account_id',
|
||||
'destination_accounts.name as destination_account_name',
|
||||
'destination_accounts.encrypted as destination_account_encrypted',
|
||||
]
|
||||
);
|
||||
|
||||
$transactions = [];
|
||||
|
||||
/** @var Transaction $entry */
|
||||
foreach ($set as $entry) {
|
||||
$sourceBalance = $this->getBalance($entry->id);
|
||||
$destinationBalance = $this->getBalance($entry->destination_id);
|
||||
$budget = $entry->budgets->first();
|
||||
$category = $entry->categories->first();
|
||||
$transaction = [
|
||||
'source_id' => $entry->id,
|
||||
'source_amount' => $entry->amount,
|
||||
|
||||
'description' => $entry->description,
|
||||
'source_account_id' => $entry->account_id,
|
||||
'source_account_name' => intval($entry->account_encrypted) === 1 ? Crypt::decrypt($entry->account_name) : $entry->account_name,
|
||||
'source_account_before' => $sourceBalance,
|
||||
'source_account_after' => bcadd($sourceBalance, $entry->amount),
|
||||
'destination_id' => $entry->destination_id,
|
||||
'destination_amount' => bcmul($entry->amount, '-1'),
|
||||
'destination_account_id' => $entry->destination_account_id,
|
||||
'destination_account_name' =>
|
||||
intval($entry->destination_account_encrypted) === 1 ? Crypt::decrypt($entry->destination_account_name) : $entry->destination_account_name,
|
||||
'destination_account_before' => $destinationBalance,
|
||||
'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')),
|
||||
'budget_id' => is_null($budget) ? 0 : $budget->id,
|
||||
'category' => is_null($category) ? '' : $category->name,
|
||||
];
|
||||
|
||||
|
||||
$transactions[] = $transaction;
|
||||
}
|
||||
|
||||
return $transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the balance of an account before the given transaction has hit. This is tricky, because
|
||||
* the balance does not depend on the transaction itself but the journal it's part of. And of course
|
||||
* the order of transactions within the journal. So the query is pretty complex:
|
||||
*
|
||||
* @param int $transactionId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getBalance(int $transactionId): string
|
||||
{
|
||||
// find the transaction first:
|
||||
$transaction = Transaction::find($transactionId);
|
||||
$date = $transaction->transactionJournal->date->format('Y-m-d');
|
||||
$order = intval($transaction->transactionJournal->order);
|
||||
$journalId = intval($transaction->transaction_journal_id);
|
||||
$identifier = intval($transaction->identifier);
|
||||
|
||||
// go!
|
||||
$sum
|
||||
= Transaction
|
||||
::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->where('account_id', $transaction->account_id)
|
||||
->whereNull('transactions.deleted_at')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->where('transactions.id', '!=', $transactionId)
|
||||
->where(
|
||||
function (Builder $q1) use ($date, $order, $journalId, $identifier) {
|
||||
$q1->where('transaction_journals.date', '<', $date); // date
|
||||
$q1->orWhere(
|
||||
function (Builder $q2) use ($date, $order) { // function 1
|
||||
$q2->where('transaction_journals.date', $date);
|
||||
$q2->where('transaction_journals.order', '>', $order);
|
||||
}
|
||||
);
|
||||
$q1->orWhere(
|
||||
function (Builder $q3) use ($date, $order, $journalId) { // function 2
|
||||
$q3->where('transaction_journals.date', $date);
|
||||
$q3->where('transaction_journals.order', $order);
|
||||
$q3->where('transaction_journals.id', '<', $journalId);
|
||||
}
|
||||
);
|
||||
$q1->orWhere(
|
||||
function (Builder $q4) use ($date, $order, $journalId, $identifier) { // function 3
|
||||
$q4->where('transaction_journals.date', $date);
|
||||
$q4->where('transaction_journals.order', $order);
|
||||
$q4->where('transaction_journals.id', $journalId);
|
||||
$q4->where('transactions.identifier', '>', $identifier);
|
||||
}
|
||||
);
|
||||
}
|
||||
)->sum('transactions.amount');
|
||||
|
||||
return strval($sum);
|
||||
}
|
||||
}
|
67
app/Repositories/Journal/JournalTaskerInterface.php
Normal file
67
app/Repositories/Journal/JournalTaskerInterface.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* JournalTaskerInterface.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace FireflyIII\Repositories\Journal;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface JournalTaskerInterface
|
||||
*
|
||||
* @package FireflyIII\Repositories\Journal
|
||||
*/
|
||||
interface JournalTaskerInterface
|
||||
{
|
||||
/**
|
||||
* Returns a page of a specific type(s) of journal.
|
||||
*
|
||||
* @param array $types
|
||||
* @param int $page
|
||||
* @param int $pageSize
|
||||
*
|
||||
* @return LengthAwarePaginator
|
||||
*/
|
||||
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* Returns a collection of ALL journals, given a specific account and a date range.
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPiggyBankEvents(TransactionJournal $journal): Collection;
|
||||
|
||||
/**
|
||||
* Get an overview of the transactions of a journal, tailored to the view
|
||||
* that shows a transaction (transaction/show/xx).
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTransactionsOverview(TransactionJournal $journal): array;
|
||||
}
|
@ -15,6 +15,7 @@ namespace FireflyIII\Repositories\PiggyBank;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\User;
|
||||
@ -176,6 +177,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
|
||||
{
|
||||
$piggyBank = PiggyBank::create($data);
|
||||
|
||||
$this->updateNote($piggyBank, $data['note']);
|
||||
|
||||
return $piggyBank;
|
||||
}
|
||||
|
||||
@ -196,6 +199,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
|
||||
|
||||
$piggyBank->save();
|
||||
|
||||
$this->updateNote($piggyBank, $data['note']);
|
||||
|
||||
// if the piggy bank is now smaller than the current relevant rep,
|
||||
// remove money from the rep.
|
||||
$repetition = $piggyBank->currentRelevantRep();
|
||||
@ -210,4 +215,31 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
|
||||
|
||||
return $piggyBank;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PiggyBank $piggyBank
|
||||
* @param string $note
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function updateNote(PiggyBank $piggyBank, string $note): bool
|
||||
{
|
||||
if (strlen($note) === 0) {
|
||||
$dbNote = $piggyBank->notes()->first();
|
||||
if (!is_null($dbNote)) {
|
||||
$dbNote->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
$dbNote= $piggyBank->notes()->first();
|
||||
if (is_null($dbNote)) {
|
||||
$dbNote= new Note();
|
||||
$dbNote->noteable()->associate($piggyBank);
|
||||
}
|
||||
$dbNote->text = trim($note);
|
||||
$dbNote->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,11 @@ declare(strict_types = 1);
|
||||
namespace FireflyIII\Repositories\User;
|
||||
|
||||
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Role;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
* Class UserRepository
|
||||
@ -56,4 +58,74 @@ class UserRepository implements UserRepositoryInterface
|
||||
{
|
||||
return $this->all()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function find(int $userId): User
|
||||
{
|
||||
$user = User::find($userId);
|
||||
if (!is_null($user)) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
return new User;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return basic user information.
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUserData(User $user): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// two factor:
|
||||
$is2faEnabled = Preferences::getForUser($user, 'twoFactorAuthEnabled', false)->data;
|
||||
$has2faSecret = !is_null(Preferences::getForUser($user, 'twoFactorAuthSecret'));
|
||||
$return['has_2fa'] = false;
|
||||
if ($is2faEnabled && $has2faSecret) {
|
||||
$return['has_2fa'] = true;
|
||||
}
|
||||
|
||||
// is user activated?
|
||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
||||
$isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data;
|
||||
$return['is_activated'] = true;
|
||||
if ($isConfirmed === false && $confirmAccount === true) {
|
||||
$return['is_activated'] = false;
|
||||
}
|
||||
|
||||
$return['is_admin'] = $user->hasRole('owner');
|
||||
$return['blocked'] = intval($user->blocked) === 1;
|
||||
$return['blocked_code'] = $user->blocked_code;
|
||||
$return['accounts'] = $user->accounts()->count();
|
||||
$return['journals'] = $user->transactionJournals()->count();
|
||||
$return['transactions'] = $user->transactions()->count();
|
||||
$return['attachments'] = $user->attachments()->count();
|
||||
$return['attachments_size'] = $user->attachments()->sum('size');
|
||||
$return['bills'] = $user->bills()->count();
|
||||
$return['categories'] = $user->categories()->count();
|
||||
$return['budgets'] = $user->budgets()->count();
|
||||
$return['budgets_with_limits'] = BudgetLimit
|
||||
::distinct()
|
||||
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
|
||||
->where('amount', '>', 0)
|
||||
->whereNull('budgets.deleted_at')
|
||||
->where('budgets.user_id', $user->id)->get(['budget_limits.budget_id'])->count();
|
||||
$return['export_jobs'] = $user->exportJobs()->count();
|
||||
$return['export_jobs_success'] = $user->exportJobs()->where('status', 'export_downloaded')->count();
|
||||
$return['import_jobs'] = $user->exportJobs()->count();
|
||||
$return['import_jobs_success'] = $user->exportJobs()->where('status', 'import_complete')->count();
|
||||
$return['rule_groups'] = $user->ruleGroups()->count();
|
||||
$return['rules'] = $user->rules()->count();
|
||||
$return['tags'] = $user->tags()->count();
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@ -47,4 +47,20 @@ interface UserRepositoryInterface
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int;
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function find(int $userId): User;
|
||||
|
||||
/**
|
||||
* Return basic user information.
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUserData(User $user): array;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace FireflyIII\Rules;
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
@ -31,8 +31,8 @@ class TransactionMatcher
|
||||
private $limit = 10;
|
||||
/** @var int Maximum number of transaction to search in (for performance reasons) * */
|
||||
private $range = 200;
|
||||
/** @var JournalRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var JournalTaskerInterface */
|
||||
private $tasker;
|
||||
/** @var array */
|
||||
private $transactionTypes = [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
|
||||
/** @var array List of triggers to match */
|
||||
@ -41,11 +41,11 @@ class TransactionMatcher
|
||||
/**
|
||||
* TransactionMatcher constructor. Typehint the repository.
|
||||
*
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param JournalTaskerInterface $tasker
|
||||
*/
|
||||
public function __construct(JournalRepositoryInterface $repository)
|
||||
public function __construct(JournalTaskerInterface $tasker)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->tasker = $tasker;
|
||||
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ class TransactionMatcher
|
||||
// - the maximum number of transactions to search in have been searched
|
||||
do {
|
||||
// Fetch a batch of transactions from the database
|
||||
$paginator = $this->repository->getJournals($this->transactionTypes, $page, $pagesize);
|
||||
$paginator = $this->tasker->getJournals($this->transactionTypes, $page, $pagesize);
|
||||
$set = $paginator->getCollection();
|
||||
|
||||
|
||||
|
@ -427,6 +427,7 @@ class ExpandedForm
|
||||
*/
|
||||
protected function expandOptionArray(string $name, $label, array $options): array
|
||||
{
|
||||
$name = str_replace('[]', '', $name);
|
||||
$options['class'] = 'form-control';
|
||||
$options['id'] = 'ffInput_' . $name;
|
||||
$options['autocomplete'] = 'off';
|
||||
@ -494,6 +495,7 @@ class ExpandedForm
|
||||
if (isset($options['label'])) {
|
||||
return $options['label'];
|
||||
}
|
||||
$name = str_replace('[]', '', $name);
|
||||
|
||||
return strval(trans('form.' . $name));
|
||||
|
||||
|
@ -16,7 +16,6 @@ namespace FireflyIII\Support\Twig;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Route;
|
||||
use Twig_Extension;
|
||||
|
@ -29,6 +29,20 @@ use Twig_SimpleFunction;
|
||||
*/
|
||||
class Transaction extends Twig_Extension
|
||||
{
|
||||
/**
|
||||
* @return Twig_SimpleFunction
|
||||
*/
|
||||
public function formatAmountPlainWithCode(): Twig_SimpleFunction
|
||||
{
|
||||
return new Twig_SimpleFunction(
|
||||
'formatAmountPlainWithCode', function (string $amount, string $code): string {
|
||||
|
||||
return Amount::formatWithCode($code, $amount, false);
|
||||
|
||||
}, ['is_safe' => ['html']]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Twig_SimpleFunction
|
||||
*/
|
||||
@ -62,11 +76,14 @@ class Transaction extends Twig_Extension
|
||||
{
|
||||
$functions = [
|
||||
$this->formatAmountWithCode(),
|
||||
$this->formatAmountPlainWithCode(),
|
||||
$this->transactionSourceAccount(),
|
||||
$this->transactionDestinationAccount(),
|
||||
$this->optionalJournalAmount(),
|
||||
$this->transactionBudgets(),
|
||||
$this->transactionIdBudgets(),
|
||||
$this->transactionCategories(),
|
||||
$this->transactionIdCategories(),
|
||||
$this->splitJournalIndicator(),
|
||||
];
|
||||
|
||||
@ -144,22 +161,7 @@ class Transaction extends Twig_Extension
|
||||
{
|
||||
return new Twig_SimpleFunction(
|
||||
'transactionBudgets', function (TransactionModel $transaction): string {
|
||||
// see if the transaction has a budget:
|
||||
$budgets = $transaction->budgets()->get();
|
||||
if ($budgets->count() === 0) {
|
||||
$budgets = $transaction->transactionJournal()->first()->budgets()->get();
|
||||
}
|
||||
if ($budgets->count() > 0) {
|
||||
$str = [];
|
||||
foreach ($budgets as $budget) {
|
||||
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$budget->id]), $budget->name, $budget->name);
|
||||
}
|
||||
|
||||
return join(', ', $str);
|
||||
}
|
||||
|
||||
|
||||
return '';
|
||||
return $this->getTransactionBudgets($transaction);
|
||||
}, ['is_safe' => ['html']]
|
||||
);
|
||||
}
|
||||
@ -171,21 +173,7 @@ class Transaction extends Twig_Extension
|
||||
{
|
||||
return new Twig_SimpleFunction(
|
||||
'transactionCategories', function (TransactionModel $transaction): string {
|
||||
// see if the transaction has a category:
|
||||
$categories = $transaction->categories()->get();
|
||||
if ($categories->count() === 0) {
|
||||
$categories = $transaction->transactionJournal()->first()->categories()->get();
|
||||
}
|
||||
if ($categories->count() > 0) {
|
||||
$str = [];
|
||||
foreach ($categories as $category) {
|
||||
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$category->id]), $category->name, $category->name);
|
||||
}
|
||||
|
||||
return join(', ', $str);
|
||||
}
|
||||
|
||||
return '';
|
||||
return $this->getTransactionCategories($transaction);
|
||||
}, ['is_safe' => ['html']]
|
||||
);
|
||||
}
|
||||
@ -228,6 +216,34 @@ class Transaction extends Twig_Extension
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Twig_SimpleFunction
|
||||
*/
|
||||
public function transactionIdBudgets(): Twig_SimpleFunction
|
||||
{
|
||||
return new Twig_SimpleFunction(
|
||||
'transactionIdBudgets', function (int $transactionId): string {
|
||||
$transaction = TransactionModel::find($transactionId);
|
||||
|
||||
return $this->getTransactionBudgets($transaction);
|
||||
}, ['is_safe' => ['html']]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Twig_SimpleFunction
|
||||
*/
|
||||
public function transactionIdCategories(): Twig_SimpleFunction
|
||||
{
|
||||
return new Twig_SimpleFunction(
|
||||
'transactionIdCategories', function (int $transactionId): string {
|
||||
$transaction = TransactionModel::find($transactionId);
|
||||
|
||||
return $this->getTransactionCategories($transaction);
|
||||
}, ['is_safe' => ['html']]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Twig_SimpleFunction
|
||||
*/
|
||||
@ -298,4 +314,53 @@ class Transaction extends Twig_Extension
|
||||
}, ['is_safe' => ['html']]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionModel $transaction
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getTransactionBudgets(TransactionModel $transaction): string
|
||||
{
|
||||
// see if the transaction has a budget:
|
||||
$budgets = $transaction->budgets()->get();
|
||||
if ($budgets->count() === 0) {
|
||||
$budgets = $transaction->transactionJournal()->first()->budgets()->get();
|
||||
}
|
||||
if ($budgets->count() > 0) {
|
||||
$str = [];
|
||||
foreach ($budgets as $budget) {
|
||||
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$budget->id]), $budget->name, $budget->name);
|
||||
}
|
||||
|
||||
return join(', ', $str);
|
||||
}
|
||||
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionModel $transaction
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getTransactionCategories(TransactionModel $transaction): string
|
||||
{
|
||||
// see if the transaction has a category:
|
||||
$categories = $transaction->categories()->get();
|
||||
if ($categories->count() === 0) {
|
||||
$categories = $transaction->transactionJournal()->first()->categories()->get();
|
||||
}
|
||||
if ($categories->count() > 0) {
|
||||
$str = [];
|
||||
foreach ($categories as $category) {
|
||||
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$category->id]), $category->name, $category->name);
|
||||
}
|
||||
|
||||
return join(', ', $str);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
@ -124,6 +124,28 @@ class FireflyValidator extends Validator
|
||||
return (intval($checksum) === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $attribute
|
||||
* @param $value
|
||||
* @param $parameters
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateMustExist($attribute, $value, $parameters): bool
|
||||
{
|
||||
$field = $parameters[1] ?? 'id';
|
||||
|
||||
if (intval($value) === 0) {
|
||||
return true;
|
||||
}
|
||||
$count = DB::table($parameters[0])->where($field, $value)->count();
|
||||
if ($count === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $attribute
|
||||
*
|
||||
|
@ -28,7 +28,7 @@
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"ext-intl": "*",
|
||||
"laravel/framework": "5.3.*",
|
||||
"laravel/framework": "5.3.18",
|
||||
"davejamesmiller/laravel-breadcrumbs": "^3.0",
|
||||
"watson/validating": "^3.0",
|
||||
"doctrine/dbal": "^2.5",
|
||||
@ -76,6 +76,7 @@
|
||||
"post-update-cmd": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
|
||||
"php artisan firefly:upgrade-instructions",
|
||||
"php artisan firefly:upgrade-database",
|
||||
"php artisan firefly:verify",
|
||||
"php artisan optimize"
|
||||
]
|
||||
|
233
composer.lock
generated
233
composer.lock
generated
@ -4,8 +4,8 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "ce5008a06e815bab7e4027161939b57e",
|
||||
"content-hash": "8a35f78c306f3ddd1080d7c1b687f77d",
|
||||
"hash": "cc3d23620e727ee1f4741b2e83f8685f",
|
||||
"content-hash": "473d3c681e5c41989e9dced651a939df",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@ -1024,16 +1024,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v5.3.10",
|
||||
"version": "v5.3.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "6febb0ee61999cde3bc7b2963c8903032bb22691"
|
||||
"reference": "9bee167d173857c25966c19afdaa66f127ca6784"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/6febb0ee61999cde3bc7b2963c8903032bb22691",
|
||||
"reference": "6febb0ee61999cde3bc7b2963c8903032bb22691",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/9bee167d173857c25966c19afdaa66f127ca6784",
|
||||
"reference": "9bee167d173857c25966c19afdaa66f127ca6784",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1148,7 +1148,7 @@
|
||||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2016-09-20 13:46:16"
|
||||
"time": "2016-10-08 01:51:20"
|
||||
},
|
||||
{
|
||||
"name": "laravelcollective/html",
|
||||
@ -1332,20 +1332,20 @@
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "1.0.27",
|
||||
"version": "1.0.32",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9"
|
||||
"reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/50e2045ed70a7e75a5e30bc3662904f3b67af8a9",
|
||||
"reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1b5c4a0031697f46e779a9d1b309c2e1b24daeab",
|
||||
"reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
"php": ">=5.5.9"
|
||||
},
|
||||
"conflict": {
|
||||
"league/flysystem-sftp": "<1.0.6"
|
||||
@ -1411,7 +1411,7 @@
|
||||
"sftp",
|
||||
"storage"
|
||||
],
|
||||
"time": "2016-08-10 08:55:11"
|
||||
"time": "2016-10-19 20:38:46"
|
||||
},
|
||||
{
|
||||
"name": "maximebf/debugbar",
|
||||
@ -1696,16 +1696,16 @@
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.2",
|
||||
"version": "v2.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "088c04e2f261c33bed6ca5245491cfca69195ccf"
|
||||
"reference": "c0125896dbb151380ab47e96c621741e79623beb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf",
|
||||
"reference": "088c04e2f261c33bed6ca5245491cfca69195ccf",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/c0125896dbb151380ab47e96c621741e79623beb",
|
||||
"reference": "c0125896dbb151380ab47e96c621741e79623beb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1740,7 +1740,7 @@
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2016-04-03 06:00:07"
|
||||
"time": "2016-10-17 15:23:22"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
@ -1805,16 +1805,16 @@
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "5277094ed527a1c4477177d102fe4c53551953e0"
|
||||
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0",
|
||||
"reference": "5277094ed527a1c4477177d102fe4c53551953e0",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
|
||||
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1848,7 +1848,7 @@
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"time": "2016-09-19 16:02:08"
|
||||
"time": "2016-10-10 12:19:37"
|
||||
},
|
||||
{
|
||||
"name": "psy/psysh",
|
||||
@ -1924,16 +1924,16 @@
|
||||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/uuid.git",
|
||||
"reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786"
|
||||
"reference": "a07797b986671b0dc823885a81d5e3516b931599"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/a6d15c8618ea3951fd54d34e326b68d3d0bc0786",
|
||||
"reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/a07797b986671b0dc823885a81d5e3516b931599",
|
||||
"reference": "a07797b986671b0dc823885a81d5e3516b931599",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2000,7 +2000,7 @@
|
||||
"identifier",
|
||||
"uuid"
|
||||
],
|
||||
"time": "2016-08-02 18:39:32"
|
||||
"time": "2016-10-02 15:51:17"
|
||||
},
|
||||
{
|
||||
"name": "rcrowe/twigbridge",
|
||||
@ -2068,23 +2068,23 @@
|
||||
},
|
||||
{
|
||||
"name": "rmccue/requests",
|
||||
"version": "v1.6.1",
|
||||
"version": "v1.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rmccue/Requests.git",
|
||||
"reference": "6aac485666c2955077d77b796bbdd25f0013a4ea"
|
||||
"reference": "87932f52ffad70504d93f04f15690cf16a089546"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rmccue/Requests/zipball/6aac485666c2955077d77b796bbdd25f0013a4ea",
|
||||
"reference": "6aac485666c2955077d77b796bbdd25f0013a4ea",
|
||||
"url": "https://api.github.com/repos/rmccue/Requests/zipball/87932f52ffad70504d93f04f15690cf16a089546",
|
||||
"reference": "87932f52ffad70504d93f04f15690cf16a089546",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"satooshi/php-coveralls": "dev-master"
|
||||
"requests/test-server": "dev-master"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -2113,7 +2113,7 @@
|
||||
"iri",
|
||||
"sockets"
|
||||
],
|
||||
"time": "2014-05-18 04:59:02"
|
||||
"time": "2016-10-13 00:11:37"
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
@ -2170,16 +2170,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/class-loader",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/class-loader.git",
|
||||
"reference": "2d0ba77c46ecc96a6641009a98f72632216811ba"
|
||||
"reference": "bcb072aba46ddf3b1a496438c63be6be647739aa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/class-loader/zipball/2d0ba77c46ecc96a6641009a98f72632216811ba",
|
||||
"reference": "2d0ba77c46ecc96a6641009a98f72632216811ba",
|
||||
"url": "https://api.github.com/repos/symfony/class-loader/zipball/bcb072aba46ddf3b1a496438c63be6be647739aa",
|
||||
"reference": "bcb072aba46ddf3b1a496438c63be6be647739aa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2222,24 +2222,25 @@
|
||||
],
|
||||
"description": "Symfony ClassLoader Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-23 13:39:15"
|
||||
"time": "2016-09-06 23:30:54"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563"
|
||||
"reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/8ea494c34f0f772c3954b5fbe00bffc5a435e563",
|
||||
"reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
|
||||
"reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"symfony/debug": "~2.8|~3.0",
|
||||
"symfony/polyfill-mbstring": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
@ -2282,20 +2283,20 @@
|
||||
],
|
||||
"description": "Symfony Console Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-19 06:48:39"
|
||||
"time": "2016-09-28 00:11:12"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11"
|
||||
"reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/34f6ac18c2974ca5fce68adf419ee7d15def6f11",
|
||||
"reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/e2b3f74a67fc928adc3c1b9027f73e1bc01190a8",
|
||||
"reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2339,11 +2340,11 @@
|
||||
],
|
||||
"description": "Symfony Debug Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-23 13:39:15"
|
||||
"time": "2016-09-06 11:02:40"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
@ -2403,16 +2404,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577"
|
||||
"reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/e568ef1784f447a0e54dcb6f6de30b9747b0f577",
|
||||
"reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/205b5ffbb518a98ba2ae60a52656c4a31ab00c6f",
|
||||
"reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2448,20 +2449,20 @@
|
||||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-26 12:04:02"
|
||||
"time": "2016-09-28 00:11:12"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4"
|
||||
"reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/63592e00fd90632b57ee50220a1ddb29b6bf3bb4",
|
||||
"reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/5114f1becca9f29e3af94374f1689c83c1aa3d97",
|
||||
"reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2501,20 +2502,20 @@
|
||||
],
|
||||
"description": "Symfony HttpFoundation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-22 12:11:19"
|
||||
"time": "2016-09-21 20:55:10"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3"
|
||||
"reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/aeda215d6b01f119508c090d2a09ebb5b0bc61f3",
|
||||
"reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/dc339d6eebadfa6e39c52868b4d4a715eff13c69",
|
||||
"reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2583,7 +2584,7 @@
|
||||
],
|
||||
"description": "Symfony HttpKernel Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-09-03 15:28:24"
|
||||
"time": "2016-10-03 19:01:06"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
@ -2754,16 +2755,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "e64e93041c80e77197ace5ab9385dedb5a143697"
|
||||
"reference": "66de154ae86b1a07001da9fbffd620206e4faf94"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/e64e93041c80e77197ace5ab9385dedb5a143697",
|
||||
"reference": "e64e93041c80e77197ace5ab9385dedb5a143697",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/66de154ae86b1a07001da9fbffd620206e4faf94",
|
||||
"reference": "66de154ae86b1a07001da9fbffd620206e4faf94",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2799,11 +2800,11 @@
|
||||
],
|
||||
"description": "Symfony Process Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-16 14:58:24"
|
||||
"time": "2016-09-29 14:13:09"
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
@ -2878,16 +2879,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "a35edc277513c9bc0f063ca174c36b346f974528"
|
||||
"reference": "93013a18d272e59dab8e67f583155b78c68947eb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/a35edc277513c9bc0f063ca174c36b346f974528",
|
||||
"reference": "a35edc277513c9bc0f063ca174c36b346f974528",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/93013a18d272e59dab8e67f583155b78c68947eb",
|
||||
"reference": "93013a18d272e59dab8e67f583155b78c68947eb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2938,20 +2939,20 @@
|
||||
],
|
||||
"description": "Symfony Translation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-05 08:37:39"
|
||||
"time": "2016-09-06 11:02:40"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "62ee73706c421654a4c840028954510277f7dfc8"
|
||||
"reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/62ee73706c421654a4c840028954510277f7dfc8",
|
||||
"reference": "62ee73706c421654a4c840028954510277f7dfc8",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/70bfe927b86ba9999aeebd829715b0bb2cd39a10",
|
||||
"reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3001,20 +3002,20 @@
|
||||
"debug",
|
||||
"dump"
|
||||
],
|
||||
"time": "2016-08-31 09:05:42"
|
||||
"time": "2016-09-29 14:13:09"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v1.25.0",
|
||||
"version": "v1.26.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "f16a634ab08d87e520da5671ec52153d627f10f6"
|
||||
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/f16a634ab08d87e520da5671ec52153d627f10f6",
|
||||
"reference": "f16a634ab08d87e520da5671ec52153d627f10f6",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
|
||||
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3027,7 +3028,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.25-dev"
|
||||
"dev-master": "1.26-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -3062,7 +3063,7 @@
|
||||
"keywords": [
|
||||
"templating"
|
||||
],
|
||||
"time": "2016-09-21 23:05:12"
|
||||
"time": "2016-10-05 18:57:41"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
@ -3481,16 +3482,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-docblock",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
||||
"reference": "9270140b940ff02e58ec577c237274e92cd40cdd"
|
||||
"reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd",
|
||||
"reference": "9270140b940ff02e58ec577c237274e92cd40cdd",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
|
||||
"reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3522,7 +3523,7 @@
|
||||
}
|
||||
],
|
||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
||||
"time": "2016-06-10 09:48:41"
|
||||
"time": "2016-09-30 07:12:33"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/type-resolver",
|
||||
@ -3879,16 +3880,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "5.5.5",
|
||||
"version": "5.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "a57126dc681b08289fef6ac96a48e30656f84350"
|
||||
"reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a57126dc681b08289fef6ac96a48e30656f84350",
|
||||
"reference": "a57126dc681b08289fef6ac96a48e30656f84350",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60c32c5b5e79c2248001efa2560f831da11cc2d7",
|
||||
"reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3922,7 +3923,6 @@
|
||||
"ext-pdo": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-tidy": "*",
|
||||
"ext-xdebug": "*",
|
||||
"phpunit/php-invoker": "~1.1"
|
||||
},
|
||||
@ -3932,7 +3932,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.5.x-dev"
|
||||
"dev-master": "5.6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -3958,20 +3958,20 @@
|
||||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2016-09-21 14:40:13"
|
||||
"time": "2016-10-07 13:03:26"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit-mock-objects",
|
||||
"version": "3.2.7",
|
||||
"version": "3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
|
||||
"reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a"
|
||||
"reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/546898a2c0c356ef2891b39dd7d07f5d82c8ed0a",
|
||||
"reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/238d7a2723bce689c79eeac9c7d5e1d623bb9dc2",
|
||||
"reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4017,7 +4017,7 @@
|
||||
"mock",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2016-09-06 16:07:45"
|
||||
"time": "2016-10-09 07:01:45"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/code-unit-reverse-lookup",
|
||||
@ -4534,16 +4534,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "2851e1932d77ce727776154d659b232d061e816a"
|
||||
"reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/2851e1932d77ce727776154d659b232d061e816a",
|
||||
"reference": "2851e1932d77ce727776154d659b232d061e816a",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/ca809c64072e0fe61c1c7fb3c76cdc32265042ac",
|
||||
"reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4583,11 +4583,11 @@
|
||||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-06-29 05:41:56"
|
||||
"time": "2016-09-06 11:02:40"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dom-crawler",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dom-crawler.git",
|
||||
@ -4643,16 +4643,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v3.1.4",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d"
|
||||
"reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/f291ed25eb1435bddbe8a96caaef16469c2a092d",
|
||||
"reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/368b9738d4033c8b93454cb0dbd45d305135a6d3",
|
||||
"reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4688,7 +4688,7 @@
|
||||
],
|
||||
"description": "Symfony Yaml Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-09-02 02:12:52"
|
||||
"time": "2016-09-25 08:27:07"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
@ -4747,7 +4747,8 @@
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.0.0"
|
||||
"php": ">=7.0.0",
|
||||
"ext-intl": "*"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
||||
|
@ -188,8 +188,8 @@ return [
|
||||
|
||||
|
||||
// own stuff:
|
||||
//Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
|
||||
//Barryvdh\Debugbar\ServiceProvider::class,
|
||||
// Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
|
||||
// Barryvdh\Debugbar\ServiceProvider::class,
|
||||
DaveJamesMiller\Breadcrumbs\ServiceProvider::class,
|
||||
TwigBridge\ServiceProvider::class,
|
||||
'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider',
|
||||
@ -226,50 +226,50 @@ return [
|
||||
|
||||
'aliases' => [
|
||||
|
||||
'App' => Illuminate\Support\Facades\App::class,
|
||||
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
||||
'Auth' => Illuminate\Support\Facades\Auth::class,
|
||||
'Blade' => Illuminate\Support\Facades\Blade::class,
|
||||
'Cache' => Illuminate\Support\Facades\Cache::class,
|
||||
'Config' => Illuminate\Support\Facades\Config::class,
|
||||
'Cookie' => Illuminate\Support\Facades\Cookie::class,
|
||||
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
||||
'DB' => Illuminate\Support\Facades\DB::class,
|
||||
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
||||
'Event' => Illuminate\Support\Facades\Event::class,
|
||||
'File' => Illuminate\Support\Facades\File::class,
|
||||
'Gate' => Illuminate\Support\Facades\Gate::class,
|
||||
'Hash' => Illuminate\Support\Facades\Hash::class,
|
||||
'Lang' => Illuminate\Support\Facades\Lang::class,
|
||||
'Log' => Illuminate\Support\Facades\Log::class,
|
||||
'Mail' => Illuminate\Support\Facades\Mail::class,
|
||||
'Notification' => Illuminate\Support\Facades\Notification::class,
|
||||
'Password' => Illuminate\Support\Facades\Password::class,
|
||||
'Queue' => Illuminate\Support\Facades\Queue::class,
|
||||
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
||||
'Redis' => Illuminate\Support\Facades\Redis::class,
|
||||
'Request' => Illuminate\Support\Facades\Request::class,
|
||||
'Response' => Illuminate\Support\Facades\Response::class,
|
||||
'Route' => Illuminate\Support\Facades\Route::class,
|
||||
'Schema' => Illuminate\Support\Facades\Schema::class,
|
||||
'Session' => Illuminate\Support\Facades\Session::class,
|
||||
'Storage' => Illuminate\Support\Facades\Storage::class,
|
||||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
'Twig' => 'TwigBridge\Facade\Twig',
|
||||
'Form' => Collective\Html\FormFacade::class,
|
||||
'Html' => Collective\Html\HtmlFacade::class,
|
||||
'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade',
|
||||
'Preferences' => 'FireflyIII\Support\Facades\Preferences',
|
||||
'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig',
|
||||
'Navigation' => 'FireflyIII\Support\Facades\Navigation',
|
||||
'Amount' => 'FireflyIII\Support\Facades\Amount',
|
||||
'Steam' => 'FireflyIII\Support\Facades\Steam',
|
||||
'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
|
||||
'Entrust' => 'Zizaco\Entrust\EntrustFacade',
|
||||
'Input' => 'Illuminate\Support\Facades\Input',
|
||||
'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade',
|
||||
'App' => Illuminate\Support\Facades\App::class,
|
||||
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
||||
'Auth' => Illuminate\Support\Facades\Auth::class,
|
||||
'Blade' => Illuminate\Support\Facades\Blade::class,
|
||||
'Cache' => Illuminate\Support\Facades\Cache::class,
|
||||
'Config' => Illuminate\Support\Facades\Config::class,
|
||||
'Cookie' => Illuminate\Support\Facades\Cookie::class,
|
||||
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
||||
'DB' => Illuminate\Support\Facades\DB::class,
|
||||
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
||||
'Event' => Illuminate\Support\Facades\Event::class,
|
||||
'File' => Illuminate\Support\Facades\File::class,
|
||||
'Gate' => Illuminate\Support\Facades\Gate::class,
|
||||
'Hash' => Illuminate\Support\Facades\Hash::class,
|
||||
'Lang' => Illuminate\Support\Facades\Lang::class,
|
||||
'Log' => Illuminate\Support\Facades\Log::class,
|
||||
'Mail' => Illuminate\Support\Facades\Mail::class,
|
||||
'Notification' => Illuminate\Support\Facades\Notification::class,
|
||||
'Password' => Illuminate\Support\Facades\Password::class,
|
||||
'Queue' => Illuminate\Support\Facades\Queue::class,
|
||||
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
||||
'Redis' => Illuminate\Support\Facades\Redis::class,
|
||||
'Request' => Illuminate\Support\Facades\Request::class,
|
||||
'Response' => Illuminate\Support\Facades\Response::class,
|
||||
'Route' => Illuminate\Support\Facades\Route::class,
|
||||
'Schema' => Illuminate\Support\Facades\Schema::class,
|
||||
'Session' => Illuminate\Support\Facades\Session::class,
|
||||
'Storage' => Illuminate\Support\Facades\Storage::class,
|
||||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
'Twig' => 'TwigBridge\Facade\Twig',
|
||||
'Form' => Collective\Html\FormFacade::class,
|
||||
'Html' => Collective\Html\HtmlFacade::class,
|
||||
'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade',
|
||||
'Preferences' => 'FireflyIII\Support\Facades\Preferences',
|
||||
'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig',
|
||||
'Navigation' => 'FireflyIII\Support\Facades\Navigation',
|
||||
'Amount' => 'FireflyIII\Support\Facades\Amount',
|
||||
'Steam' => 'FireflyIII\Support\Facades\Steam',
|
||||
'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
|
||||
'Entrust' => 'Zizaco\Entrust\EntrustFacade',
|
||||
'Input' => 'Illuminate\Support\Facades\Input',
|
||||
'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade',
|
||||
|
||||
|
||||
],
|
||||
|
@ -22,7 +22,7 @@ return [
|
||||
'single_user_mode' => true,
|
||||
],
|
||||
'chart' => 'chartjs',
|
||||
'version' => '4.0.2',
|
||||
'version' => '4.1',
|
||||
'csv_import_enabled' => true,
|
||||
'maxUploadSize' => 5242880,
|
||||
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
|
||||
|
@ -28,5 +28,6 @@ return [
|
||||
'3.8' => 'This version of Firefly III requires PHP 7.0.',
|
||||
'3.10' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-3.10',
|
||||
'4.0' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0',
|
||||
'4.1' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0',
|
||||
],
|
||||
];
|
||||
|
41
database/migrations/2016_10_22_075804_changes_for_v410.php
Normal file
41
database/migrations/2016_10_22_075804_changes_for_v410.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Class ChangesForV410
|
||||
*/
|
||||
class ChangesForV410 extends Migration
|
||||
{
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('notes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create(
|
||||
'notes', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->integer('noteable_id', false, true);
|
||||
$table->string('noteable_type');
|
||||
$table->string('title')->nullable();
|
||||
$table->text('text')->nullable();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
11
public/css/firefly.css
vendored
11
public/css/firefly.css
vendored
@ -20,6 +20,13 @@ body.waiting * {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.preferences-box {
|
||||
border:1px #ddd solid;
|
||||
border-radius: 4px 4px 0 0;
|
||||
padding: 15px;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
#map-canvas {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
@ -76,6 +83,6 @@ body.waiting * {
|
||||
}
|
||||
|
||||
.loading {
|
||||
background:url('/images/loading-small.gif') no-repeat center center;
|
||||
min-height:30px;
|
||||
background: url('/images/loading-small.gif') no-repeat center center;
|
||||
min-height: 30px;
|
||||
}
|
@ -37,6 +37,7 @@ function drawChart() {
|
||||
stackedColumnChart('chart/budget/frontpage', 'budgets-chart', {beforeDraw: beforeDrawIsEmpty});
|
||||
columnChart('chart/category/frontpage', 'categories-chart', {beforeDraw: beforeDrawIsEmpty});
|
||||
columnChart('chart/account/expense', 'expense-accounts-chart', {beforeDraw: beforeDrawIsEmpty});
|
||||
columnChart('chart/account/revenue', 'revenue-accounts-chart', {beforeDraw: beforeDrawIsEmpty});
|
||||
|
||||
|
||||
getBoxAmounts();
|
||||
|
@ -14,57 +14,167 @@ var categories = {};
|
||||
$(function () {
|
||||
"use strict";
|
||||
$('.btn-do-split').click(cloneRow);
|
||||
$('.remove-current-split').click(removeRow);
|
||||
|
||||
$.getJSON('json/expense-accounts').done(function (data) {
|
||||
destAccounts = data;
|
||||
console.log('destAccounts length is now ' + destAccounts.length);
|
||||
$('input[name$="destination_account_name]"]').typeahead({source: destAccounts});
|
||||
});
|
||||
|
||||
$.getJSON('json/revenue-accounts').done(function (data) {
|
||||
srcAccounts = data;
|
||||
console.log('srcAccounts length is now ' + srcAccounts.length);
|
||||
$('input[name$="source_account_name]"]').typeahead({source: srcAccounts});
|
||||
});
|
||||
|
||||
$.getJSON('json/categories').done(function (data) {
|
||||
categories = data;
|
||||
console.log('categories length is now ' + categories.length);
|
||||
$('input[name$="category]"]').typeahead({source: categories});
|
||||
});
|
||||
|
||||
$('input[name="amount[]"]').on('input', calculateSum)
|
||||
$('input[name$="][amount]"]').on('input', calculateSum);
|
||||
|
||||
// add auto complete:
|
||||
|
||||
|
||||
});
|
||||
|
||||
function removeRow(e) {
|
||||
"use strict";
|
||||
var rows = $('table.split-table tbody tr');
|
||||
if (rows.length === 1) {
|
||||
console.log('Will not remove last split');
|
||||
return false;
|
||||
}
|
||||
var row = $(e.target);
|
||||
var index = row.data('split');
|
||||
console.log('Trying to remove row with split ' + index);
|
||||
$('table.split-table tbody tr[data-split="' + index + '"]').remove();
|
||||
|
||||
|
||||
|
||||
resetSplits();
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
function cloneRow() {
|
||||
"use strict";
|
||||
var source = $('.initial-row').clone();
|
||||
var source = $('.table.split-table tbody tr').last().clone();
|
||||
var count = $('.split-table tbody tr').length + 1;
|
||||
var index = count - 1;
|
||||
source.removeClass('initial-row');
|
||||
source.find('.count').text('#' + count);
|
||||
source.find('input[name="amount[]"]').val("").on('input', calculateSum);
|
||||
|
||||
// // get each input, change the name?
|
||||
// $.each(source.find('input, select'), function (i, v) {
|
||||
// var obj = $(v);
|
||||
// var name = obj.attr('name').replace('[0]', '[' + index + ']');
|
||||
// obj.attr('name', name);
|
||||
// });
|
||||
|
||||
source.find('input[name$="][amount]"]').val("").on('input', calculateSum);
|
||||
if (destAccounts.length > 0) {
|
||||
console.log('Will be able to extend dest-accounts.');
|
||||
source.find('input[name="destination_account_name[]"]').typeahead({source: destAccounts});
|
||||
source.find('input[name$="destination_account_name]"]').typeahead({source: destAccounts});
|
||||
}
|
||||
|
||||
if (destAccounts.length > 0) {
|
||||
console.log('Will be able to extend src-accounts.');
|
||||
source.find('input[name="source_account_name[]"]').typeahead({source: srcAccounts});
|
||||
source.find('input[name$="source_account_name]"]').typeahead({source: srcAccounts});
|
||||
}
|
||||
if(categories.length > 0) {
|
||||
if (categories.length > 0) {
|
||||
console.log('Will be able to extend categories.');
|
||||
source.find('input[name="category[]"]').typeahead({source: categories});
|
||||
source.find('input[name$="category]"]').typeahead({source: categories});
|
||||
}
|
||||
|
||||
$('.split-table tbody').append(source);
|
||||
|
||||
// remove original click things, add them again:
|
||||
$('.remove-current-split').unbind('click').click(removeRow);
|
||||
|
||||
|
||||
calculateSum();
|
||||
resetSplits();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resetSplits() {
|
||||
"use strict";
|
||||
// loop rows, reset numbers:
|
||||
|
||||
// update the row split number:
|
||||
$.each($('table.split-table tbody tr'), function (i, v) {
|
||||
var row = $(v);
|
||||
row.attr('data-split', i);
|
||||
console.log('Row is now ' + row.data('split'));
|
||||
});
|
||||
|
||||
// loop each remove button, update the index
|
||||
$.each($('.remove-current-split'), function (i, v) {
|
||||
var button = $(v);
|
||||
button.attr('data-split', i);
|
||||
button.find('i').attr('data-split', i);
|
||||
console.log('Remove button index is now ' + button.data('split'));
|
||||
|
||||
});
|
||||
|
||||
// loop each indicator (#) and update it:
|
||||
$.each($('td.count'), function (i, v) {
|
||||
var cell = $(v);
|
||||
var index = i + 1;
|
||||
cell.text('#' + index);
|
||||
console.log('Cell is now ' + cell.text());
|
||||
});
|
||||
|
||||
// loop each possible field.
|
||||
|
||||
// ends with ][description]
|
||||
$.each($('input[name$="][description]"]'), function (i, v) {
|
||||
var input = $(v);
|
||||
input.attr('name', 'transaction[' + i + '][description]');
|
||||
console.log('description is now ' + input.attr('name'));
|
||||
});
|
||||
// ends with ][destination_account_name]
|
||||
$.each($('input[name$="][destination_account_name]"]'), function (i, v) {
|
||||
var input = $(v);
|
||||
input.attr('name', 'transaction[' + i + '][destination_account_name]');
|
||||
console.log('destination_account_name is now ' + input.attr('name'));
|
||||
});
|
||||
// ends with ][source_account_name]
|
||||
$.each($('input[name$="][source_account_name]"]'), function (i, v) {
|
||||
var input = $(v);
|
||||
input.attr('name', 'transaction[' + i + '][source_account_name]');
|
||||
console.log('source_account_name is now ' + input.attr('name'));
|
||||
});
|
||||
// ends with ][amount]
|
||||
$.each($('input[name$="][amount]"]'), function (i, v) {
|
||||
var input = $(v);
|
||||
input.attr('name', 'transaction[' + i + '][amount]');
|
||||
console.log('amount is now ' + input.attr('name'));
|
||||
});
|
||||
// ends with ][budget_id]
|
||||
$.each($('input[name$="][budget_id]"]'), function (i, v) {
|
||||
var input = $(v);
|
||||
input.attr('name', 'transaction[' + i + '][budget_id]');
|
||||
console.log('budget_id is now ' + input.attr('name'));
|
||||
});
|
||||
// ends with ][category]
|
||||
$.each($('input[name$="][category]"]'), function (i, v) {
|
||||
var input = $(v);
|
||||
input.attr('name', 'transaction[' + i + '][category]');
|
||||
console.log('category is now ' + input.attr('name'));
|
||||
});
|
||||
}
|
||||
|
||||
function calculateSum() {
|
||||
"use strict";
|
||||
var sum = 0;
|
||||
var set = $('input[name="amount[]"]');
|
||||
var set = $('input[name$="][amount]"]');
|
||||
for (var i = 0; i < set.length; i++) {
|
||||
var current = $(set[i]);
|
||||
sum += (current.val() == "" ? 0 : parseFloat(current.val()));
|
||||
|
@ -69,6 +69,7 @@ return [
|
||||
'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the <a href="/budgets">budgets</a>-page. Budgets can help you keep track of expenses.',
|
||||
'source_accounts' => 'Source account(s)',
|
||||
'destination_accounts' => 'Destination account(s)',
|
||||
'user_id_is' => 'Your user id is <strong>:user</strong>',
|
||||
|
||||
// repeat frequencies:
|
||||
'repeat_freq_monthly' => 'monthly',
|
||||
@ -273,6 +274,14 @@ return [
|
||||
'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)',
|
||||
'pref_save_settings' => 'Save settings',
|
||||
'saved_preferences' => 'Preferences saved!',
|
||||
'preferences_general' => 'General',
|
||||
'preferences_frontpage' => 'Home screen',
|
||||
'preferences_security' => 'Security',
|
||||
'preferences_layout' => 'Layout',
|
||||
'pref_home_show_deposits' => 'Show deposits on the home screen',
|
||||
'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?',
|
||||
'pref_home_do_show_deposits' => 'Yes, show them',
|
||||
'successful_count' => 'of which :count successful',
|
||||
'transaction_page_size_title' => 'Page size',
|
||||
'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions',
|
||||
'transaction_page_size_label' => 'Page size',
|
||||
@ -661,146 +670,158 @@ return [
|
||||
'add_money_to_piggy_title' => 'Add money to piggy bank ":name"',
|
||||
'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"',
|
||||
'add' => 'Add',
|
||||
'remove' => 'Remove',
|
||||
'max_amount_add' => 'The maximum amount you can add is',
|
||||
'max_amount_remove' => 'The maximum amount you can remove is',
|
||||
'update_piggy_button' => 'Update piggy bank',
|
||||
'update_piggy_title' => 'Update piggy bank ":name"',
|
||||
'updated_piggy_bank' => 'Updated piggy bank ":name"',
|
||||
'details' => 'Details',
|
||||
'events' => 'Events',
|
||||
'target_amount' => 'Target amount',
|
||||
'start_date' => 'Start date',
|
||||
'target_date' => 'Target date',
|
||||
'no_target_date' => 'No target date',
|
||||
'todo' => 'to do',
|
||||
'table' => 'Table',
|
||||
'piggy_bank_not_exists' => 'Piggy bank no longer exists.',
|
||||
'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.',
|
||||
'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date',
|
||||
'delete_piggy_bank' => 'Delete piggy bank ":name"',
|
||||
'cannot_add_amount_piggy' => 'Could not add :amount to ":name".',
|
||||
'deleted_piggy_bank' => 'Deleted piggy bank ":name"',
|
||||
'added_amount_to_piggy' => 'Added :amount to ":name"',
|
||||
'removed_amount_from_piggy' => 'Removed :amount from ":name"',
|
||||
'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".',
|
||||
|
||||
'remove' => 'Remove',
|
||||
'max_amount_add' => 'The maximum amount you can add is',
|
||||
'max_amount_remove' => 'The maximum amount you can remove is',
|
||||
'update_piggy_button' => 'Update piggy bank',
|
||||
'update_piggy_title' => 'Update piggy bank ":name"',
|
||||
'updated_piggy_bank' => 'Updated piggy bank ":name"',
|
||||
'details' => 'Details',
|
||||
'events' => 'Events',
|
||||
'target_amount' => 'Target amount',
|
||||
'start_date' => 'Start date',
|
||||
'target_date' => 'Target date',
|
||||
'no_target_date' => 'No target date',
|
||||
'todo' => 'to do',
|
||||
'table' => 'Table',
|
||||
'piggy_bank_not_exists' => 'Piggy bank no longer exists.',
|
||||
'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.',
|
||||
'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date',
|
||||
'delete_piggy_bank' => 'Delete piggy bank ":name"',
|
||||
'cannot_add_amount_piggy' => 'Could not add :amount to ":name".',
|
||||
'deleted_piggy_bank' => 'Deleted piggy bank ":name"',
|
||||
'added_amount_to_piggy' => 'Added :amount to ":name"',
|
||||
'removed_amount_from_piggy' => 'Removed :amount from ":name"',
|
||||
'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".',
|
||||
|
||||
// tags
|
||||
'regular_tag' => 'Just a regular tag.',
|
||||
'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.',
|
||||
'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.',
|
||||
'delete_tag' => 'Delete tag ":tag"',
|
||||
'deleted_tag' => 'Deleted tag ":tag"',
|
||||
'new_tag' => 'Make new tag',
|
||||
'edit_tag' => 'Edit tag ":tag"',
|
||||
'updated_tag' => 'Updated tag ":tag"',
|
||||
'created_tag' => 'Tag ":tag" has been created!',
|
||||
'no_year' => 'No year set',
|
||||
'no_month' => 'No month set',
|
||||
'tag_title_nothing' => 'Default tags',
|
||||
'tag_title_balancingAct' => 'Balancing act tags',
|
||||
'tag_title_advancePayment' => 'Advance payment tags',
|
||||
'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like <span class="label label-info">expensive</span>, <span class="label label-info">bill</span> or <span class="label label-info">for-party</span>. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called <span class="label label-success"> Christmas dinner with friends</span> and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.',
|
||||
'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.',
|
||||
'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.',
|
||||
'regular_tag' => 'Just a regular tag.',
|
||||
'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.',
|
||||
'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.',
|
||||
'delete_tag' => 'Delete tag ":tag"',
|
||||
'deleted_tag' => 'Deleted tag ":tag"',
|
||||
'new_tag' => 'Make new tag',
|
||||
'edit_tag' => 'Edit tag ":tag"',
|
||||
'updated_tag' => 'Updated tag ":tag"',
|
||||
'created_tag' => 'Tag ":tag" has been created!',
|
||||
'no_year' => 'No year set',
|
||||
'no_month' => 'No month set',
|
||||
'tag_title_nothing' => 'Default tags',
|
||||
'tag_title_balancingAct' => 'Balancing act tags',
|
||||
'tag_title_advancePayment' => 'Advance payment tags',
|
||||
'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like <span class="label label-info">expensive</span>, <span class="label label-info">bill</span> or <span class="label label-info">for-party</span>. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called <span class="label label-success"> Christmas dinner with friends</span> and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.',
|
||||
'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.',
|
||||
'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.',
|
||||
|
||||
'transaction_journal_information' => 'Transaction information',
|
||||
'transaction_journal_meta' => 'Meta information',
|
||||
'total_amount' => 'Total amount',
|
||||
|
||||
// administration
|
||||
'administration' => 'Administration',
|
||||
'user_administration' => 'User administration',
|
||||
'list_all_users' => 'All users',
|
||||
'all_users' => 'All users',
|
||||
'all_blocked_domains' => 'All blocked domains',
|
||||
'blocked_domains' => 'Blocked domains',
|
||||
'no_domains_banned' => 'No domains blocked',
|
||||
'all_user_domains' => 'All user email address domains',
|
||||
'all_domains_is_filtered' => 'This list does not include already blocked domains.',
|
||||
'domain_now_blocked' => 'Domain :domain is now blocked',
|
||||
'domain_now_unblocked' => 'Domain :domain is now unblocked',
|
||||
'manual_block_domain' => 'Block a domain by hand',
|
||||
'block_domain' => 'Block domain',
|
||||
'no_domain_filled_in' => 'No domain filled in',
|
||||
'domain_already_blocked' => 'Domain :domain is already blocked',
|
||||
'domain_is_now_blocked' => 'Domain :domain is now blocked',
|
||||
'instance_configuration' => 'Configuration',
|
||||
'firefly_instance_configuration' => 'Configuration options for Firefly III',
|
||||
'setting_single_user_mode' => 'Single user mode',
|
||||
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
|
||||
'store_configuration' => 'Store configuration',
|
||||
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your <a href=":link">settings</a>.',
|
||||
'administration' => 'Administration',
|
||||
'user_administration' => 'User administration',
|
||||
'list_all_users' => 'All users',
|
||||
'all_users' => 'All users',
|
||||
'all_blocked_domains' => 'All blocked domains',
|
||||
'blocked_domains' => 'Blocked domains',
|
||||
'no_domains_banned' => 'No domains blocked',
|
||||
'all_user_domains' => 'All user email address domains',
|
||||
'all_domains_is_filtered' => 'This list does not include already blocked domains.',
|
||||
'domain_now_blocked' => 'Domain :domain is now blocked',
|
||||
'domain_now_unblocked' => 'Domain :domain is now unblocked',
|
||||
'manual_block_domain' => 'Block a domain by hand',
|
||||
'block_domain' => 'Block domain',
|
||||
'no_domain_filled_in' => 'No domain filled in',
|
||||
'domain_already_blocked' => 'Domain :domain is already blocked',
|
||||
'domain_is_now_blocked' => 'Domain :domain is now blocked',
|
||||
'instance_configuration' => 'Configuration',
|
||||
'firefly_instance_configuration' => 'Configuration options for Firefly III',
|
||||
'setting_single_user_mode' => 'Single user mode',
|
||||
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).',
|
||||
'store_configuration' => 'Store configuration',
|
||||
'single_user_administration' => 'User administration for :email',
|
||||
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your <a href=":link">settings</a>.',
|
||||
'user_data_information' => 'User data',
|
||||
'user_information' => 'User information',
|
||||
'total_size' => 'total size',
|
||||
'budget_or_budgets' => 'budget(s)',
|
||||
'budgets_with_limits' => 'budget(s) with configured amount',
|
||||
'rule_or_rules' => 'rule(s)',
|
||||
'rulegroup_or_groups' => 'rule group(s)',
|
||||
|
||||
// split a transaction:
|
||||
'transaction_meta_data' => 'Transaction meta-data',
|
||||
'transaction_dates' => 'Transaction dates',
|
||||
'splits' => 'Splits',
|
||||
'split_title_withdrawal' => 'Split your new withdrawal',
|
||||
'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
|
||||
'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
|
||||
'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
|
||||
'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
|
||||
'store_splitted_withdrawal' => 'Store splitted withdrawal',
|
||||
'update_splitted_withdrawal' => 'Update splitted withdrawal',
|
||||
'split_title_deposit' => 'Split your new deposit',
|
||||
'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.',
|
||||
'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.',
|
||||
'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.',
|
||||
'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
|
||||
'store_splitted_deposit' => 'Store splitted deposit',
|
||||
'split_title_transfer' => 'Split your new transfer',
|
||||
'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
|
||||
'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
|
||||
'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
|
||||
'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
|
||||
'store_splitted_transfer' => 'Store splitted transfer',
|
||||
'add_another_split' => 'Add another split',
|
||||
'split-transactions' => 'Split transactions',
|
||||
'split-new-transaction' => 'Split a new transaction',
|
||||
'do_split' => 'Do a split',
|
||||
'split_this_withdrawal' => 'Split this withdrawal',
|
||||
'split_this_deposit' => 'Split this deposit',
|
||||
'split_this_transfer' => 'Split this transfer',
|
||||
'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.',
|
||||
'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.',
|
||||
'no_edit_multiple_left' => 'You have selected no valid transactions to edit.',
|
||||
'transaction_meta_data' => 'Transaction meta-data',
|
||||
'transaction_dates' => 'Transaction dates',
|
||||
'splits' => 'Splits',
|
||||
'split_title_withdrawal' => 'Split your new withdrawal',
|
||||
'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
|
||||
'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
|
||||
'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
|
||||
'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
|
||||
'store_splitted_withdrawal' => 'Store splitted withdrawal',
|
||||
'update_splitted_withdrawal' => 'Update splitted withdrawal',
|
||||
'split_title_deposit' => 'Split your new deposit',
|
||||
'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.',
|
||||
'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.',
|
||||
'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.',
|
||||
'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
|
||||
'store_splitted_deposit' => 'Store splitted deposit',
|
||||
'split_title_transfer' => 'Split your new transfer',
|
||||
'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.',
|
||||
'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.',
|
||||
'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.',
|
||||
'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
|
||||
'store_splitted_transfer' => 'Store splitted transfer',
|
||||
'add_another_split' => 'Add another split',
|
||||
'split-transactions' => 'Split transactions',
|
||||
'split-new-transaction' => 'Split a new transaction',
|
||||
'do_split' => 'Do a split',
|
||||
'split_this_withdrawal' => 'Split this withdrawal',
|
||||
'split_this_deposit' => 'Split this deposit',
|
||||
'split_this_transfer' => 'Split this transfer',
|
||||
'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.',
|
||||
'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.',
|
||||
'no_edit_multiple_left' => 'You have selected no valid transactions to edit.',
|
||||
|
||||
// import
|
||||
'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their <a href="https://github.com/firefly-iii/import-configurations/wiki">configuration file</a>.',
|
||||
'import_data_index' => 'Index',
|
||||
'import_file_type_csv' => 'CSV (comma separated values)',
|
||||
'import_file_type_help' => 'Select the type of file you will upload',
|
||||
'import_start' => 'Start the import',
|
||||
'configure_import' => 'Further configure your import',
|
||||
'import_finish_configuration' => 'Finish configuration',
|
||||
'settings_for_import' => 'Settings',
|
||||
'import_status' => 'Import status',
|
||||
'import_status_text' => 'The import is currently running, or will start momentarily.',
|
||||
'import_complete' => 'Import configuration complete!',
|
||||
'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.',
|
||||
'import_download_config' => 'Download configuration',
|
||||
'import_start_import' => 'Start import',
|
||||
'import_intro_beta' => 'The import function of Firefly III is in beta. Many users of Firefly III have tried many different files. Although each individual compontent of this import routine works (really), the combination might break. If your file cannot be imported by Firefly, please read <a href="https://github.com/JC5/firefly-iii/wiki/Submit-issues-with-sensitive-data-in-them">this wiki page</a> so I can fix the problem you have run into.',
|
||||
'import_data' => 'Import data',
|
||||
'import_data_full' => 'Import data into Firefly III',
|
||||
'import' => 'Import',
|
||||
'import_intro_what_it_does' => 'This page allows you to import data into Firefly III. To do so, export data from your bank, or from another financial management system. Upload that file here. Firefly III will convert the data. You need to give it some directions. Please select a file and follow the instructions.',
|
||||
'import_intro_import_conf_title' => 'Import "configuration"',
|
||||
'import_intro_beta_warning' => 'Warning',
|
||||
'import_intro_import_conf_text' => 'As you will discover over the next few pages, this import routine has a lot of settings. These settings are mainly dependent on the bank (or financial management software) your file comes from. There is a good chance somebody else already imported such a file and has shared their <em>configuration file</em>. Please visit the <strong><a href="https://github.com/firefly-iii/import-configurations/wiki">import configuration center</a></strong> to see if there already is a configuration available for your bank or system. If there is, you should download this configuration file and upload it here as well. It will save you a lot of time!',
|
||||
'import_file_help' => 'Select your file',
|
||||
'import_status_settings_complete' => 'The import is ready to start.',
|
||||
'import_status_import_complete' => 'The import has completed.',
|
||||
'import_status_import_running' => 'The import is currently running. Please be patient.',
|
||||
'import_status_header' => 'Import status and progress',
|
||||
'import_status_errors' => 'Import errors',
|
||||
'import_status_report' => 'Import report',
|
||||
'import_finished' => 'Import has finished',
|
||||
'import_error_single' => 'An error has occured during the import.',
|
||||
'import_error_multi' => 'Some errors occured during the import.',
|
||||
'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:',
|
||||
'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.',
|
||||
'import_double' => 'Row #:row: This row has been imported before, and is stored in <a href=":link">:description</a>.',
|
||||
'import_finished_all' => 'The import has finished. Please check out the results below.',
|
||||
'import_with_key' => 'Import with key \':key\'',
|
||||
'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their <a href="https://github.com/firefly-iii/import-configurations/wiki">configuration file</a>.',
|
||||
'import_data_index' => 'Index',
|
||||
'import_file_type_csv' => 'CSV (comma separated values)',
|
||||
'import_file_type_help' => 'Select the type of file you will upload',
|
||||
'import_start' => 'Start the import',
|
||||
'configure_import' => 'Further configure your import',
|
||||
'import_finish_configuration' => 'Finish configuration',
|
||||
'settings_for_import' => 'Settings',
|
||||
'import_status' => 'Import status',
|
||||
'import_status_text' => 'The import is currently running, or will start momentarily.',
|
||||
'import_complete' => 'Import configuration complete!',
|
||||
'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.',
|
||||
'import_download_config' => 'Download configuration',
|
||||
'import_start_import' => 'Start import',
|
||||
'import_intro_beta' => 'The import function of Firefly III is in beta. Many users of Firefly III have tried many different files. Although each individual compontent of this import routine works (really), the combination might break. If your file cannot be imported by Firefly, please read <a href="https://github.com/JC5/firefly-iii/wiki/Submit-issues-with-sensitive-data-in-them">this wiki page</a> so I can fix the problem you have run into.',
|
||||
'import_data' => 'Import data',
|
||||
'import_data_full' => 'Import data into Firefly III',
|
||||
'import' => 'Import',
|
||||
'import_intro_what_it_does' => 'This page allows you to import data into Firefly III. To do so, export data from your bank, or from another financial management system. Upload that file here. Firefly III will convert the data. You need to give it some directions. Please select a file and follow the instructions.',
|
||||
'import_intro_import_conf_title' => 'Import "configuration"',
|
||||
'import_intro_beta_warning' => 'Warning',
|
||||
'import_intro_import_conf_text' => 'As you will discover over the next few pages, this import routine has a lot of settings. These settings are mainly dependent on the bank (or financial management software) your file comes from. There is a good chance somebody else already imported such a file and has shared their <em>configuration file</em>. Please visit the <strong><a href="https://github.com/firefly-iii/import-configurations/wiki">import configuration center</a></strong> to see if there already is a configuration available for your bank or system. If there is, you should download this configuration file and upload it here as well. It will save you a lot of time!',
|
||||
'import_file_help' => 'Select your file',
|
||||
'import_status_settings_complete' => 'The import is ready to start.',
|
||||
'import_status_import_complete' => 'The import has completed.',
|
||||
'import_status_import_running' => 'The import is currently running. Please be patient.',
|
||||
'import_status_header' => 'Import status and progress',
|
||||
'import_status_errors' => 'Import errors',
|
||||
'import_status_report' => 'Import report',
|
||||
'import_finished' => 'Import has finished',
|
||||
'import_error_single' => 'An error has occured during the import.',
|
||||
'import_error_multi' => 'Some errors occured during the import.',
|
||||
'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:',
|
||||
'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.',
|
||||
'import_double' => 'Row #:row: This row has been imported before, and is stored in <a href=":link">:description</a>.',
|
||||
'import_finished_all' => 'The import has finished. Please check out the results below.',
|
||||
'import_with_key' => 'Import with key \':key\'',
|
||||
|
||||
'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the <strong><a href="https://github.com/firefly-iii/import-configurations/wiki">import configuration center</a></strong>. This will allow other users of Firefly III to import their files more easily.',
|
||||
|
||||
|
@ -25,6 +25,7 @@ return [
|
||||
'matchedOn' => 'Matched on',
|
||||
'matchesOn' => 'Matched on',
|
||||
'account_type' => 'Account type',
|
||||
'created_at' => 'Created at',
|
||||
'new_balance' => 'New balance',
|
||||
'account' => 'Account',
|
||||
'matchingAmount' => 'Amount',
|
||||
@ -71,4 +72,17 @@ return [
|
||||
'blocked_code' => 'Block code',
|
||||
'domain' => 'Domain',
|
||||
'registration_attempts' => 'Registration attempts',
|
||||
'source_account' => 'Source account',
|
||||
'destination_account' => 'Destination account',
|
||||
|
||||
'accounts_count' => 'Number of accounts',
|
||||
'journals_count' => 'Number of journals',
|
||||
'attachments_count' => 'Number of attachments',
|
||||
'bills_count' => 'Number of bills',
|
||||
'categories_count' => 'Number of categories',
|
||||
'export_jobs_count' => 'Number of export jobs',
|
||||
'import_jobs_count' => 'Number of import jobs',
|
||||
'budget_count' => 'Number of budgets',
|
||||
'rule_and_groups_count' => 'Number of rules and rule groups',
|
||||
'tags_count' => 'Number of tags',
|
||||
];
|
||||
|
@ -35,7 +35,8 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>#{{ user.id }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.users.show',user.id) }}">{{ user.email }}</a></td>
|
||||
<td>
|
||||
{{ user.created_at.formatLocalized(monthAndDayFormat) }}
|
||||
{{ user.created_at.format('H:i') }}
|
||||
|
151
resources/views/admin/users/show.twig
Normal file
151
resources/views/admin/users/show.twig
Normal file
@ -0,0 +1,151 @@
|
||||
{% extends "./layout/default.twig" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, user) }}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'user_information'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ trans('list.id') }}</td>
|
||||
<td>#{{ user.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.email') }}</td>
|
||||
<td><a href="mailto:{{ user.email }}">{{ user.email }}</a>
|
||||
<td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.created_at') }}</td>
|
||||
<td>
|
||||
{{ user.created_at.formatLocalized(monthAndDayFormat) }}
|
||||
{{ user.created_at.format('H:i') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.registered_from') }}</td>
|
||||
<td>{{ registration }} ({{ registrationHost }})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.confirmed_from') }}</td>
|
||||
<td>{{ confirmation }} ({{ confirmationHost }})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.is_admin') }}</td>
|
||||
<td>
|
||||
{% if information.is_admin %}
|
||||
<small class="text-success"><i class="fa fa-fw fa-check"></i></small> Yes
|
||||
{% else %}
|
||||
<small class="text-danger"><i class="fa fa-fw fa-times"></i></small> No
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.has_two_factor') }}</td>
|
||||
<td>
|
||||
{% if information.has_2fa %}
|
||||
<small class="text-success"><i class="fa fa-fw fa-check"></i></small> Yes
|
||||
{% else %}
|
||||
<small class="text-danger"><i class="fa fa-fw fa-times"></i></small> No
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.is_activated') }}</td>
|
||||
<td>
|
||||
{% if information.is_activated %}
|
||||
<small class="text-success"><i class="fa fa-fw fa-check"></i></small> Yes
|
||||
{% else %}
|
||||
<small class="text-danger"><i class="fa fa-fw fa-times"></i></small> No
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.is_blocked') }}</td>
|
||||
<td>
|
||||
{% if information.blocked %}
|
||||
<small class="text-danger"><i class="fa fa-fw fa-check" title="{{ 'yes'|_ }}"></i></small> Yes:
|
||||
|
||||
{% if information.blocked_code == "" %}
|
||||
<em>~</em>
|
||||
{% else %}
|
||||
{{ information.blocked_code }}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<small class="text-success"><i class="fa fa-fw fa-times" title="{{ 'no'|_ }}"></i></small> No
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'user_data_information'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<tr>
|
||||
<td>{{ trans('list.accounts_count') }}</td>
|
||||
<td>{{ information.accounts }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.journals_count') }}</td>
|
||||
<td>{{ information.journals }} ({{ information.transactions }} {{ trans('firefly.transactions') }})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.attachments_count') }}</td>
|
||||
<td>{{ information.attachments }} ({{ trans('firefly.total_size') }}: {{ information.attachments_size|filesize }})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.bills_count') }}</td>
|
||||
<td>{{ information.bills }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.categories_count') }}</td>
|
||||
<td>{{ information.categories }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.export_jobs_count') }}</td>
|
||||
<td>{{ information.export_jobs }}, {{ trans('firefly.successful_count', {count: information.export_jobs_success}) }}
|
||||
<!-- of which x successful --></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.import_jobs_count') }}</td>
|
||||
<td>{{ information.import_jobs }}, {{ trans('firefly.successful_count', {count: information.import_jobs_success}) }}
|
||||
<!-- of which x successful --></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.budget_count') }}</td>
|
||||
<td>{{ information.budgets }} {{ trans('firefly.budget_or_budgets') }},
|
||||
{{ information.budgets_with_limits }} {{ trans('firefly.budgets_with_limits') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.rule_and_groups_count') }}</td>
|
||||
<td>
|
||||
{{ information.rules }} {{ 'rule_or_rules'|_ }} {{ 'in'|_ }} {{ information.rule_groups }}
|
||||
|
||||
{{ 'rulegroup_or_groups'|_ }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.tags_count') }}</td>
|
||||
<td>{{ information.tags }} tags</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -2,7 +2,12 @@
|
||||
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{{ Form.input('date', name, value, options) }}
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-calendar"></i>
|
||||
</div>
|
||||
{{ Form.input('date', name, value, options) }}
|
||||
</div>
|
||||
{% include 'form/help.twig' %}
|
||||
{% include 'form/feedback.twig' %}
|
||||
</div>
|
||||
|
@ -101,6 +101,18 @@
|
||||
<canvas id="expense-accounts-chart" style="width:100%;" height="400"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- OPTIONAL REVENUE ACCOUNTS -->
|
||||
{% if showDepositsFrontpage %}
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'revenue_accounts'|_ }}</h3>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<canvas id="revenue-accounts-chart" style="width:100%;" height="400"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<th class="hidden-sm hidden-xs">{{ trans('list.matchesOn') }}</th>
|
||||
<th colspan="2">{{ trans('list.matchingAmount') }}</th>
|
||||
<th class="hidden-sm hidden-xs">{{ trans('list.paid_current_period') }}</th>
|
||||
<th class="hidden-sm hidden-xs">{{ trans('list.expectedMatch') }}</th>
|
||||
<th class="hidden-sm hidden-xs">{{ trans('list.next_expected_match') }}</th>
|
||||
<th class="hidden-sm hidden-xs">{{ trans('list.active') }}</th>
|
||||
<th class="hidden-sm hidden-xs">{{ trans('list.automatch') }}</th>
|
||||
<th class="hidden-sm hidden-xs">{{ trans('list.repeat_freq') }}</th>
|
||||
|
@ -31,6 +31,7 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.date('targetdate') }}
|
||||
{{ ExpandedForm.textarea('note') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -33,6 +33,7 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.date('targetdate') }}
|
||||
{{ ExpandedForm.textarea('note') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<td>{{ 'account'|_ }}</td>
|
||||
<td style="width:40%;">{{ 'account'|_ }}</td>
|
||||
<td><a href="{{ route('accounts.show', piggyBank.account_id) }}">{{ piggyBank.account.name }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -73,6 +73,20 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if note %}
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ trans('form.notes') }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
{{ note.text|nl2br }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'table'|_ }}</h3>
|
||||
|
@ -7,217 +7,254 @@
|
||||
{% block content %}
|
||||
<form method="POST" action="{{ route('preferences.update') }}" accept-charset="UTF-8" class="form-horizontal" id="preferences">
|
||||
<input name="_token" type="hidden" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'pref_home_screen_accounts'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-info">{{ 'pref_home_screen_accounts_help'|_ }}</p>
|
||||
{% for account in accounts %}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
{% if account.id in frontPageAccounts.data or frontPageAccounts.data|length == 0 %}
|
||||
<input type="checkbox" name="frontPageAccounts[]" value="{{ account.id }}"
|
||||
checked> {{ account.name }}
|
||||
{% else %}
|
||||
<input type="checkbox" name="frontPageAccounts[]"
|
||||
value="{{ account.id }}"> {{ account.name }}
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<!-- start of preferences tabs -->
|
||||
<div class="nav-tabs-custom">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#general" data-toggle="tab" aria-expanded="true">{{ 'preferences_general'|_ }}</a></li>
|
||||
<li class=""><a href="#frontpage" data-toggle="tab" aria-expanded="false">{{ 'preferences_frontpage'|_ }}</a></li>
|
||||
<li class=""><a href="#security" data-toggle="tab" aria-expanded="false">{{ 'preferences_security'|_ }}</a></li>
|
||||
<li class=""><a href="#layout" data-toggle="tab" aria-expanded="false">{{ 'preferences_layout'|_ }}</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="general">
|
||||
<!-- general settings here -->
|
||||
<div class="row">
|
||||
<!-- general settings column A -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<!-- language -->
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'pref_languages'|_ }}</h3>
|
||||
<p class="text-info">{{ 'pref_languages_help'|_ }}</p>
|
||||
{% for key, lang in Config.get('firefly.languages') %}
|
||||
{% if lang.complete == true or (lang.complete == false and showIncomplete) %}
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="language" value="{{ key }}"
|
||||
{% if language == key %}
|
||||
checked
|
||||
{% endif %}
|
||||
/>
|
||||
{{ lang.name_locale }} ({{ lang.name_english }})
|
||||
{% if lang.complete == false %}
|
||||
<span class="small text-danger">({{ 'language_incomplete'|_ }})</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- fiscal year -->
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'pref_custom_fiscal_year'|_ }}</h3>
|
||||
<p class="text-info">
|
||||
{{ 'pref_custom_fiscal_year_help'|_ }}
|
||||
</p>
|
||||
{% set isCustomFiscalYear = customFiscalYear == 1 ? true : false %}
|
||||
{{ ExpandedForm.checkbox('customFiscalYear','1',isCustomFiscalYear,{ 'label' : 'pref_custom_fiscal_year_label'|_ }) }}
|
||||
{{ ExpandedForm.date('fiscalYearStart',fiscalYearStart,{ 'label' : 'pref_fiscal_year_start_label'|_ }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- general settings column B -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
|
||||
<!-- transaction preferences -->
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'pref_optional_fields_transaction'|_ }}</h3>
|
||||
<p class="text-info">
|
||||
{{ 'pref_optional_fields_transaction_help'|_ }}
|
||||
</p>
|
||||
<h4>{{ 'optional_tj_date_fields'|_ }}</h4>
|
||||
{{ ExpandedForm.checkbox('tj[interest_date]','1', tjOptionalFields.interest_date,{ 'label' : 'pref_optional_tj_interest_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[book_date]','1', tjOptionalFields.book_date,{ 'label' : 'pref_optional_tj_book_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[process_date]','1', tjOptionalFields.process_date,{ 'label' : 'pref_optional_tj_process_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[due_date]','1', tjOptionalFields.due_date,{ 'label' : 'pref_optional_tj_due_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[payment_date]','1', tjOptionalFields.payment_date,{ 'label' : 'pref_optional_tj_payment_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[invoice_date]','1', tjOptionalFields.invoice_date,{ 'label' : 'pref_optional_tj_invoice_date'|_ }) }}
|
||||
|
||||
<h4>{{ 'optional_tj_business_fields'|_ }}</h4>
|
||||
{{ ExpandedForm.checkbox('tj[internal_reference]','1', tjOptionalFields.internal_reference,{ 'label' : 'pref_optional_tj_internal_reference'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[notes]','1', tjOptionalFields.notes,{ 'label' : 'pref_optional_tj_notes'|_ }) }}
|
||||
|
||||
<h4>{{ 'optional_tj_attachment_fields'|_ }}</h4>
|
||||
{{ ExpandedForm.checkbox('tj[attachments]','1', tjOptionalFields.attachments,{ 'label' : 'pref_optional_tj_attachments'|_ }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'pref_custom_fiscal_year'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-info">
|
||||
{{ 'pref_custom_fiscal_year_help'|_ }}
|
||||
</p>
|
||||
{% set isCustomFiscalYear = customFiscalYear == 1 ? true : false %}
|
||||
{{ ExpandedForm.checkbox('customFiscalYear','1',isCustomFiscalYear,{ 'label' : 'pref_custom_fiscal_year_label'|_ }) }}
|
||||
{{ ExpandedForm.date('fiscalYearStart',fiscalYearStart,{ 'label' : 'pref_fiscal_year_start_label'|_ }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- transaction preferences -->
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'pref_optional_fields_transaction'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-info">
|
||||
{{ 'pref_optional_fields_transaction_help'|_ }}
|
||||
</p>
|
||||
<h4>{{ 'optional_tj_date_fields'|_ }}</h4>
|
||||
{{ ExpandedForm.checkbox('tj[interest_date]','1', tjOptionalFields.interest_date,{ 'label' : 'pref_optional_tj_interest_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[book_date]','1', tjOptionalFields.book_date,{ 'label' : 'pref_optional_tj_book_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[process_date]','1', tjOptionalFields.process_date,{ 'label' : 'pref_optional_tj_process_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[due_date]','1', tjOptionalFields.due_date,{ 'label' : 'pref_optional_tj_due_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[payment_date]','1', tjOptionalFields.payment_date,{ 'label' : 'pref_optional_tj_payment_date'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[invoice_date]','1', tjOptionalFields.invoice_date,{ 'label' : 'pref_optional_tj_invoice_date'|_ }) }}
|
||||
|
||||
<h4>{{ 'optional_tj_business_fields'|_ }}</h4>
|
||||
{{ ExpandedForm.checkbox('tj[internal_reference]','1', tjOptionalFields.internal_reference,{ 'label' : 'pref_optional_tj_internal_reference'|_ }) }}
|
||||
{{ ExpandedForm.checkbox('tj[notes]','1', tjOptionalFields.notes,{ 'label' : 'pref_optional_tj_notes'|_ }) }}
|
||||
|
||||
<h4>{{ 'optional_tj_attachment_fields'|_ }}</h4>
|
||||
{{ ExpandedForm.checkbox('tj[attachments]','1', tjOptionalFields.attachments,{ 'label' : 'pref_optional_tj_attachments'|_ }) }}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'pref_view_range'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-info">{{ 'pref_view_range_help'|_ }}</p>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1D" {% if viewRange == '1D' %} checked {% endif %}>
|
||||
{{ 'pref_1D'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1W" {% if viewRange == '1W' %} checked {% endif %}>
|
||||
{{ 'pref_1W'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1M" {% if viewRange == '1M' %} checked {% endif %}>
|
||||
{{ 'pref_1M'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="3M" {% if viewRange == '3M' %} checked {% endif %}>
|
||||
{{ 'pref_3M'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="6M" {% if viewRange == '6M' %} checked {% endif %}>
|
||||
{{ 'pref_6M'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1Y" {% if viewRange == '1Y' %} checked {% endif %}>
|
||||
{{ 'pref_1Y'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'pref_languages'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-info">{{ 'pref_languages_help'|_ }}</p>
|
||||
{% for key, lang in Config.get('firefly.languages') %}
|
||||
{% if lang.complete == true or (lang.complete == false and showIncomplete) %}
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="language" value="{{ key }}"
|
||||
{% if language == key %}
|
||||
checked
|
||||
{% endif %}
|
||||
/>
|
||||
{{ lang.name_locale }} ({{ lang.name_english }})
|
||||
{% if lang.complete == false %}
|
||||
<span class="small text-danger">({{ 'language_incomplete'|_ }})</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
<div class="tab-pane" id="frontpage">
|
||||
<!-- frontpage settings here -->
|
||||
<div class="row">
|
||||
<!-- frontpage settings column a -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'pref_home_screen_accounts'|_ }}</h3>
|
||||
<p class="text-info">{{ 'pref_home_screen_accounts_help'|_ }}</p>
|
||||
{% for account in accounts %}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
{% if account.id in frontPageAccounts.data or frontPageAccounts.data|length == 0 %}
|
||||
<input type="checkbox" name="frontPageAccounts[]" value="{{ account.id }}"
|
||||
checked> {{ account.name }}
|
||||
{% else %}
|
||||
<input type="checkbox" name="frontPageAccounts[]"
|
||||
value="{{ account.id }}"> {{ account.name }}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- page size -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'transaction_page_size_title'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-info">{{ 'transaction_page_size_help'|_ }}</p>
|
||||
{{ ExpandedForm.integer('transactionPageSize',transactionPageSize,{'label' : 'transaction_page_size_label'|_}) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-lg-6 col-md-6 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'pref_two_factor_auth'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-info">{{ 'pref_two_factor_auth_help'|_ }}</p>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="twoFactorAuthEnabled" value="1"
|
||||
{% if is2faEnabled == '1' %} checked {% endif %}> {{ 'pref_enable_two_factor_auth'|_ }}
|
||||
</label>
|
||||
<!-- frontpage settings column b -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<!-- show deposit chart -->
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'pref_home_show_deposits'|_ }}</h3>
|
||||
<p class="text-info">{{ 'pref_home_show_deposits_info'|_ }}</p>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="showDepositsFrontpage[]" value="{{ showDepositsFrontpage }}"
|
||||
{% if showDepositsFrontpage %}
|
||||
checked
|
||||
{% endif %}
|
||||
> {{ 'pref_home_do_show_deposits'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="security">
|
||||
<!-- security settings here -->
|
||||
<div class="row">
|
||||
<!-- security settings column A -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
|
||||
{% if is2faEnabled == 1 and has2faSecret == true %}
|
||||
<!-- 2fa -->
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'pref_two_factor_auth'|_ }}</h3>
|
||||
<p class="text-info">{{ 'pref_two_factor_auth_help'|_ }}</p>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="twoFactorAuthEnabled" value="1"
|
||||
{% if is2faEnabled == '1' %} checked {% endif %}> {{ 'pref_enable_two_factor_auth'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<a href="{{ route('preferences.code') }}">{{ 'pref_two_factor_auth_reset_code'|_ }}</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<a href="{{ route('preferences.delete-code') }}">{{ 'pref_two_factor_auth_remove_code'|_ }}</a>
|
||||
{{ 'pref_two_factor_auth_remove_will_disable'|_ }}
|
||||
</label>
|
||||
{% if is2faEnabled == 1 and has2faSecret == true %}
|
||||
|
||||
<div class="col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<a href="{{ route('preferences.code') }}">{{ 'pref_two_factor_auth_reset_code'|_ }}</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<a href="{{ route('preferences.delete-code') }}">{{ 'pref_two_factor_auth_remove_code'|_ }}</a>
|
||||
{{ 'pref_two_factor_auth_remove_will_disable'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- security settings column B -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="layout">
|
||||
<!-- layout settings here -->
|
||||
<div class="row">
|
||||
<!-- layout settings column A -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<!-- view range -->
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'pref_view_range'|_ }}</h3>
|
||||
<p class="text-info">{{ 'pref_view_range_help'|_ }}</p>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1D" {% if viewRange == '1D' %} checked {% endif %}>
|
||||
{{ 'pref_1D'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1W" {% if viewRange == '1W' %} checked {% endif %}>
|
||||
{{ 'pref_1W'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1M" {% if viewRange == '1M' %} checked {% endif %}>
|
||||
{{ 'pref_1M'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="3M" {% if viewRange == '3M' %} checked {% endif %}>
|
||||
{{ 'pref_3M'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="6M" {% if viewRange == '6M' %} checked {% endif %}>
|
||||
{{ 'pref_6M'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="viewRange"
|
||||
value="1Y" {% if viewRange == '1Y' %} checked {% endif %}>
|
||||
{{ 'pref_1Y'|_ }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end -->
|
||||
</div>
|
||||
|
||||
<!-- layout settings column B -->
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<div class="preferences-box">
|
||||
<h3>{{ 'transaction_page_size_title'|_ }}</h3>
|
||||
<p class="text-info">{{ 'transaction_page_size_help'|_ }}</p>
|
||||
{{ ExpandedForm.integer('transactionPageSize',transactionPageSize,{'label' : 'transaction_page_size_label'|_}) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
|
@ -12,6 +12,9 @@
|
||||
<h3 class="box-title">Options</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
{{ trans('firefly.user_id_is',{user: userId})|raw }}
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="{{ route('profile.change-password') }}">{{ 'change_your_password'|_ }}</a></li>
|
||||
<li><a class="text-danger" href="{{ route('profile.delete-account') }}">{{ 'delete_account'|_ }}</a></li>
|
||||
|
@ -1,270 +0,0 @@
|
||||
{% extends "./layout/default.twig" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, preFilled.what) }}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<form method="POST" action="{{ route('split.journal.store',journal.id) }}" accept-charset="UTF-8" class="form-horizontal" id="update"
|
||||
enctype="multipart/form-data">
|
||||
|
||||
<input name="_token" type="hidden" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="id" value="{{ journal.id }}"/>
|
||||
<input type="hidden" name="what" value="{{ preFilled.what }}"/>
|
||||
|
||||
<!--
|
||||
A splitted withdrawal has a single source with multiple destinations.
|
||||
Amount X is withdrawn from one account and submitted to multiple accounts.
|
||||
Groceries can be split in several categories this way.
|
||||
|
||||
A splitted deposit has a singe destination and multiple sources.
|
||||
Amount X is deposited from multiple sources.
|
||||
Salary can be split in several types this way (base salary, reimbursements, bonus)
|
||||
|
||||
-->
|
||||
|
||||
{% if errors.all()|length > 0 %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<div class="box box-danger">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'errors'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<ul>
|
||||
{% for key, err in errors.all() %}
|
||||
<li class="text-danger">{{ err }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'transaction_data'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.text('journal_description', journal.description) }}
|
||||
{{ ExpandedForm.select('journal_currency_id', currencies, journal.transaction_currency_id) }}
|
||||
{{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }}
|
||||
<input type="hidden" name="journal_amount" value="{{ preFilled.journal_amount }}"/>
|
||||
|
||||
<!-- show source if withdrawal or transfer -->
|
||||
{% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %}
|
||||
{{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }}
|
||||
{% endif %}
|
||||
|
||||
<!-- show destination account id, if deposit (is asset): -->
|
||||
{% if preFilled.what == 'deposit' %}
|
||||
{{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }}
|
||||
{% endif %}
|
||||
|
||||
<!-- show static destination if transfer -->
|
||||
{% if preFilled.what == 'transfer' %}
|
||||
{{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'transaction_meta_data'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.date('date', journal.date) }}
|
||||
|
||||
{% if optionalFields.interest_date or journal.interest_date %}
|
||||
<!-- INTEREST DATE -->
|
||||
{{ ExpandedForm.date('interest_date', journal.interest_date) }}
|
||||
{% endif %}
|
||||
|
||||
{% if optionalFields.book_date or journal.book_date %}
|
||||
<!-- BOOK DATE -->
|
||||
{{ ExpandedForm.date('book_date', journal.book_date) }}
|
||||
{% endif %}
|
||||
|
||||
{% if optionalFields.process_date or journal.process_date %}
|
||||
<!-- PROCESSING DATE -->
|
||||
{{ ExpandedForm.date('process_date', journal.process_date) }}
|
||||
{% endif %}
|
||||
|
||||
{% if optionalFields.due_date or journal.due_date %}
|
||||
<!-- DUE DATE -->
|
||||
{{ ExpandedForm.date('due_date', journal.due_date) }}
|
||||
{% endif %}
|
||||
|
||||
{% if optionalFields.payment_date or journal.payment_date %}
|
||||
<!-- PAYMENT DATE -->
|
||||
{{ ExpandedForm.date('payment_date', journal.payment_date) }}
|
||||
{% endif %}
|
||||
|
||||
{% if optionalFields.internal_reference or journal.internal_reference %}
|
||||
<!-- REFERENCE -->
|
||||
{{ ExpandedForm.text('internal_reference', journal.internal_reference) }}
|
||||
{% endif %}
|
||||
|
||||
{% if optionalFields.notes or journal.notes %}
|
||||
<!-- NOTES -->
|
||||
{{ ExpandedForm.textarea('notes', journal.notes) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'splits'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<table class="table table-bordered table-condensed table-striped split-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ trans('list.split_number') }}</th>
|
||||
<th>{{ trans('list.description') }}</th>
|
||||
|
||||
|
||||
<!-- withdrawal and deposit have a destination. -->
|
||||
{% if preFilled.what == 'withdrawal' %}
|
||||
<th>{{ trans('list.destination') }}</th>
|
||||
{% endif %}
|
||||
|
||||
{% if preFilled.what == 'deposit' %}
|
||||
<th>{{ trans('list.source') }}</th>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<th>{{ trans('list.amount') }}</th>
|
||||
|
||||
<!-- only withdrawal has budget -->
|
||||
{% if preFilled.what == 'withdrawal' %}
|
||||
<th>{{ trans('list.budget') }}</th>
|
||||
{% endif %}
|
||||
<th>{{ trans('list.category') }}</th>
|
||||
|
||||
{% if preFilled.what == 'transfer' %}
|
||||
<th>{{ trans('list.piggy_bank') }}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for index, descr in preFilled.description %}
|
||||
<tr class="{% if loop.index == 1 %}initial-row{% else %}not-initial-row{% endif %}">
|
||||
<td class="count">#{{ loop.index }}</td>
|
||||
<td>
|
||||
<input type="text" name="description[]" value="{{ descr }}" class="form-control"/>
|
||||
</td>
|
||||
|
||||
<!-- withdrawal has several destination names. -->
|
||||
{% if preFilled.what == 'withdrawal' %}
|
||||
<td>
|
||||
<input type="text" name="destination_account_name[]" value="{{ preFilled.destination_account_name[index] }}"
|
||||
class="form-control"/>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
<!-- deposit has several source names -->
|
||||
{% if preFilled.what == 'deposit' %}
|
||||
<td>
|
||||
<input type="text" name="source_account_name[]" value="{{ preFilled.source_account_name[index] }}"
|
||||
class="form-control"/>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<td style="width:10%;">
|
||||
<input type="number" name="amount[]" value="{{ preFilled.amount[index] }}"
|
||||
class="form-control" autocomplete="off" step="any" min="0.01">
|
||||
</td>
|
||||
{% if preFilled.what == 'withdrawal' %}
|
||||
<td>
|
||||
<select class="form-control" name="budget_id[]">
|
||||
{% for key, budget in budgets %}
|
||||
<option label="{{ budget }}" value="{{ key }}"
|
||||
{% if preFilled.budget_id[index] == key %}
|
||||
selected="selected"
|
||||
{% endif %}
|
||||
>{{ budget }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<input type="text" name="category[]" value="{{ preFilled.category[index] }}" class="form-control"/>
|
||||
</td>
|
||||
{% if preFilled.what == 'transfer' %}
|
||||
<td>
|
||||
<!-- RELATE THIS TRANSFER TO A PIGGY BANK -->
|
||||
{{ Form.select('piggy_bank_id[]',piggyBanks, preFilled.piggy_bank_id[index], {class: 'form-control'}) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<br/>
|
||||
<a href="#" class="btn btn-default btn-do-split"><i class="fa fa-plus-circle"></i> {{ 'add_another_split'|_ }}</a>
|
||||
</p>
|
||||
<!--<p class="pull-right">
|
||||
<button type="submit" id="transaction-btn" class="btn btn-success pull-right">
|
||||
{{ ('update_splitted_'~preFilled.what)|_ }}
|
||||
</button>
|
||||
</p>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
{% if optionalFields.attachments %}
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'optionalFields'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<!-- ATTACHMENTS -->
|
||||
{{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<!-- panel for options -->
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'options'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.optionsList('create','split-transaction') }}
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="submit" class="pull-right btn btn-success">{{ ('store_' ~ preFilled.what)|_ }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
var originalSum = {{ preFilled.journal_amount }};
|
||||
var what = "{{ preFilled.what }}";
|
||||
</script>
|
||||
<script type="text/javascript" src="js/lib/bootstrap3-typeahead.min.js"></script>
|
||||
<script type="text/javascript" src="js/ff/transactions/create-edit.js"></script>
|
||||
<script type="text/javascript" src="js/ff/split/journal/from-store.js"></script>
|
||||
{% endblock %}
|
||||
{% block styles %}
|
||||
{% endblock %}
|
@ -1,210 +0,0 @@
|
||||
{% extends "./layout/default.twig" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, journal) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Metadata</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<td style="width:30%;">{{ trans('list.date') }}</td>
|
||||
<td>{{ journal.date.formatLocalized(monthAndDayFormat) }}</td>
|
||||
</tr>
|
||||
{% if journal.interest_date %}
|
||||
<tr>
|
||||
<td>{{ trans('list.interest_date') }}</td>
|
||||
<td>{{ journal.interest_date.formatLocalized(monthAndDayFormat) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if journal.book_date %}
|
||||
<tr>
|
||||
<td>{{ trans('list.book_date') }}</td>
|
||||
<td>{{ journal.book_date.formatLocalized(monthAndDayFormat) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if journal.process_date %}
|
||||
<tr>
|
||||
<td>{{ trans('list.process_date') }}</td>
|
||||
<td>{{ journal.process_date.formatLocalized(monthAndDayFormat) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td>{{ trans('list.type') }}</td>
|
||||
<td>{{ journal.transactiontype.type|_ }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.completed') }}</td>
|
||||
<td>
|
||||
{% if journal.completed %}
|
||||
<span class="text-success">{{ 'yes'|_ }}</span>
|
||||
{% else %}
|
||||
<span class="text-danger">{{ 'no'|_ }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'budgets'|_ }}</td>
|
||||
<td>{{ journalBudgets(journal)|raw }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'categories'|_ }}</td>
|
||||
<td>{{ journalCategories(journal)|raw }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'source_accounts'|_ }}</td>
|
||||
<td>{{ sourceAccount(journal)|raw }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'destination_accounts'|_ }}</td>
|
||||
<td>{{ destinationAccount(journal)|raw }}</td>
|
||||
</tr>
|
||||
{% if journal.bill %}
|
||||
<tr>
|
||||
<td>{{ 'bill'|_ }}</td>
|
||||
<td><a href="{{ route('bills.show', journal.bill_id) }}">{{ journal.bill.name }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if journal.tags|length > 0 %}
|
||||
<tr>
|
||||
<td>{{ 'tags'|_ }}</td>
|
||||
<td>
|
||||
{% for tag in journal.tags %}
|
||||
|
||||
<h4 style="display: inline;"><a class="label label-success" href="{{ route('tags.show',tag) }}">
|
||||
{% if tag.tagMode == 'nothing' %}
|
||||
<i class="fa fa-fw fa-tag"></i>
|
||||
{% endif %}
|
||||
{% if tag.tagMode == 'balancingAct' %}
|
||||
<i class="fa fa-fw fa-refresh"></i>
|
||||
{% endif %}
|
||||
{% if tag.tagMode == 'advancePayment' %}
|
||||
<i class="fa fa-fw fa-sort-numeric-desc"></i>
|
||||
{% endif %}
|
||||
{{ tag.tag }}</a>
|
||||
</h4>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-default" href="{{ route('transactions.edit',journal.id) }}">{{ 'edit'|_ }}</a>
|
||||
<a href="{{ route('transactions.delete',journal.id) }}" class="btn btn-danger">{{ 'delete'|_ }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- events, if present -->
|
||||
{% if journal.piggyBankEvents|length > 0 %}
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'piggyBanks'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{% include 'list/piggy-bank-events' with {'events': events, 'showPiggyBank':true} %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
{% if journal.attachments|length > 0 %}
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'attachments'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
{% for att in journal.attachments %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="btn-group btn-group-xs">
|
||||
<a href="{{ route('attachments.edit', att.id) }}" class="btn btn-default"><i class="fa fa-pencil"></i></a>
|
||||
<a href="{{ route('attachments.delete', att.id) }}" class="btn btn-danger"><i class="fa fa-trash"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<i class="fa {{ att.mime|mimeIcon }}"></i>
|
||||
<a href="{{ route('attachments.download', att.id) }}" title="{{ att.filename }}">
|
||||
{% if att.title %}
|
||||
{{ att.title }}
|
||||
{% else %}
|
||||
{{ att.filename }}
|
||||
{% endif %}
|
||||
</a>
|
||||
({{ att.size|filesize }})
|
||||
{% if att.description %}
|
||||
<br/>
|
||||
<em>{{ att.description }}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="width:100px;">
|
||||
<img src="{{ route('attachments.preview', att.id) }}"/>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- more than two transactions-->
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Transactions</h3>
|
||||
</div>
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ trans('list.description') }}</th>
|
||||
<th>{{ trans('list.account') }}</th>
|
||||
<th>{{ trans('list.amount') }}</th>
|
||||
<th>{{ trans('list.new_balance') }}</th>
|
||||
<th>{{ trans('list.budget') }}</th>
|
||||
<th>{{ trans('list.category') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for index, t in transactions %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if (index+1) != transactions|length or what == 'transfer' %}
|
||||
{{ t.description }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><a href="{{ route('accounts.show',t.account.id) }}">{{ t.account.name }}</a> ({{ t.account.accounttype.type|_ }})</td>
|
||||
<td>{{ t.sum|formatAmount }}</td>
|
||||
<td>{{ t.before|formatAmount }} → {{ (t.sum+t.before)|formatAmount }}</td>
|
||||
<td>
|
||||
{% if (index+1) != transactions|length or what == 'transfer' %}
|
||||
{{ transactionBudgets(t)|raw }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if (index+1) != transactions|length or what == 'transfer' %}
|
||||
{{ transactionCategories(t)|raw }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end -->
|
||||
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user